From bec4ed103a24146f8c3bea72f42458ce36a62a5a Mon Sep 17 00:00:00 2001 From: Josh Klontz Date: Wed, 19 Dec 2012 22:01:21 -0500 Subject: [PATCH] initial release --- .gitignore | 26 ++++++++++++++++++++++++++ CMakeLists.txt | 201 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ CTestConfig.cmake | 12 ++++++++++++ LICENSE.txt | 13 +++++++++++++ README.md | 1 + app/CMakeLists.txt | 13 +++++++++++++ app/br-gui/CMakeLists.txt | 12 ++++++++++++ app/br-gui/main.cpp | 34 ++++++++++++++++++++++++++++++++++ app/br-gui/mainwindow.cpp | 34 ++++++++++++++++++++++++++++++++++ app/br-gui/mainwindow.h | 37 +++++++++++++++++++++++++++++++++++++ app/br/CMakeLists.txt | 7 +++++++ app/br/br.cpp | 199 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ app/examples/CMakeLists.txt | 8 ++++++++ app/examples/compare_face_galleries.cpp | 47 +++++++++++++++++++++++++++++++++++++++++++++++ app/examples/compare_faces.cpp | 42 ++++++++++++++++++++++++++++++++++++++++++ app/examples/evaluate_face_recognition.cpp | 44 ++++++++++++++++++++++++++++++++++++++++++++ app/openbr-gui/CMakeLists.txt | 21 +++++++++++++++++++++ app/openbr-gui/icons/glyphicons_190_circle_plus@2x.png | Bin 0 -> 1566 bytes app/openbr-gui/icons/icons.qrc | 5 +++++ app/openbr-gui/imageviewer.cpp | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ app/openbr-gui/imageviewer.h | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ app/openbr-gui/initialize.cpp | 32 ++++++++++++++++++++++++++++++++ app/openbr-gui/initialize.h | 24 ++++++++++++++++++++++++ app/openbr-gui/transformeditor.cpp | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ app/openbr-gui/transformeditor.h | 41 +++++++++++++++++++++++++++++++++++++++++ app/openbr-gui/transformlisteditor.cpp | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ app/openbr-gui/transformlisteditor.h | 46 ++++++++++++++++++++++++++++++++++++++++++++++ sdk/CMakeLists.txt | 28 ++++++++++++++++++++++++++++ sdk/core/bee.cpp | 361 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sdk/core/bee.h | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sdk/core/classify.cpp | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sdk/core/classify.h | 30 ++++++++++++++++++++++++++++++ sdk/core/cluster.cpp | 358 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sdk/core/cluster.h | 37 +++++++++++++++++++++++++++++++++++++ sdk/core/common.cpp | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sdk/core/common.h | 317 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sdk/core/core.cpp | 320 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sdk/core/fuse.cpp | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sdk/core/fuse.h | 28 ++++++++++++++++++++++++++++ sdk/core/opencvutils.cpp | 339 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sdk/core/opencvutils.h | 125 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sdk/core/plot.cpp | 504 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sdk/core/plot.h | 34 ++++++++++++++++++++++++++++++++++ sdk/core/qtutils.cpp | 293 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sdk/core/qtutils.h | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sdk/core/resource.cpp | 17 +++++++++++++++++ sdk/core/resource.h | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sdk/openbr.cpp | 250 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sdk/openbr.h | 369 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sdk/openbr_export.cpp | 386 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sdk/openbr_export.h | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ sdk/openbr_plugin.cpp | 1215 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sdk/openbr_plugin.h | 1019 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sdk/plugins/format.cpp | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sdk/plugins/gallery.cpp | 663 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sdk/plugins/meta.cpp | 437 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sdk/plugins/misc.cpp | 211 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sdk/plugins/output.cpp | 423 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sdk/plugins/plugins.cmake | 15 +++++++++++++++ share/openbr/Doxyfile.in | 1811 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ share/openbr/Info.plist.in | 30 ++++++++++++++++++++++++++++++ share/openbr/MBGC_file_overview.pdf | Bin 0 -> 83820 bytes share/openbr/bundle.sh | 6 ++++++ share/openbr/cmake/CppcheckTargets.cmake | 213 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ share/openbr/cmake/FindCImg.cmake | 3 +++ share/openbr/cmake/FindCT8.cmake | 29 +++++++++++++++++++++++++++++ share/openbr/cmake/FindEigen3.cmake | 10 ++++++++++ share/openbr/cmake/FindFST3.cmake | 4 ++++ share/openbr/cmake/FindGlyphIcons.cmake | 3 +++ share/openbr/cmake/FindLLVM.cmake | 5 +++++ share/openbr/cmake/FindLibGC.cmake | 3 +++ share/openbr/cmake/FindLibMR.cmake | 4 ++++ share/openbr/cmake/FindLibQxt.cmake | 21 +++++++++++++++++++++ share/openbr/cmake/FindLibSVM.cmake | 4 ++++ share/openbr/cmake/FindLoki.cmake | 3 +++ share/openbr/cmake/FindMKL.cmake | 6 ++++++ share/openbr/cmake/FindNT4.cmake | 32 ++++++++++++++++++++++++++++++++ share/openbr/cmake/FindOpenCL.cmake | 155 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ share/openbr/cmake/FindPP4.cmake | 18 ++++++++++++++++++ share/openbr/cmake/FindPP5.cmake | 17 +++++++++++++++++ share/openbr/cmake/FindQwt.cmake | 4 ++++ share/openbr/cmake/FindTopSurf.cmake | 5 +++++ share/openbr/cmake/FindWinDDK.cmake | 9 +++++++++ share/openbr/cmake/FindYKPers.cmake | 21 +++++++++++++++++++++ share/openbr/cmake/FindYubiKey.cmake | 15 +++++++++++++++ share/openbr/cmake/Findcppcheck.cmake | 163 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ share/openbr/cmake/Findcppcheck.cpp | 17 +++++++++++++++++ share/openbr/cmake/Findlibusb1.0.cmake | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ share/openbr/cmake/FindpHash.cmake | 4 ++++ share/openbr/cmake/InstallDependencies.cmake | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ share/openbr/cmake/NSIS.InstallOptions.ini.in | 46 ++++++++++++++++++++++++++++++++++++++++++++++ share/openbr/cmake/NSIS.template.in | 949 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ share/openbr/cmake/OpenBRConfig.cmake | 13 +++++++++++++ share/openbr/cmake/UseLATEX.cmake | 1288 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ share/openbr/openbr-small.png | Bin 0 -> 2890 bytes share/openbr/openbr.bib | 151 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ share/openbr/openbr.icns | Bin 0 -> 86802 bytes share/openbr/openbr.ico | Bin 0 -> 136014 bytes share/openbr/openbr.png | Bin 0 -> 12456 bytes share/openbr/resources.rc.in | 24 ++++++++++++++++++++++++ share/openbr/version.h.in | 16 ++++++++++++++++ swig/CMakeLists.txt | 42 ++++++++++++++++++++++++++++++++++++++++++ swig/mm_sdk_swig.i | 7 +++++++ 103 files changed, 14998 insertions(+), 0 deletions(-) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 CTestConfig.cmake create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 app/CMakeLists.txt create mode 100644 app/br-gui/CMakeLists.txt create mode 100644 app/br-gui/main.cpp create mode 100644 app/br-gui/mainwindow.cpp create mode 100644 app/br-gui/mainwindow.h create mode 100644 app/br/CMakeLists.txt create mode 100644 app/br/br.cpp create mode 100644 app/examples/CMakeLists.txt create mode 100644 app/examples/compare_face_galleries.cpp create mode 100644 app/examples/compare_faces.cpp create mode 100644 app/examples/evaluate_face_recognition.cpp create mode 100644 app/openbr-gui/CMakeLists.txt create mode 100644 app/openbr-gui/icons/glyphicons_190_circle_plus@2x.png create mode 100644 app/openbr-gui/icons/icons.qrc create mode 100644 app/openbr-gui/imageviewer.cpp create mode 100644 app/openbr-gui/imageviewer.h create mode 100644 app/openbr-gui/initialize.cpp create mode 100644 app/openbr-gui/initialize.h create mode 100644 app/openbr-gui/transformeditor.cpp create mode 100644 app/openbr-gui/transformeditor.h create mode 100644 app/openbr-gui/transformlisteditor.cpp create mode 100644 app/openbr-gui/transformlisteditor.h create mode 100644 sdk/CMakeLists.txt create mode 100644 sdk/core/bee.cpp create mode 100644 sdk/core/bee.h create mode 100644 sdk/core/classify.cpp create mode 100644 sdk/core/classify.h create mode 100644 sdk/core/cluster.cpp create mode 100644 sdk/core/cluster.h create mode 100644 sdk/core/common.cpp create mode 100644 sdk/core/common.h create mode 100644 sdk/core/core.cpp create mode 100644 sdk/core/fuse.cpp create mode 100644 sdk/core/fuse.h create mode 100644 sdk/core/opencvutils.cpp create mode 100644 sdk/core/opencvutils.h create mode 100644 sdk/core/plot.cpp create mode 100644 sdk/core/plot.h create mode 100644 sdk/core/qtutils.cpp create mode 100644 sdk/core/qtutils.h create mode 100644 sdk/core/resource.cpp create mode 100644 sdk/core/resource.h create mode 100644 sdk/openbr.cpp create mode 100644 sdk/openbr.h create mode 100644 sdk/openbr_export.cpp create mode 100644 sdk/openbr_export.h create mode 100644 sdk/openbr_plugin.cpp create mode 100644 sdk/openbr_plugin.h create mode 100644 sdk/plugins/format.cpp create mode 100644 sdk/plugins/gallery.cpp create mode 100644 sdk/plugins/meta.cpp create mode 100644 sdk/plugins/misc.cpp create mode 100644 sdk/plugins/output.cpp create mode 100644 sdk/plugins/plugins.cmake create mode 100644 share/openbr/Doxyfile.in create mode 100644 share/openbr/Info.plist.in create mode 100644 share/openbr/MBGC_file_overview.pdf create mode 100755 share/openbr/bundle.sh create mode 100644 share/openbr/cmake/CppcheckTargets.cmake create mode 100644 share/openbr/cmake/FindCImg.cmake create mode 100644 share/openbr/cmake/FindCT8.cmake create mode 100644 share/openbr/cmake/FindEigen3.cmake create mode 100644 share/openbr/cmake/FindFST3.cmake create mode 100644 share/openbr/cmake/FindGlyphIcons.cmake create mode 100644 share/openbr/cmake/FindLLVM.cmake create mode 100644 share/openbr/cmake/FindLibGC.cmake create mode 100644 share/openbr/cmake/FindLibMR.cmake create mode 100644 share/openbr/cmake/FindLibQxt.cmake create mode 100644 share/openbr/cmake/FindLibSVM.cmake create mode 100644 share/openbr/cmake/FindLoki.cmake create mode 100644 share/openbr/cmake/FindMKL.cmake create mode 100644 share/openbr/cmake/FindNT4.cmake create mode 100644 share/openbr/cmake/FindOpenCL.cmake create mode 100644 share/openbr/cmake/FindPP4.cmake create mode 100644 share/openbr/cmake/FindPP5.cmake create mode 100644 share/openbr/cmake/FindQwt.cmake create mode 100644 share/openbr/cmake/FindTopSurf.cmake create mode 100644 share/openbr/cmake/FindWinDDK.cmake create mode 100644 share/openbr/cmake/FindYKPers.cmake create mode 100644 share/openbr/cmake/FindYubiKey.cmake create mode 100644 share/openbr/cmake/Findcppcheck.cmake create mode 100644 share/openbr/cmake/Findcppcheck.cpp create mode 100644 share/openbr/cmake/Findlibusb1.0.cmake create mode 100644 share/openbr/cmake/FindpHash.cmake create mode 100644 share/openbr/cmake/InstallDependencies.cmake create mode 100644 share/openbr/cmake/NSIS.InstallOptions.ini.in create mode 100644 share/openbr/cmake/NSIS.template.in create mode 100644 share/openbr/cmake/OpenBRConfig.cmake create mode 100644 share/openbr/cmake/UseLATEX.cmake create mode 100644 share/openbr/openbr-small.png create mode 100644 share/openbr/openbr.bib create mode 100644 share/openbr/openbr.icns create mode 100644 share/openbr/openbr.ico create mode 100644 share/openbr/openbr.png create mode 100644 share/openbr/resources.rc.in create mode 100644 share/openbr/version.h.in create mode 100644 swig/CMakeLists.txt create mode 100644 swig/mm_sdk_swig.i diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..513bd91 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +#Repository specific +build* + +#Generic +*.log +*.user + +#Latex +*.aux +*.nav +*.out +*.snm +*.toc + +#QtCreator +*CMakeLists.txt.user* + +#R +*.RData +*.Rhistory + +#Subversion +*.svn* + +#Synology +*.DS* diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e669d79 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,201 @@ +project(openbr) +cmake_minimum_required(VERSION 2.8.6) + +# Global settings +set(BR_SHARE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/share/openbr") +set(CPACK_PACKAGE_NAME "OpenBR") +set(CPACK_PACKAGE_VENDOR "Open Biometric Recognition") +set(CPACK_PACKAGE_DESCRIPTION "Open Source Biometric Recognition and Evaluation") +set(CPACK_PACKAGE_INSTALL_DIRECTORY ${CPACK_PACKAGE_NAME}) +set(CPACK_PACKAGE_VERSION_MAJOR 0) +set(CPACK_PACKAGE_VERSION_MINOR 1) +set(CPACK_PACKAGE_VERSION_PATCH 0) +set(CPACK_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") +set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.txt") +set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") +set(CMAKE_MODULE_PATH "${BR_SHARE_DIR}/cmake" ${CMAKE_MODULE_PATH}) +set(PACKAGE_YEAR 2012) + +# Define icons +if(WIN32) + set(CPACK_PACKAGE_ICON "${BR_SHARE_DIR}\\\\openbr-small.png") + set(NATIVE_ICON "${BR_SHARE_DIR}/openbr.ico") +elseif(APPLE) + set(CPACK_PACKAGE_ICON "${BR_SHARE_DIR}/openbr-small.png") + set(NATIVE_ICON "${BR_SHARE_DIR}/openbr.icns") +else() + set(CPACK_PACKAGE_ICON "${BR_SHARE_DIR}/openbr-small.png") + set(NATIVE_ICON "${BR_SHARE_DIR}/openbr.png") +endif() + +# Add resource file for Windows builds +if(WIN32) + configure_file(${BR_SHARE_DIR}/resources.rc.in resources.rc) + set(BR_WINDOWS_RESOURCES ${CMAKE_CURRENT_BINARY_DIR}/resources.rc) +endif() + +# Add icon file for Apple builds +if(APPLE) + set(BR_APPLE_RESOURCES ${NATIVE_ICON}) +endif() + +if(UNIX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -fvisibility=hidden -fno-omit-frame-pointer") +endif() + +if(CMAKE_COMPILER_IS_GNUXX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Weffc++") +endif() + +if(MINGW) + # Fixes a linker warning + set(CMAKE_EXE_LINKER_FLAGS "-Wl,--enable-auto-import") + set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--enable-auto-import") +endif() + +if(MSVC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W3 /DNOMINMAX /D_CRT_SECURE_NO_WARNINGS /wd4018 /wd4244 /wd4267 /wd4305 /wd4308 /wd4307 /wd4554 /wd4996 /nologo") +endif() + +if(${CMAKE_C_COMPILER} STREQUAL "/usr/bin/icc") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -wd279 -wd2196") +endif() + +# Configure build +option(BR_EMBEDDED "Limit software dependencies") +if(${BR_EMBEDDED}) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DBR_EMBEDDED") + set(QT_DEPENDENCIES QtCore) +else() + set(QT_DEPENDENCIES QtCore QtGui QtNetwork QtOpenGL QtSql QtXml) +endif() + +option(BR_DISTRIBUTED "Target distributed memory models") +if(${BR_DISTRIBUTED}) + find_package(MPI REQUIRED) + set(CMAKE_CXX_COMPILE_FLAGS ${CMAKE_CXX_COMPILE_FLAGS} ${MPI_COMPILE_FLAGS}) + set(CMAKE_CXX_LINK_FLAGS ${CMAKE_CXX_LINK_FLAGS} ${MPI_LINK_FLAGS}) + include_directories(MPI_INCLUDE_PATH) + set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -DBR_DISTRIBUTED) + set(BR_THIRDPARTY_LIBS ${BR_THIRDPARTY_LIBS} ${MPI_LIBRARY}) +endif() + +option(BR_EXCEPTIONS "Enable exception handling" ON) +if(${BR_EXCEPTIONS}) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DBR_EXCEPTIONS") +endif() + +# Find Qt +find_package(Qt4 COMPONENTS ${QT_DEPENDENCIES} REQUIRED) +set(QT_USE_QTMAIN TRUE) +include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +set(BR_THIRDPARTY_LIBS ${BR_THIRDPARTY_LIBS} ${QT_LIBRARIES}) + +# Find OpenCV +find_package(OpenCV REQUIRED) +set(BR_THIRDPARTY_LIBS ${BR_THIRDPARTY_LIBS} ${OpenCV_LIBS}) +set(OPENCV_DEPENDENCIES calib3d contrib core features2d flann gpu highgui imgproc legacy ml nonfree objdetect photo stitching ts video videostab) + +# Enable Testing +include(CppcheckTargets) +include(CTest) +enable_testing() +if(BUILD_TESTING) + set(BUILDNAME "${BUILDNAME}" CACHE STRING "Name of build on the dashboard") + mark_as_advanced(BUILDNAME) +endif() + +# Build the SDK +include_directories(sdk) +add_subdirectory(sdk) + +# Build applications +add_subdirectory(app) + +# Build SWIG wrappers +add_subdirectory(swig) + +# Build the documentation? +option(BR_BUILD_DOCUMENTATION "Build Documentation (Requires doxygen and latex)") +if(${BR_BUILD_DOCUMENTATION}) + find_package(Doxygen REQUIRED) + configure_file(${BR_SHARE_DIR}/Doxyfile.in Doxyfile) + add_custom_target(doc ALL ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/share/openbr/MBGC_file_overview.pdf ${CMAKE_CURRENT_BINARY_DIR}/html/MBGC_file_overview.pdf COPYONLY) + install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION .) + + option(BR_BUILD_DOCUMENTATION_PDF "Build PDF Documentation") + if(${BR_BUILD_DOCUMENTATION_PDF}) + include(${BR_SHARE_DIR}/cmake/UseLATEX.cmake) + add_custom_command(OUTPUT latex/refman.aux DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/latex/refman.tex COMMAND ${PDFLATEX_COMPILER} refman WORKING_DIRECTORY latex) + add_custom_command(OUTPUT latex/refman.bbl DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/latex/refman.aux COMMAND ${BIBTEX_COMPILER} refman WORKING_DIRECTORY latex) + add_custom_command(OUTPUT latex/refman.dvi DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/latex/refman.bbl COMMAND ${PDFLATEX_COMPILER} refman WORKING_DIRECTORY latex) + add_custom_command(OUTPUT latex/refman.log DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/latex/refman.bbl latex/refman.dvi COMMAND ${PDFLATEX_COMPILER} refman WORKING_DIRECTORY latex) + add_custom_target(doc_pdf ALL echo DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/latex/refman.log doc) # Trigger latex build process + install(FILES ${CMAKE_BINARY_DIR}/latex/refman.pdf DESTINATION .) + endif() +endif() + +# Install +install(FILES LICENSE.txt README.md DESTINATION .) +install(DIRECTORY share DESTINATION .) +install(DIRECTORY ${BR_THIRDPARTY_SHARE} DESTINATION share) +include(InstallDependencies) +install_compiler_libraries() +install_qt_libraries(QT_DEPENDENCIES) +install_qt_imageformats() +install_opencv_libraries(OPENCV_DEPENDENCIES) + +# Package +set(BR_PUBLISH_DIR ${CMAKE_BINARY_DIR} CACHE PATH "Where to publish packages") +set(CPACK_OUTPUT_FILE_PREFIX ${BR_PUBLISH_DIR}) +set(CPACK_BINARY_BUNDLE OFF) +set(CPACK_BINARY_DEB OFF) +set(CPACK_BINARY_DRAGNDROP OFF) +set(CPACK_BINARY_NSIS OFF) +set(CPACK_BINARY_OSXX11 OFF) +set(CPACK_BINARY_PACKAGEMAKER OFF) +set(CPACK_BINARY_RPM OFF) +set(CPACK_BINARY_STGZ OFF) +set(CPACK_BINARY_TBZ2 OFF) +set(CPACK_BINARY_TGZ OFF) +set(CPACK_BINARY_TZ OFF) +set(CPACK_BINARY_ZIP OFF) +set(CPACK_SOURCE_TGZ OFF) +set(CPACK_SOURCE_TZ OFF) +set(CPACK_SOURCE_ZIP OFF) + +if(CMAKE_HOST_WIN32) + set(CPACK_BINARY_ZIP ON) + set(CPACK_BINARY_NSIS ON) + + # NSIS + set(CPACK_NSIS_MODIFY_PATH ON) + set(CPACK_NSIS_MUI_ICON ${NATIVE_ICON}) + set(CPACK_NSIS_MUI_UNIICON ${NATIVE_ICON}) + set(CPACK_NSIS_MENU_LINKS "doc/html/index.html" "Documentation") + if("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + set(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES64") + endif() +elseif(CMAKE_HOST_APPLE) + set(CPACK_BINARY_TGZ ON) + set(CPACK_BINARY_BUNDLE ON) + + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/README.md" "README.txt" COPYONLY) + set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_BINARY_DIR}/README.txt") + + set(CPACK_BUNDLE_NAME ${CPACK_PACKAGE_NAME}) + set(CPACK_BUNDLE_ICON ${NATIVE_ICON}) + set(CPACK_BUNDLE_PLIST ${CMAKE_CURRENT_BINARY_DIR}/Info.plist) + set(CPACK_BUNDLE_STARTUP_COMMAND ${BR_SHARE_DIR}/bundle.sh) + configure_file(${BR_SHARE_DIR}/Info.plist.in Info.plist) +else() + set(CPACK_BINARY_TBZ2 ON) + set(CPACK_BINARY_DEB ON) + + # DEB + set(CPACK_DEBIAN_PACKAGE_MAINTAINER "jklontz@ieee.org") +endif() + +include(CPack) diff --git a/CTestConfig.cmake b/CTestConfig.cmake new file mode 100644 index 0000000..e5935c7 --- /dev/null +++ b/CTestConfig.cmake @@ -0,0 +1,12 @@ +set(CTEST_PROJECT_NAME ${CPACK_PACKAGE_NAME}) +set(CTEST_NIGHTLY_START_TIME "00:00:00 EST") + +if(NOT DEFINED CTEST_DROP_METHOD) + set(CTEST_DROP_METHOD "http") +endif() + +if(CTEST_DROP_METHOD STREQUAL "http") + set(CTEST_DROP_SITE "my.cdash.org") + set(CTEST_DROP_LOCATION "/submit.php?project=OpenBR") + set(CTEST_DROP_SITE_CDASH TRUE) +endif(CTEST_DROP_METHOD STREQUAL "http") diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..661310f --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,13 @@ +Copyright 2012 The MITRE Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b0621be --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +Open Source Biometrics and Evaluation \ No newline at end of file diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt new file mode 100644 index 0000000..db66efa --- /dev/null +++ b/app/CMakeLists.txt @@ -0,0 +1,13 @@ +include_directories(.) + +# Build the command line interface +add_subdirectory(br) + +# Build the gui sdk +add_subdirectory(openbr-gui) + +# Build the gui interface +add_subdirectory(br-gui) + +# Build examples/tests +add_subdirectory(examples) diff --git a/app/br-gui/CMakeLists.txt b/app/br-gui/CMakeLists.txt new file mode 100644 index 0000000..98a54db --- /dev/null +++ b/app/br-gui/CMakeLists.txt @@ -0,0 +1,12 @@ +if(NOT ${BR_EMBEDDED}) + include_directories(.) + aux_source_directory(. SRC) + + # Build executable + add_executable(br-gui WIN32 ${SRC} ${MOC} ${UIC} ${WINDOWS_RESOURCES} ${APPLE_RESOURCES}) + set_target_properties(br-gui PROPERTIES AUTOMOC TRUE) + target_link_libraries(br-gui openbr openbr-gui ${QT_LIBRARIES}) + + # Install + install(TARGETS br-gui RUNTIME DESTINATION bin BUNDLE DESTINATION .) +endif() diff --git a/app/br-gui/main.cpp b/app/br-gui/main.cpp new file mode 100644 index 0000000..fb72d3e --- /dev/null +++ b/app/br-gui/main.cpp @@ -0,0 +1,34 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include +#include + +#include "mainwindow.h" + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + br_initialize_gui(); + + MainWindow mainWindow; + mainWindow.show(); + + int result = a.exec(); + br_finalize(); + return result; +} diff --git a/app/br-gui/mainwindow.cpp b/app/br-gui/mainwindow.cpp new file mode 100644 index 0000000..a492911 --- /dev/null +++ b/app/br-gui/mainwindow.cpp @@ -0,0 +1,34 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +#include "mainwindow.h" + +using namespace br; + +/**** MAIN_WINDOW ****/ +/*** PUBLIC ***/ +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , transform(Transform::make("Pipe([Open])", this)) + , transformEditor(transform) +{ + setWindowTitle("OpenBR " + Context::version()); + layout.addWidget(&transformEditor); + mainWidget.setLayout(&layout); + setCentralWidget(&transformEditor); +} diff --git a/app/br-gui/mainwindow.h b/app/br-gui/mainwindow.h new file mode 100644 index 0000000..c1b5f7e --- /dev/null +++ b/app/br-gui/mainwindow.h @@ -0,0 +1,37 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef __MAINWINDOW_H +#define __MAINWINDOW_H + +#include +#include +#include +#include + +class MainWindow : public QMainWindow +{ + Q_OBJECT + QWidget mainWidget; + QVBoxLayout layout; + br::Transform *transform; + br::TransformEditor transformEditor; + +public: + explicit MainWindow(QWidget *parent = 0); +}; + +#endif // __MAINWINDOW_H diff --git a/app/br/CMakeLists.txt b/app/br/CMakeLists.txt new file mode 100644 index 0000000..dc25cd4 --- /dev/null +++ b/app/br/CMakeLists.txt @@ -0,0 +1,7 @@ +if(UNIX) + find_package(Threads REQUIRED) +endif() + +add_executable(br br.cpp) +target_link_libraries(br openbr ${CMAKE_THREAD_LIBS_INIT}) +install(TARGETS br RUNTIME DESTINATION bin) diff --git a/app/br/br.cpp b/app/br/br.cpp new file mode 100644 index 0000000..5bf7f97 --- /dev/null +++ b/app/br/br.cpp @@ -0,0 +1,199 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include +#include +#include + +/*! + * \defgroup cli Command Line Interface + * \brief Command line application for running algorithms and evaluating results. + * + * The easiest and fastest way to leverage the project, we use it all the time! + * Commands are designed to mirror the \ref c_sdk and are evaluated in the order they are entered. + * To get started, try running: + * \code + * $ br -help + * \endcode + * + * See \ref examples for common use cases. + */ + +static void help() +{ + printf(" = Input; {arg} = Output; [arg] = Optional; (arg0|...|argN) = Choice\n" + "\n" + "==== Core Commands ====\n" + "-train ... [{model}]\n" + "-enroll ... {output_gallery}\n" + "-compare [{output}]\n" + "-eval [{csv}]\n" + "-plot ... {destination}\n" + "\n" + "==== Other Commands ====\n" + "-fuse ... (None|MinMax|ZScore|WScore) (Min|Max|Sum[W1:W2:...:Wn]|Replace|Difference|None) {simmat}\n" + "-cluster ... {csv}\n" + "-makeMask {mask}\n" + "-combineMasks ... {mask} (And|Or)\n" + "-convert <(csv,simmat,mask)> {(csv,simmat,mask)}\n" + "-reformat {output}\n" + "-evalClassification \n" + "-evalRegression \n" + "-evalClusters \n" + "-confusion \n" + "-plotMetadata ... \n" + "\n" + "==== Configuration ====\n" + "- \n" + "\n" + "==== Miscellaneous ====\n" + "-objects [abstraction [implementation]]\n" + "-about\n" + "-version\n" + "-shell\n" + "-exit\n"); +} + +static void check(bool condition, const char *error_message) +{ + if (!condition) { + printf("%s\n", error_message); + br_finalize(); + exit(1); + } +} + +int main(int argc, char *argv[]) +{ + br_initialize(argc, argv); + + // Remove program name + argv = &argv[1]; + argc--; + + if (argc == 0) printf("%s\nTry running 'br -help'\n", br_about()); + + bool shell = false; + while (shell || (argc > 0)) { + const char *fun; + int parc; + const char **parv; + if (shell) { + printf("> "); + br_read_line(&parc, &parv); + if (parc == 0) continue; + fun = parv[0]; + parc--; + parv = &parv[1]; + } else /* argc > 0 */ { + fun = &argv[0][1]; + parc = 0; while ((1+parc < argc) && (argv[1+parc][0] != '-')) parc++; + parv = (const char **)&argv[1]; + argc = argc - (1+parc); + argv = &argv[parc+1]; + } + + // Core Tasks + if (!strcmp(fun, "train")) { + check(parc >= 1, "Insufficient parameter count for 'train'."); + br_train_n(parc == 1 ? 1 : parc-1, parv, parc == 1 ? "" : parv[parc-1]); + } else if (!strcmp(fun, "enroll")) { + check(parc >= 1, "Insufficient parameter count for 'enroll'."); + if (parc == 1) br_enroll(parv[0]); + else br_enroll_n(parc-1, parv, parv[parc-1]); + } else if (!strcmp(fun, "compare")) { + check((parc >= 2) && (parc <= 3), "Incorrect parameter count for 'compare'."); + br_compare(parv[0], parv[1], parc == 3 ? parv[2] : ""); + } else if (!strcmp(fun, "eval")) { + check((parc >= 2) && (parc <= 3), "Incorrect parameter count for 'eval'."); + br_eval(parv[0], parv[1], parc == 3 ? parv[2] : ""); + } else if (!strcmp(fun, "plot")) { + check(parc >= 2, "Incorrect parameter count for 'plot'."); + br_plot(parc-1, parv, parv[parc-1], true); + } + + // Secondary Tasks + else if (!strcmp(fun, "fuse")) { + check(parc >= 5, "Insufficient parameter count for 'fuse'."); + br_fuse(parc-4, parv, parv[parc-4], parv[parc-3], parv[parc-2], parv[parc-1]); + } else if (!strcmp(fun, "cluster")) { + check(parc >= 3, "Insufficient parameter count for 'cluster'."); + br_cluster(parc-2, parv, atof(parv[parc-2]), parv[parc-1]); + } else if (!strcmp(fun, "makeMask")) { + check(parc == 3, "Incorrect parameter count for 'makeMask'."); + br_make_mask(parv[0], parv[1], parv[2]); + } else if (!strcmp(fun, "combineMasks")) { + check(parc >= 4, "Insufficient parameter count for 'combineMasks'."); + br_combine_masks(parc-2, parv, parv[parc-2], parv[parc-1]); + } else if (!strcmp(fun, "convert")) { + check(parc == 2, "Incorrect parameter count for 'convert'."); + br_convert(parv[0], parv[1]); + } else if (!strcmp(fun, "reformat")) { + check(parc == 4, "Incorrect parameter count for 'reformat'."); + br_reformat(parv[0], parv[1], parv[2], parv[3]); + } else if (!strcmp(fun, "evalClassification")) { + check(parc == 2, "Incorrect parameter count for 'evalClassification'."); + br_eval_classification(parv[0], parv[1]); + } else if (!strcmp(fun, "evalRegression")) { + check(parc == 2, "Incorrect parameter count for 'evalRegression'."); + br_eval_regression(parv[0], parv[1]); + } else if (!strcmp(fun, "evalClusters")) { + check(parc == 2, "Incorrect parameter count for 'evalClusters'."); + br_eval_clustering(parv[0], parv[1]); + } else if (!strcmp(fun, "confusion")) { + check(parc == 2, "Incorrect parameter count for 'confusion'."); + int true_positives, false_positives, true_negatives, false_negatives; + br_confusion(parv[0], atof(parv[1]), + &true_positives, &false_positives, &true_negatives, &false_negatives); + printf("True Positives = %d\nFalse Positives = %d\nTrue Negatives = %d\nFalseNegatives = %d\n", + true_positives, false_positives, true_negatives, false_negatives); + } else if (!strcmp(fun, "plotMetadata")) { + check(parc >= 2, "Incorrect parameter count for 'plotMetadata'."); + br_plot_metadata(parc-1, parv, parv[parc-1], true); + } + + // Miscellaneous + else if (!strcmp(fun, "help")) { + check(parc == 0, "No parameters expected for 'help'."); + help(); + } else if (!strcmp(fun, "objects")) { + check(parc <= 2, "Incorrect parameter count for 'objects'."); + printf("%s\n", br_objects(parc >= 1 ? parv[0] : ".*", parc >= 2 ? parv[1] : ".*")); + } else if (!strcmp(fun, "about")) { + check(parc == 0, "No parameters expected for 'about'."); + printf("%s\n", br_about()); + } else if (!strcmp(fun, "version")) { + check(parc == 0, "No parameters expected for 'version'."); + printf("%s\n", br_version()); + } else if (!strcmp(fun, "shell")) { + check(parc == 0, "No parameters expected for 'shell'."); + shell = true; + } else if (!strcmp(fun, "exit")) { + check(parc == 0, "No parameters expected for 'exit'."); + shell = false; + } else if (!strcmp(fun, "br")) { + printf("That's me!\n"); + } else if (parc <= 1) { + br_set_property(fun, parc >=1 ? parv[0] : ""); + } else { + printf("Unrecognized function '%s'\n", fun); + } + } + + br_finalize(); + return 0; +} diff --git a/app/examples/CMakeLists.txt b/app/examples/CMakeLists.txt new file mode 100644 index 0000000..f05f50d --- /dev/null +++ b/app/examples/CMakeLists.txt @@ -0,0 +1,8 @@ +file(GLOB EXAMPLES *.cpp) +foreach(EXAMPLE ${EXAMPLES}) + get_filename_component(EXAMPLE_BASENAME ${EXAMPLE} NAME_WE) + set(EXAMPLE_TARGET "br-${EXAMPLE_BASENAME}") + add_executable(${EXAMPLE_TARGET} ${EXAMPLE}) + target_link_libraries(${EXAMPLE_TARGET} openbr) + add_test("${EXAMPLE_BASENAME}_test" ${EXAMPLE_TARGET}) +endforeach() diff --git a/app/examples/compare_face_galleries.cpp b/app/examples/compare_face_galleries.cpp new file mode 100644 index 0000000..a7ddc51 --- /dev/null +++ b/app/examples/compare_face_galleries.cpp @@ -0,0 +1,47 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +//! [compare_face_galleries] +// Command line equivalent: +// $ br -algorithm FaceRecognition -forceEnrollment -path ../share/openbr/images/ \ +// -enroll ../share/openbr/images.xml 'images.gal;images.csv[separator=;]' \ +// -compare images.gal images.gal scores.mtx \ +// -convert scores.mtx scores.csv + +#include + +int main(int argc, char *argv[]) +{ + br_initialize(argc, argv); + br_set_property("algorithm", "FaceRecognition"); + br_set_property("forceEnrollment", "true"); + + // Used to resolve images specified with relative paths + br_set_property("path", "../share/openbr/images/"); + + // 'images.gal' is a binary format used for template comparison, 'images.csv' is a text format used to retrieve template metadata. + br_enroll("../share/openbr/images.xml", "images.gal;images.csv[separator=;]"); + + // Create a binary self-similarity matrix + br_compare("images.gal", "images.gal", "scores.mtx"); + + // Convert the similarity matrix to a readable text format + br_convert("scores.mtx", "scores.csv"); + + br_finalize(); + return 0; +} +//! [compare_face_galleries] diff --git a/app/examples/compare_faces.cpp b/app/examples/compare_faces.cpp new file mode 100644 index 0000000..e56d3d7 --- /dev/null +++ b/app/examples/compare_faces.cpp @@ -0,0 +1,42 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +//! [compare_faces] +// Command line equivalent: +// $ br -algorithm FaceRecognition -forceEnrollment \ +// -compare ../share/openbr/images/S354-01-t10_01.jpg ../share/openbr/images/S354-02-t10_01.jpg \ +// -compare ../share/openbr/images/S024-01-t10_01.jpg ../share/openbr/images/S354-02-t10_01.jpg + +#include + +int main(int argc, char *argv[]) +{ + br_initialize(argc, argv); + + // Specify how to enroll and compare images + br_set_property("algorithm", "FaceRecognition"); + + // Enroll exactly one template per image + br_set_property("forceEnrollment", "true"); + + // Images taken from MEDS-II dataset: http://www.nist.gov/itl/iad/ig/sd32.cfm + br_compare("../share/openbr/images/S354-01-t10_01.jpg", "../share/openbr/images/S354-02-t10_01.jpg"); + br_compare("../share/openbr/images/S024-01-t10_01.jpg", "../share/openbr/images/S354-02-t10_01.jpg"); + + br_finalize(); + return 0; +} +//! [compare_faces] diff --git a/app/examples/evaluate_face_recognition.cpp b/app/examples/evaluate_face_recognition.cpp new file mode 100644 index 0000000..aafc78e --- /dev/null +++ b/app/examples/evaluate_face_recognition.cpp @@ -0,0 +1,44 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +//! [evaluate_face_recognition] +// Command line equivalent: +// $ mkdir Algorithm_Dataset +// $ br -makeMask ../share/openbr/images.xml ../share/openbr/images.xml images.mask \ +// -eval images.mtx images.mask Algorithm_Dataset/FaceRecognition_MEDS.csv results \ +// -plot AlgorithmDataset + +#include + +int main(int argc, char *argv[]) +{ + br_initialize(argc, argv); + + // Make a self-similar ground truth "mask" matrix from a sigset. + br_make_mask("../share/openbr/images.xml", "../share/openbr/images.xml", "scores.mask"); + + // First run "compare_face_galleries" to generate "scores.mtx" + br_eval("scores.mtx", "scores.mask", "Algorithm_Dataset/FaceRecognition_MEDS.csv"); + + // Requires R installation, see documentation of br_plot for details + const char *files[1]; + files[0] = "Algorithm_Dataset/FaceRecognition_MEDS.csv"; + br_plot(1, files, "results", true); + + br_finalize(); + return 0; +} +//! [evaluate_face_recognition] diff --git a/app/openbr-gui/CMakeLists.txt b/app/openbr-gui/CMakeLists.txt new file mode 100644 index 0000000..b223fea --- /dev/null +++ b/app/openbr-gui/CMakeLists.txt @@ -0,0 +1,21 @@ +if(NOT ${BR_EMBEDDED}) + include_directories(.) + include_directories(${CMAKE_CURRENT_BINARY_DIR}) + + file(GLOB SRC *.cpp) + file(GLOB HEADERS *.h) + qt4_add_resources(ICONS icons/icons.qrc) # Don't forget to modify initialize.cpp + + add_library(openbr-gui SHARED ${SRC} ${ICONS} ${BR_WINDOWS_RESOURCES}) + set_target_properties(openbr-gui PROPERTIES + DEFINE_SYMBOL BR_LIBRARY_GUI + VERSION ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH} + SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR} + LINK_INTERFACE_LIBRARIES "" + AUTOMOC TRUE) + target_link_libraries(openbr-gui openbr ${QT_LIBRARIES} ${OpenCV_LIBS}) + add_cppcheck(openbr-gui) + + install(FILES ${HEADERS} DESTINATION include/openbr-gui) + install(TARGETS openbr-gui RUNTIME DESTINATION bin LIBRARY DESTINATION lib ARCHIVE DESTINATION lib) +endif() diff --git a/app/openbr-gui/icons/glyphicons_190_circle_plus@2x.png b/app/openbr-gui/icons/glyphicons_190_circle_plus@2x.png new file mode 100644 index 0000000..e026a41 Binary files /dev/null and b/app/openbr-gui/icons/glyphicons_190_circle_plus@2x.png differ diff --git a/app/openbr-gui/icons/icons.qrc b/app/openbr-gui/icons/icons.qrc new file mode 100644 index 0000000..45c638d --- /dev/null +++ b/app/openbr-gui/icons/icons.qrc @@ -0,0 +1,5 @@ + + + glyphicons_190_circle_plus@2x.png + + diff --git a/app/openbr-gui/imageviewer.cpp b/app/openbr-gui/imageviewer.cpp new file mode 100644 index 0000000..834b1a0 --- /dev/null +++ b/app/openbr-gui/imageviewer.cpp @@ -0,0 +1,96 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include +#include + +#include "imageviewer.h" + +/*** PUBLIC ***/ +br::ImageViewer::ImageViewer(QWidget *parent) + : QLabel(parent) +{ + setAlignment(Qt::AlignCenter); + setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); +} + +void br::ImageViewer::setDefaultText(const QString &text, bool async) +{ + defaultText = text; + updatePixmap(async); +} + +void br::ImageViewer::setImage(const QString &file, bool async) +{ + src = QImage(file); + updatePixmap(async); + +} + +void br::ImageViewer::setImage(const QImage &image, bool async) +{ + src = image; + updatePixmap(async); +} + +void br::ImageViewer::setImage(const QPixmap &pixmap, bool async) +{ + src = pixmap.toImage(); + updatePixmap(async); +} + +/*** PRIVATE ***/ +void br::ImageViewer::updatePixmap(bool async) +{ + if (async) { + QTimer::singleShot(0, this, SLOT(updatePixmap())); + return; + } + + if (src.isNull()) { + QLabel::setPixmap(QPixmap()); + setText(defaultText); + } else { + QLabel::setPixmap(QPixmap::fromImage(src.scaled(size(), Qt::KeepAspectRatio))); + } +} + +/*** PROTECTED SLOTS ***/ +void br::ImageViewer::keyPressEvent(QKeyEvent *event) +{ + QLabel::keyPressEvent(event); + + if ((event->key() == Qt::Key_S) && (event->modifiers() == Qt::ControlModifier) && !src.isNull()) { + event->accept(); + const QString fileName = QFileDialog::getSaveFileName(this, "Save Image"); + if (!fileName.isEmpty()) src.save(fileName); + } +} + +void br::ImageViewer::mouseMoveEvent(QMouseEvent *event) +{ + QLabel::mouseMoveEvent(event); + event->accept(); + setFocus(); +} + +void br::ImageViewer::resizeEvent(QResizeEvent *event) +{ + QLabel::resizeEvent(event); + event->accept(); + updatePixmap(); +} diff --git a/app/openbr-gui/imageviewer.h b/app/openbr-gui/imageviewer.h new file mode 100644 index 0000000..89756a1 --- /dev/null +++ b/app/openbr-gui/imageviewer.h @@ -0,0 +1,61 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef __IMAGEVIEWER_H +#define __IMAGEVIEWER_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace br +{ + +class BR_EXPORT_GUI ImageViewer : public QLabel +{ + Q_OBJECT + QString defaultText; + QImage src; + +public: + explicit ImageViewer(QWidget *parent = 0); + void setDefaultText(const QString &text, bool async = false); + void setImage(const QString &file, bool async = false); + void setImage(const QImage &image, bool async = false); + void setImage(const QPixmap &pixmap, bool async = false); + bool isNull() const { return src.isNull(); } + int imageWidth() const { return src.width(); } + int imageHeight() const { return src.height(); } + +protected slots: + void keyPressEvent(QKeyEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void resizeEvent(QResizeEvent *event); + +private slots: + void updatePixmap(bool async = false); +}; + +} // namespace br + +#endif // __IMAGEVIEWER_H diff --git a/app/openbr-gui/initialize.cpp b/app/openbr-gui/initialize.cpp new file mode 100644 index 0000000..d906bd5 --- /dev/null +++ b/app/openbr-gui/initialize.cpp @@ -0,0 +1,32 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include +#include + +#include "initialize.h" + +void br_initialize_gui() +{ + Q_INIT_RESOURCE(icons); + qRegisterMetaType("br::File"); + qRegisterMetaType("br::FileList"); + qRegisterMetaType("br::Template"); + qRegisterMetaType("br::TemplateList"); + br_initialize_qt(); + br_set_property("log", qPrintable(QString("%1/log.txt").arg(br_scratch_path()))); +} diff --git a/app/openbr-gui/initialize.h b/app/openbr-gui/initialize.h new file mode 100644 index 0000000..4ea19bc --- /dev/null +++ b/app/openbr-gui/initialize.h @@ -0,0 +1,24 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef __INITIALIZE_H +#define __INITIALIZE_H + +#include + +BR_EXPORT_GUI void br_initialize_gui(); + +#endif // __INITIALIZE_H diff --git a/app/openbr-gui/transformeditor.cpp b/app/openbr-gui/transformeditor.cpp new file mode 100644 index 0000000..c07ad94 --- /dev/null +++ b/app/openbr-gui/transformeditor.cpp @@ -0,0 +1,48 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +#include "transformeditor.h" +#include "transformlisteditor.h" + +using namespace br; + +br::TransformEditor::TransformEditor(Transform *transform, QWidget *parent) + : QWidget(parent) +{ + name.addItems(QString(br_objects("Transform", ".*", false)).split('\n')); + layout.addWidget(&name); + setLayout(&layout); + + name.setCurrentIndex(name.findText(transform->name().remove("Transform"))); + const QMetaObject *mo = transform->metaObject(); + for (int i=mo->propertyOffset(); ipropertyCount(); i++) { + QMetaProperty mp = mo->property(i); + const QString typeName = mp.typeName(); + if (typeName == "QList") { + QString separator; + if (transform->name() == "ChainTransform") separator = "!"; + else if (transform->name() == "PipeTransform") separator = "+"; + else if (transform->name() == "ForkTransform") separator = "/"; + else separator = "?"; + parameters.append(new TransformListEditor(mp.read(transform).value< QList >(), separator, this)); + } + } + + foreach (QWidget *parameter, parameters) + layout.addWidget(parameter); +} diff --git a/app/openbr-gui/transformeditor.h b/app/openbr-gui/transformeditor.h new file mode 100644 index 0000000..0834b05 --- /dev/null +++ b/app/openbr-gui/transformeditor.h @@ -0,0 +1,41 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef __TRANSFORMEDITOR_H +#define __TRANSFORMEDITOR_H + +#include +#include +#include +#include + +namespace br +{ + +class BR_EXPORT_GUI TransformEditor : public QWidget +{ + Q_OBJECT + QHBoxLayout layout; + QComboBox name; + QList parameters; + +public: + explicit TransformEditor(br::Transform *transform, QWidget *parent = 0); +}; + +} // namespace br + +#endif // __TRANSFORMEDITOR_H diff --git a/app/openbr-gui/transformlisteditor.cpp b/app/openbr-gui/transformlisteditor.cpp new file mode 100644 index 0000000..327c598 --- /dev/null +++ b/app/openbr-gui/transformlisteditor.cpp @@ -0,0 +1,57 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include + +#include "transformeditor.h" +#include "transformlisteditor.h" + +using namespace br; + +br::TransformListEditor::TransformListEditor(const QList &transforms, const QString &separator, QWidget *parent) + : QWidget(parent) +{ + this->separator = separator; + + foreach (br::Transform *transform, transforms) { + parameters.append(new TransformEditor(transform, this)); + if (!separator.isEmpty() && (transform != transforms.last())) + parameters.append(new QLabel(separator, this)); + } + + foreach (QWidget *parameter, parameters) + layout.addWidget(parameter); + + addTransform.addMenu("Add..."); + //addTransform.setTitle("Add..."); + //addTransform.addAction("test"); + //addTransform.setIcon(QIcon("://glyphicons_190_circle_plus@2x.png")); + layout.addWidget(&addTransform); + setLayout(&layout); + + //connect(&addTransform, SIGNAL(clicked()), this, SLOT(addTransformClicked())); +} + +void br::TransformListEditor::addTransformClicked() +{ + if (!separator.isEmpty()) { + parameters.append(new QLabel(separator, this)); + layout.insertWidget(layout.count()-1, parameters.last()); + } + parameters.append(new TransformEditor(Transform::make("Identity", this), this)); + layout.insertWidget(layout.count()-1, parameters.last()); +} diff --git a/app/openbr-gui/transformlisteditor.h b/app/openbr-gui/transformlisteditor.h new file mode 100644 index 0000000..b9d9c91 --- /dev/null +++ b/app/openbr-gui/transformlisteditor.h @@ -0,0 +1,46 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef __TRANSFORMLISTEDITOR_H +#define __TRANSFORMLISTEDITOR_H + +#include +#include +#include +#include +#include + +namespace br +{ + +class BR_EXPORT_GUI TransformListEditor : public QWidget +{ + Q_OBJECT + QHBoxLayout layout; + QList parameters; + QString separator; + QMenuBar addTransform; + +public: + explicit TransformListEditor(const QList &transforms, const QString &separator = "", QWidget *parent = 0); + +private slots: + void addTransformClicked(); +}; + +} // namespace br + +#endif // __TRANSFORMLISTEDITOR_H diff --git a/sdk/CMakeLists.txt b/sdk/CMakeLists.txt new file mode 100644 index 0000000..3d0751f --- /dev/null +++ b/sdk/CMakeLists.txt @@ -0,0 +1,28 @@ +include_directories(.) + +# Create version.h +configure_file(${BR_SHARE_DIR}/version.h.in version.h) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +# Collect source files +aux_source_directory(. SRC) +aux_source_directory(core BR_CORE) +include(plugins/plugins.cmake) + +add_library(openbr SHARED ${SRC} ${BR_CORE} ${BR_PLUGIN} ${BR_THIRDPARTY_SRC} ${BR_WINDOWS_RESOURCES}) +set_target_properties(openbr PROPERTIES + DEFINE_SYMBOL BR_LIBRARY + VERSION ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH} + SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR} + LINK_INTERFACE_LIBRARIES "" + AUTOMOC TRUE) +target_link_libraries(openbr ${BR_THIRDPARTY_LIBS}) +add_cppcheck(openbr) + +# Install +install(TARGETS openbr + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib) +file(GLOB HEADERS *.h) +install(FILES ${HEADERS} DESTINATION include) diff --git a/sdk/core/bee.cpp b/sdk/core/bee.cpp new file mode 100644 index 0000000..bf26fbc --- /dev/null +++ b/sdk/core/bee.cpp @@ -0,0 +1,361 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include +#include +#include +#include +#ifndef BR_EMBEDDED +#include +#endif // BR_EMBEDDED +#include +#include +#include + +#include "bee.h" +#include "opencvutils.h" +#include "qtutils.h" + +using namespace cv; +using namespace br; + +/**** BEE ****/ +FileList BEE::readSigset(QString sigset, bool ignoreMetadata) +{ + FileList fileList; + +#ifndef BR_EMBEDDED + QDomDocument doc(sigset); + QFile file(sigset); + bool success; + success = file.open(QIODevice::ReadOnly); if (!success) qFatal("BEE::readSigset unable to open %s for reading.", qPrintable(sigset)); + success = doc.setContent(&file); if (!success) qFatal("BEE::readSigset unable to parse %s.", qPrintable(sigset)); + file.close(); + + QDomElement docElem = doc.documentElement(); + QDomNode subject = docElem.firstChild(); + while (!subject.isNull()) { + // Looping through subjects + QDomNode fileNode = subject.firstChild(); + QDomElement d = subject.toElement(); + QString name = d.attribute("name"); + while (!fileNode.isNull()) { + // Looping through files + File file; + + QDomElement e = fileNode.toElement(); + QDomNamedNodeMap attributes = e.attributes(); + for (unsigned int i=0; i Signature; // QPair + QList signatures; + + foreach (const File &file, fileList) + signatures.append(Signature(file.subject(), file.name)); + + QFile file(sigset); + file.open(QFile::WriteOnly); + file.write("\n" + "\n"); + foreach (const Signature &signature, signatures) + file.write(qPrintable(QString("\t\n" + "\t\t\n" + "\t\n").arg(signature.first, signature.second))); + file.write("\n"); + file.close(); +} + +template +Mat readMatrix(const br::File &matrix) +{ + // Special case matrix construction + if (matrix == "Matrix") { + const int size = matrix.getInt("Size"); + const int step = matrix.getInt("Step", 1); + if (size % step != 0) qFatal("bee.cpp readMatrix step does not divide size evenly."); + + if (sizeof(T) == sizeof(BEE::Mask_t)) { + const bool selfSimilar = matrix.getBool("SelfSimilar"); + + Mat m(size, size, CV_8UC1); + m.setTo(BEE::NonMatch); + for (int i=0; i(i+j,i+k) = ((selfSimilar && (j == k)) ? BEE::DontCare : BEE::Match); + return m; + } else if (sizeof(T) == sizeof(BEE::Simmat_t)) { + Mat m(size, size, CV_32FC1); + m.setTo(BEE::NonMatch); + for (int i=0; i(i+j,i+k) = 1; + return m; + } + } + + QFile file(matrix); + bool success = file.open(QFile::ReadOnly); + if (!success) qFatal("bee.cpp readMatrix unable to open %s for reading.", qPrintable((QString)matrix)); + + // Check format + QByteArray format = file.readLine(); + bool isDistance = (format[0] == 'D'); + if (format[1] != '2') qFatal("bee.cpp readMatrix invalid matrix header."); + + // Skip sigset lines + file.readLine(); + file.readLine(); + + // Get matrix size + QStringList words = QString(file.readLine()).split(" "); + int rows = words[1].toInt(); + int cols = words[2].toInt(); + + // Get matrix data + qint64 bytesExpected = (qint64)rows*(qint64)cols*(qint64)sizeof(T); + Mat m(rows, cols, OpenCVType::make()); + if (file.read((char*)m.data, bytesExpected) != bytesExpected) + qFatal("bee.cpp readMatrix invalid matrix size."); + file.close(); + + Mat result; + if (isDistance ^ matrix.getBool("Negate")) m.convertTo(result, -1, -1); + else result = m.clone(); + return result; +} + +Mat BEE::readSimmat(const br::File &simmat) +{ + return readMatrix(simmat); +} + +Mat BEE::readMask(const br::File &mask) +{ + return readMatrix(mask); +} + +template +void writeMatrix(const Mat &m, const QString &matrix, const QString &targetSigset, const QString &querySigset) +{ + if (m.type() != OpenCVType::make()) qFatal("bee.cpp writeMatrix invalid matrix type."); + + int elemSize = sizeof(T); + QString matrixType; + if (elemSize == 1) matrixType = "B"; + else if (elemSize == 4) matrixType = "F"; + else qFatal("bee.cpp writeMatrix invalid element size.\n"); + + char buff[4]; + QFile file(matrix); + bool success = file.open(QFile::WriteOnly); if (!success) qFatal("bee.cpp writeMatrix unable to open %s for writing.", qPrintable(matrix)); + file.write("S2\n"); + file.write(qPrintable(QFileInfo(targetSigset).fileName())); + file.write("\n"); + file.write(qPrintable(QFileInfo(querySigset).fileName())); + file.write("\n"); + file.write("M"); + file.write(qPrintable(matrixType)); + file.write(" "); + file.write(qPrintable(QString::number(m.rows))); + file.write(" "); + file.write(qPrintable(QString::number(m.cols))); + file.write(" "); + int endian = 0x12345678; + memcpy(&buff, &endian, 4); + file.write(buff, 4); + file.write("\n"); + file.write((const char*)m.data, m.rows*m.cols*elemSize); + file.close(); +} + +void BEE::writeSimmat(const Mat &m, const QString &simmat, const QString &targetSigset, const QString &querySigset) +{ + writeMatrix(m, simmat, targetSigset, querySigset); +} + +void BEE::writeMask(const Mat &m, const QString &mask, const QString &targetSigset, const QString &querySigset) +{ + writeMatrix(m, mask, targetSigset, querySigset); +} + +template +void matrixToCSV(const QString &matrix, const QString &csv) +{ + qDebug("Converting %s to %s", qPrintable(matrix), qPrintable(csv)); + + QFile out(csv); + out.open(QFile::WriteOnly); + + Mat m = readMatrix(matrix); + for (int i=0; i(i,j)))); + out.write(","); + } + out.write("\n"); + } +} + +void BEE::simmatToCSV(const QString &simmat, const QString &csv) +{ + matrixToCSV(simmat, csv); +} + +void BEE::maskToCSV(const QString &mask, const QString &csv) +{ + matrixToCSV(mask, csv); +} + +template +void CSVToMatrix(const QString &csv, const QString &matrix) +{ + qDebug("Converting %s to %s", qPrintable(csv), qPrintable(matrix)); + + QStringList lines = QtUtils::readLines(csv); + Mat m(lines.size(), lines.first().split(",", QString::SkipEmptyParts).size(), OpenCVType::make()); + + for (int i=0; i(i, j) = words[j].toFloat(&ok); + if (!ok) qFatal("bee.cpp::CSVToMatrix failed to convert %s to floating point format.", qPrintable(words[j])); + } + } + + writeMatrix(m, matrix, "Unknown_Target", "Unknown_Query"); +} + +void BEE::CSVToSimmat(const QString &csv, const QString &simmat) +{ + CSVToMatrix(csv, simmat); +} + +void BEE::CSVToMask(const QString &csv, const QString &mask) +{ + CSVToMatrix(csv, mask); +} + +void BEE::makeMask(const QString &targetInput, const QString &queryInput, const QString &mask) +{ + qDebug("Making mask from %s and %s to %s", qPrintable(targetInput), qPrintable(queryInput), qPrintable(mask)); + + TemplateList target(TemplateList::fromInput(targetInput)); + TemplateList query(TemplateList::fromInput(queryInput)); + FileList targetFiles = target.files(); + FileList queryFiles = query.files(); + QList targetLabels = targetFiles.labels(); + QList queryLabels = queryFiles.labels(); + + Mat vals(queryFiles.size(), targetFiles.size(), CV_8UC1); + for (int i=0; i(i,j) = val; + } + } + writeMask(vals, mask, targetInput, queryInput); +} + +void BEE::combineMasks(const QStringList &inputMasks, const QString &outputMask, const QString &method) +{ + qDebug("Combining %d masks to %s with method %s", inputMasks.size(), qPrintable(outputMask), qPrintable(method)); + + bool AND = true; + if (method == "And") AND = true; + else if (method == "Or") AND = false; + else qFatal("combineMasks invalid method"); + + QList masks; + foreach (const QString &inputMask, inputMasks) + masks.append(readMask(inputMask)); + if (masks.size() < 2) qFatal("BEE::mergeMasks expects at least two masks."); + + const int rows = masks.first().rows; + const int columns = masks.first().cols; + + Mat combinedMask(rows, columns, CV_8UC1); + for (int i=0; i(i,j)) { + case Match: + genuineCount++; + break; + case NonMatch: + imposterCount++; + break; + case DontCare: + dontcareCount++; + break; + } + } + if ((genuineCount != 0) && (imposterCount != 0)) qFatal("BEE::combinedMasks comparison is both a genuine and an imposter."); + + Mask_t val; + if (genuineCount > 0) val = Match; + else if (imposterCount > 0) val = NonMatch; + else val = DontCare; + if (AND && (dontcareCount > 0)) val = DontCare; + combinedMask.at(i,j) = val; + } + } + + writeMask(combinedMask, outputMask, "Combined_Targets", "Combined_Queries"); +} diff --git a/sdk/core/bee.h b/sdk/core/bee.h new file mode 100644 index 0000000..b16c9c9 --- /dev/null +++ b/sdk/core/bee.h @@ -0,0 +1,58 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef __BEE_H +#define __BEE_H + +#include +#include +#include +#include +#include +#include +#include + +/*** Functions for parsing BEE style data structures. ***/ +namespace BEE +{ + const uchar Match(0xff); + const uchar NonMatch(0x7f); + const uchar DontCare(0x00); + typedef float Simmat_t; + typedef uchar Mask_t; + + // Sigset IO + br::FileList readSigset(QString sigset, bool ignoreMetadata = false); + void writeSigset(const QString &sigset, const br::FileList &metadataList); + + // Matrix IO + cv::Mat readSimmat(const br::File &simmat); + cv::Mat readMask(const br::File &mask); + void writeSimmat(const cv::Mat &m, const QString &simmat, const QString &targetSigset = "Unknown_Target", const QString &querySigset = "Unknown_Query"); + void writeMask(const cv::Mat &m, const QString &mask, const QString &targetSigset = "Unknown_Target", const QString &querySigset = "Unknown_Query"); + + // CSV IO + void simmatToCSV(const QString &simmat, const QString &csv); + void maskToCSV(const QString &mask, const QString &csv); + void CSVToSimmat(const QString &csv, const QString &simmat); + void CSVToMask(const QString &csv, const QString &mask); + + // Write BEE files + void makeMask(const QString &targetInput, const QString &queryInput, const QString &mask); + void combineMasks(const QStringList &inputMasks, const QString &outputMask, const QString &method); +} + +#endif // __BEE_H diff --git a/sdk/core/classify.cpp b/sdk/core/classify.cpp new file mode 100644 index 0000000..4bdc620 --- /dev/null +++ b/sdk/core/classify.cpp @@ -0,0 +1,117 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include +#include + +#include "classify.h" +#include "core/qtutils.h" + +// Helper struct for statistics accumulation +struct Counter +{ + int truePositive, falsePositive, falseNegative; + Counter() + { + truePositive = 0; + falsePositive = 0; + falseNegative = 0; + } +}; + +void br::EvalClassification(const QString &predictedInput, const QString &truthInput) +{ + qDebug("Evaluating classification of %s against %s", qPrintable(predictedInput), qPrintable(truthInput)); + + TemplateList predicted(TemplateList::fromInput(predictedInput)); + TemplateList truth(TemplateList::fromInput(truthInput)); + if (predicted.size() != truth.size()) qFatal("br::EvalClassification input size mismatch."); + + QHash counters; + for (int i=0; i output(Output::make("", FileList() << "Label" << "Count" << "Precision" << "Recall" << "F-score", FileList(counters.size()))); + + int tpc = 0; + int fnc = 0; + for (int i=0; isetRelative(trueLabel, i, 0); + output->setRelative(count, i, 1); + output->setRelative(precision, i, 2); + output->setRelative(recall, i, 3); + output->setRelative(fscore, i, 4); + } + + qDebug("Overall Accuracy = %f", (float)tpc / (float)(tpc + fnc)); +} + +void br::EvalRegression(const QString &predictedInput, const QString &truthInput) +{ + qDebug("Evaluating regression of %s against %s", qPrintable(predictedInput), qPrintable(truthInput)); + + const TemplateList predicted(TemplateList::fromInput(predictedInput)); + const TemplateList truth(TemplateList::fromInput(truthInput)); + if (predicted.size() != truth.size()) qFatal("br::EvalRegression input size mismatch."); + + float rmsError = 0; + QStringList truthValues, predictedValues; + for (int i=0; i +#include + +namespace br +{ + void EvalClassification(const QString &predictedInput, const QString &truthInput); + void EvalRegression(const QString &predictedInput, const QString &truthInput); +} + +#endif // __CLASSIFY_H + diff --git a/sdk/core/cluster.cpp b/sdk/core/cluster.cpp new file mode 100644 index 0000000..6ea7f50 --- /dev/null +++ b/sdk/core/cluster.cpp @@ -0,0 +1,358 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include +#include +#include +#include +#include +#include + +#include "core/bee.h" +#include "core/cluster.h" + +typedef QPair Neighbor; // QPair +typedef QList Neighbors; +typedef QVector Neighborhood; + +// Compare function used to order neighbors from highest to lowest similarity +static bool compareNeighbors(const Neighbor &a, const Neighbor &b) +{ + if (a.second == b.second) + return a.first < b.first; + return a.second > b.second; +} + +// Zhu et al. "A Rank-Order Distance based Clustering Algorithm for Face Tagging", CVPR 2011 +// Ob(x) in eq. 1, modified to consider 0/1 as ground truth imposter/genuine. +static int indexOf(const Neighbors &neighbors, int i) +{ + for (int j=0; j::max(); + if ((neighborhood[b][indexA].second == 1) || (neighborhood[a][indexB].second == 1)) return 0; + if ((neighborhood[b][indexA].second == 0) || (neighborhood[a][indexB].second == 0)) return std::numeric_limits::max(); + + int distanceA = asymmetricalROD(neighborhood, a, b); + int distanceB = asymmetricalROD(neighborhood, b, a); + return 1.f * (distanceA + distanceB) / std::min(indexA+1, indexB+1); +} + +Neighborhood getNeighborhood(const QStringList &simmats) +{ + Neighborhood neighborhood; + + float globalMax = -std::numeric_limits::max(); + float globalMin = std::numeric_limits::max(); + int numGalleries = (int)sqrt((float)simmats.size()); + if (numGalleries*numGalleries != simmats.size()) + qFatal("cluser.cpp readGalleries incorrect number of similarity matrices."); + + // Process each simmat + for (int i=0; i allNeighbors; + + int currentRows = -1; + int columnOffset = 0; + for (int j=0; j(k,l); + if ((i==j) && (k==l)) continue; // Skips self-similarity scores + + if ((val != -std::numeric_limits::infinity()) && + (val != std::numeric_limits::infinity())) { + globalMax = std::max(globalMax, val); + globalMin = std::min(globalMin, val); + } + neighbors.append(Neighbor(l+columnOffset, val)); + } + } + + columnOffset += m.cols; + } + + // Keep the top matches + for (int j=0; j::infinity()) + neighbor.second = 0; + else if (neighbor.second == std::numeric_limits::infinity()) + neighbor.second = 1; + else + neighbor.second = (neighbor.second - globalMin) / (globalMax - globalMin); + } + } + + return neighborhood; +} + +// Zhu et al. "A Rank-Order Distance based Clustering Algorithm for Face Tagging", CVPR 2011 +br::Clusters br::ClusterGallery(const QStringList &simmats, float aggressiveness, const QString &csv) +{ + qDebug("Clustering %d simmat(s)", simmats.size()); + + // Read in gallery parts, keeping top neighbors of each template + Neighborhood neighborhood = getNeighborhood(simmats); + const int cutoff = neighborhood.first().size(); + const float threshold = 3*cutoff/4 * aggressiveness/5; + + // Initialize clusters + Clusters clusters(neighborhood.size()); + for (int i=0; i nextClusterIDs(neighborhood.size()); + for (int i=0; i clusterIDLUT; + QList allClusterIDs = QSet::fromList(nextClusterIDs.toList()).values(); + for (int i=0; i= clusters.size()); + clusters = newClusters; + neighborhood = newNeighborhood; + } + + // Save clusters + if (!csv.isEmpty()) + WriteClusters(clusters, csv); + return clusters; +} + +// Santo Fortunato "Community detection in graphs", Physics Reports 486 (2010) +// wI or wII metric (page 148) +float wallaceMetric(const br::Clusters &clusters, const QVector &indices) +{ + int matches = 0; + int total = 0; + foreach (const QList &cluster, clusters) { + for (int i=0; i &indicesA, const QVector &indicesB) +{ + int a[2][2] = {{0,0},{0,0}}; + for (int i=0; i labels = TemplateList::fromInput(input).files().labels(); + + QHash labelToIndex; + int nClusters = 0; + for (int i=0; i()); + + QVector truthIndices(labels.size()); + for (int i=0; i testIndices(labels.size()); + for (int i=0; i +#include +#include +#include + +namespace br +{ + typedef QList Cluster; // List of indices into galleries + typedef QVector Clusters; + + Clusters ClusterGallery(const QStringList &simmats, float aggressiveness, const QString &csv); + void EvalClustering(const QString &csv, const QString &input); + + Clusters ReadClusters(const QString &csv); + void WriteClusters(const Clusters &clusters, const QString &csv); +} + +#endif // __CLUSTER_H diff --git a/sdk/core/common.cpp b/sdk/core/common.cpp new file mode 100644 index 0000000..1f9091f --- /dev/null +++ b/sdk/core/common.cpp @@ -0,0 +1,65 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "common.h" + +using namespace std; + +/**** GLOBAL ****/ +void Common::seedRNG() { + static bool seeded = false; + if (!seeded) { + srand(0); // We seed with 0 instead of time(NULL) to have reproducible randomness + seeded = true; + } +} + +QList Common::RandSample(int n, int max, int min, bool unique) +{ + seedRNG(); + + QList samples; samples.reserve(n); + int range = max-min; + if (range <= 0) qFatal("Common::RandSample non-positive range."); + if (unique && (n >= range)) { + for (int i=min; i Common::RandSample(int n, const QSet &values, bool unique) +{ + seedRNG(); + + QList valueList = values.toList(); + if (unique && (values.size() <= n)) return valueList; + + QList samples; samples.reserve(n); + while (samples.size() < n) { + const int randIndex = rand() % valueList.size(); + samples.append(valueList[randIndex]); + if (unique) valueList.removeAt(randIndex); + } + return samples; +} diff --git a/sdk/core/common.h b/sdk/core/common.h new file mode 100644 index 0000000..9e8bb0c --- /dev/null +++ b/sdk/core/common.h @@ -0,0 +1,317 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef __COMMON_H +#define __COMMON_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Common +{ + +/**** +Round + Round floating point to nearest integer. +****/ +template +int round(T r) +{ + return (r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5); +} + + +/**** +Sort + Returns a list of pairs sorted by value where: + pair.first = original value + pair.second = original index +****/ +template +QList< QPair > Sort(const QList &vals, bool decending = false) +{ + const int size = vals.size(); + QList< QPair > pairs; pairs.reserve(size); + for (int i=0; i(vals[i], i)); + + qSort(pairs); + if (decending) for (int k=0; k +void MinMax(const QList &vals, T *min, T *max, int *min_index, int *max_index) +{ + const int size = vals.size(); + assert(size > 0); + + *min = *max = vals[0]; + *min_index = *max_index = 0; + for (int i=1; i *max) { + *max = val; + *max_index = i; + } + } +} + +template +void MinMax(const QList &vals, T *min, T *max) +{ + int min_index, max_index; + MinMax(vals, min, max, &min_index, &max_index); +} + +template +T Min(const QList &vals) +{ + int min, max; + MinMax(vals, &min, &max); + return min; +} + +template +T Max(const QList &vals) +{ + int min, max; + MinMax(vals, &min, &max); + return max; +} + + +/**** +MeanStdDev + Returns the mean and standard deviation of a vector of values. +****/ +template +void MeanStdDev(const QList &vals, double *mean, double *stddev) +{ + const int size = vals.size(); + + // Compute Mean + double sum = 0; + for (int i=0; i class C, typename T> +T Median(C vals, T *q1 = 0, T *q3 = 0) +{ + if (vals.isEmpty()) return std::numeric_limits::quiet_NaN(); + qSort(vals); + if (q1 != 0) *q1 = vals[1*vals.size()/4]; + if (q3 != 0) *q3 = vals[3*vals.size()/4]; + return vals[vals.size()/2]; +} + + +/**** +Mode + Computes the mode of a list. +****/ +template +T Mode(const QList &vals) +{ + QMap counts; + foreach (const T &val, vals) { + if (!counts.contains(val)) + counts[val] = 0; + counts[val]++; + } + return counts.key(Max(counts.values())); +} + + +/**** +CumSum + Returns the cumulative sum of a vector of values. +****/ +template +QList CumSum(const QList &vals) +{ + QList cumsum; + cumsum.reserve(vals.size()+1); + cumsum.append(0); + foreach (const T &val, vals) + cumsum.append(cumsum.last()+val); + return cumsum; +} + + +/**** +RandSample + Returns a vector of n integers sampled in the range RandSample(int n, int max, int min = 0, bool unique = false); +QList RandSample(int n, const QSet &values, bool unique = false); + +/* Weighted random sample, each entry in weights should be >= 0. */ +template +QList RandSample(int n, const QList &weights, bool unique = false) +{ + static bool seeded = false; + if (!seeded) { + srand(time(NULL)); + seeded = true; + } + + QList cdf = CumSum(weights); + for (int i=0; i samples; samples.reserve(n); + while (samples.size() < n) { + T r = (T)rand() / (T)RAND_MAX; + for (int j=0; j= cdf[j]) && (r <= cdf[j+1])) { + if (!unique || !samples.contains(j)) + samples.append(j); + break; + } + } + } + + return samples; +} + + +/**** +Unique + See Matlab function unique() for documentation. +****/ +template +void Unique(const QList &vals, QList &b, QList &m, QList &n) +{ + const int size = vals.size(); + assert(size > 0); + + b.reserve(size); + m.reserve(size); + n.reserve(size); + + // Compute b and m + QList< QPair > sortedPairs = Sort(vals); + b.append(sortedPairs[0].first); + m.append(sortedPairs[0].second); + for (size_t i=1; i +void SplitPairs(const QList< QPair > &pairs, QList &first, QList &second) +{ + first.reserve(pairs.size()); + second.reserve(pairs.size()); + typedef QPair pair_t; + foreach (const pair_t &pair, pairs) { + first.append(pair.first); + second.append(pair.second); + } +} + + +/**** +RemoveOutliers + Removes values outside of 1.5 * Inner Quartile Range. +****/ +template +QList RemoveOutliers(QList vals) +{ + T q1, q3; + Median(vals, &q1, &q3); + T iqr = q3-q1; + T min = q1 - 1.5*iqr; + T max = q3 + 1.5*iqr; + QList newVals; + for (int i=0; i= min) && (vals[i] <= max)) + newVals.append(vals[i]); + return newVals; +} + + +/**** +Downsample + Sorts and evenly downsamples a vector to size k. +****/ +template +QList Downsample(QList vals, long k) +{ + // Use 'long' instead of 'int' so multiplication doesn't overflow + qSort(vals); + long size = (long)vals.size(); + if (size <= k) return vals; + + QList newVals; newVals.reserve(k); + for (long i=0; i + +#include "core/common.h" +#include "core/qtutils.h" + +using namespace br; + +/**** ALGORITHM_CORE ****/ +struct AlgorithmCore +{ + QSharedPointer transform; + QSharedPointer distance; + + AlgorithmCore(const QString &name) + { + this->name = name; + init(name); + } + + bool isClassifier() const + { + return distance.isNull(); + } + + void train(const QString &inputs, const QString &model) + { + TemplateList data(TemplateList::fromInput(inputs)); + + if (transform.isNull()) qFatal("AlgorithmCore::train null transform."); + qDebug("%d training files", data.size()); + + QTime time; time.start(); + qDebug("Training Enrollment"); + transform->train(data); + + if (!distance.isNull()) { + qDebug("Projecting Enrollment"); + data >> *transform; + + qDebug("Training Comparison"); + distance->train(data); + } + + if (!model.isEmpty()) { + qDebug("Storing %s", qPrintable(QFileInfo(model).fileName())); + store(model); + } + + qDebug("Training Time (sec): %d", time.elapsed()/1000); + } + + void store(const QString &model) const + { + // Create stream + QByteArray data; + QDataStream out(&data, QFile::WriteOnly); + + // Serialize algorithm to stream + out << name; + transform->store(out); + const bool hasComparer = !distance.isNull(); + out << hasComparer; + if (hasComparer) distance->store(out); + out << Globals->classes; + + // Compress and save to file + QtUtils::writeFile(model, data); + } + + void load(const QString &model) + { + // Load from file and decompress + QByteArray data; + QtUtils::readFile(model, data); + + // Create stream + QDataStream in(&data, QFile::ReadOnly); + + // Load algorithm + in >> name; init(Globals->abbreviations.contains(name) ? Globals->abbreviations[name] : name); + transform->load(in); + bool hasDistance; in >> hasDistance; + if (hasDistance) distance->load(in); + in >> Globals->classes; + } + + File getMemoryGallery(const File &file) const + { + return name + file.baseName() + file.hash() + ".mem"; + } + + FileList enroll(File input, File gallery = File()) + { + if (gallery.isNull()) gallery = getMemoryGallery(input); + + QScopedPointer g(Gallery::make(gallery)); + FileList fileList = g->files(); + if (!fileList.isEmpty() && g->isUniversal()) return fileList; // Already enrolled + + const TemplateList i(TemplateList::fromInput(input)); + if (i.isEmpty()) return FileList(); // Nothing to enroll + + if (transform.isNull()) qFatal("AlgorithmCore::enroll null transform."); + const int blocks = Globals->blocks(i.size()); + Globals->currentStep = 0; + Globals->totalSteps = i.size(); + Globals->startTime.start(); + + const int subBlockSize = 4*std::max(1, Globals->parallelism); + const int numSubBlocks = ceil(1.0*Globals->blockSize/subBlockSize); + int totalCount = 0, failureCount = 0; + double totalBytes = 0; + for (int block=0; blockblockSize + subBlock*subBlockSize, subBlockSize); + if (data.isEmpty()) break; + const int numFiles = data.size(); + + data >> *transform; + g->writeBlock(data); + const FileList newFiles = data.files(); + fileList.append(newFiles); + + totalCount += newFiles.size(); + failureCount += newFiles.failures(); + totalBytes += data.bytes(); + Globals->currentStep += numFiles; + Globals->printStatus(); + } + } + + const float speed = 1000 * Globals->totalSteps / Globals->startTime.elapsed() / std::max(1, abs(Globals->parallelism)); + if (!Globals->quiet && (Globals->totalSteps > 1)) + fprintf(stderr, "\rSPEED=%.1e SIZE=%.4g FAILURES=%d/%d \n", + speed, totalBytes/totalCount, failureCount, totalCount); + Globals->totalSteps = 0; + + return fileList; + } + + void retrieveOrEnroll(const File &file, QScopedPointer &gallery, FileList &galleryFiles) + { + gallery.reset(Gallery::make(file)); + if (!gallery->isUniversal()) { + enroll(file); + gallery.reset(Gallery::make(getMemoryGallery(file))); + galleryFiles = gallery->files(); + } else { + galleryFiles = gallery->files(); + } + } + + void compare(File targetGallery, File queryGallery, File output) + { + if (output.exists() && output.getBool("cache")) return; + if (queryGallery == ".") queryGallery = targetGallery; + + QScopedPointer t, q; + FileList targetFiles, queryFiles; + retrieveOrEnroll(targetGallery, t, targetFiles); + retrieveOrEnroll(queryGallery, q, queryFiles); + + QScopedPointer o(Output::make(output, targetFiles, queryFiles)); + + if (distance.isNull()) qFatal("AlgorithmCore::compare null distance."); + Globals->currentStep = 0; + Globals->totalSteps = double(targetFiles.size()) * double(queryFiles.size()); + Globals->startTime.start(); + + int queryBlock = -1; + bool queryDone = false; + while (!queryDone) { + queryBlock++; + TemplateList queries = q->readBlock(&queryDone); + + int targetBlock = -1; + bool targetDone = false; + while (!targetDone) { + targetBlock++; + TemplateList targets = t->readBlock(&targetDone); + + o->setBlock(queryBlock, targetBlock); + distance->compare(targets, queries, o.data()); + + Globals->currentStep += double(targets.size()) * double(queries.size()); + Globals->printStatus(); + } + } + + const float speed = 1000 * Globals->totalSteps / Globals->startTime.elapsed() / std::max(1, abs(Globals->parallelism)); + if (!Globals->quiet && (Globals->totalSteps > 1)) fprintf(stderr, "\rSPEED=%.1e \n", speed); + Globals->totalSteps = 0; + } + +private: + QString name; + + QString getFileName(const QString &description) const + { + foreach (const QString &folder, QDir(Globals->sdkPath + "/share").entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name)) { + const QString file = Globals->sdkPath + "/share/" + folder + "/models/algorithms/" + description; + if (QFileInfo(file).exists()) return file; + } + return ""; + } + + void init(QString description) + { + // Check if a trained binary already exists for this algorithm + const QString file = getFileName(description); + if (!file.isEmpty()) description = file; + + if (QFileInfo(description).exists()) { + qDebug("Loading %s", qPrintable(QFileInfo(description).fileName())); + load(description); + return; + } + + // Expand abbreviated algorithms to their full strings + if (Globals->abbreviations.contains(description)) + return init(Globals->abbreviations[description]); + + QStringList words = description.split(':'); + if (words.size() > 2) qFatal("AlgorithmCore::init invalid algorithm format."); + + transform = QSharedPointer(Transform::make(words[0], NULL)); + if (words.size() > 1) distance = QSharedPointer(Factory::make("." + words[1])); + } +}; + + +class AlgorithmManager : public Initializer +{ + Q_OBJECT + +public: + static QHash > algorithms; + static QMutex algorithmsLock; + + void initialize() const {} + + void finalize() const + { + algorithms.clear(); + } + + static QSharedPointer getAlgorithm(const QString &algorithm) + { + if (algorithm.isEmpty()) qFatal("AlgorithmManager::getAlgorithm no default algorithm set."); + + if (!algorithms.contains(algorithm)) { + algorithmsLock.lock(); + if (!algorithms.contains(algorithm)) + algorithms.insert(algorithm, QSharedPointer(new AlgorithmCore(algorithm))); + algorithmsLock.unlock(); + } + + return algorithms[algorithm]; + } +}; + +QHash > AlgorithmManager::algorithms; +QMutex AlgorithmManager::algorithmsLock; + +BR_REGISTER(Initializer, AlgorithmManager) + +bool br::IsClassifier(const QString &algorithm) +{ + qDebug("Checking if %s is a classifier", qPrintable(algorithm)); + return AlgorithmManager::getAlgorithm(algorithm)->isClassifier(); +} + +void br::Train(const QString &inputs, const File &model) +{ + qDebug("Training on %s to %s", qPrintable(inputs), qPrintable(model.flat())); + AlgorithmManager::getAlgorithm(model.getString("algorithm"))->train(inputs, model); +} + +FileList br::Enroll(const File &input, const File &gallery) +{ + qDebug("Enrolling %s%s", qPrintable(input.flat()), + gallery.isNull() ? "" : qPrintable(" to " + gallery.flat())); + return AlgorithmManager::getAlgorithm(gallery.getString("algorithm"))->enroll(input, gallery); +} + +void br::Compare(const File &targetGallery, const File &queryGallery, const File &output) +{ + qDebug("Comparing %s and %s%s", qPrintable(targetGallery.flat()), + qPrintable(queryGallery.flat()), + output.isNull() ? "" : qPrintable(" to " + output.flat())); + AlgorithmManager::getAlgorithm(output.getString("algorithm"))->compare(targetGallery, queryGallery, output); +} + +QSharedPointer br::Transform::fromAlgorithm(const QString &algorithm) +{ + return AlgorithmManager::getAlgorithm(algorithm)->transform; +} + +QSharedPointer br::Distance::fromAlgorithm(const QString &algorithm) +{ + return AlgorithmManager::getAlgorithm(algorithm)->distance; +} + +#include "core.moc" diff --git a/sdk/core/fuse.cpp b/sdk/core/fuse.cpp new file mode 100644 index 0000000..ceeb39f --- /dev/null +++ b/sdk/core/fuse.cpp @@ -0,0 +1,134 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include +#include +#include +#include +#include +#include + +#include "core/bee.h" +#include "core/common.h" +#include "core/fuse.h" + +using namespace cv; + +static void normalizeMatrix(Mat &matrix, const Mat &mask, const QString &method) +{ + if (method == "None") return; + + QList vals; vals.reserve(matrix.rows*matrix.cols); + for (int i=0; i(i,j); + if ((mask.at(i,j) == BEE::DontCare) || + (val == -std::numeric_limits::infinity()) || + (val == std::numeric_limits::infinity())) + continue; + vals.append(val); + } + } + + float min, max; + double mean, stddev; + Common::MinMax(vals, &min, &max); + Common::MeanStdDev(vals, &mean, &stddev); + + if (method == "MinMax") { + for (int i=0; i(i,j) == BEE::DontCare) continue; + float &val = matrix.at(i,j); + if (val == -std::numeric_limits::infinity()) val = 0; + else if (val == std::numeric_limits::infinity()) val = 1; + else val = (val - min) / (max - min); + } + } + } else if (method == "ZScore") { + if (stddev == 0) qFatal("fuse.cpp normalizeMatrix stddev is 0."); + for (int i=0; i(i,j) == BEE::DontCare) continue; + float &val = matrix.at(i,j); + if (val == -std::numeric_limits::infinity()) val = (min - mean) / stddev; + else if (val == std::numeric_limits::infinity()) val = (max - mean) / stddev; + else val = (val - mean) / stddev; + } + } + } else { + qFatal("fuse.cpp normalizeMatrix invalid normalization method %s.", qPrintable(method)); + } +} + +void br::Fuse(const QStringList &inputSimmats, const QString &mask, const QString &normalization, const QString &fusion, const QString &outputSimmat) +{ + qDebug("Fusing %d to %s", inputSimmats.size(), qPrintable(outputSimmat)); + QList matrices; + foreach (const QString &simmat, inputSimmats) + matrices.append(BEE::readSimmat(simmat)); + if ((matrices.size() < 2) && (fusion != "None")) qFatal("br::Fuse expected at least two similarity matrices."); + if ((matrices.size() > 1) && (fusion == "None")) qFatal("mm:Fuse expected exactly one similarity matrix."); + Mat matrix_mask = BEE::readMask(mask); + + for (int i=0; i weights; + QStringList words = fusion.right(fusion.size()-3).split(":", QString::SkipEmptyParts); + if (words.size() == 0) { + for (int k=0; k +#include + +namespace br +{ + void Fuse(const QStringList &inputSimmats, const QString &mask, const QString &normalization, const QString &fusion, const QString &outputSimmat); +} + +#endif // __FUSE_H diff --git a/sdk/core/opencvutils.cpp b/sdk/core/opencvutils.cpp new file mode 100644 index 0000000..cdb0d26 --- /dev/null +++ b/sdk/core/opencvutils.cpp @@ -0,0 +1,339 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include +#include + +#include "opencvutils.h" +#include "qtutils.h" + +using namespace cv; + +void OpenCVUtils::saveImage(const Mat &src, const QString &file) +{ + if (file.isEmpty()) return; + + if (!src.data) { + qWarning("OpenCVUtils::saveImage null image."); + return; + } + + QtUtils::touchDir(QFileInfo(file).dir()); + + Mat draw; + cvtUChar(src, draw); + bool success = imwrite(file.toStdString(), draw); if (!success) qFatal("OpenCVUtils::saveImage failed to save %s", qPrintable(file)); +} + +void OpenCVUtils::showImage(const Mat &src, const QString &window, bool waitKey) +{ + if (!src.data) { + qWarning("OpenCVUtils::showImage null image."); + return; + } + + Mat draw; + cvtUChar(src, draw); + imshow(window.toStdString(), draw); + cv::waitKey(waitKey ? -1 : 1); +} + +void OpenCVUtils::cvtGray(const Mat &src, Mat &dst) +{ + if (src.channels() == 3) cvtColor(src, dst, CV_BGR2GRAY); + else if (src.channels() == 1) dst = src; + else qFatal("Invalid channel count"); +} + +void OpenCVUtils::cvtUChar(const Mat &src, Mat &dst) +{ + if (src.depth() == CV_8U) { + dst = src; + return; + } + + double globalMin = std::numeric_limits::max(); + double globalMax = -std::numeric_limits::max(); + + vector mv; + split(src, mv); + for (size_t i=0; i= globalMin); + + double range = globalMax - globalMin; + if (range != 0) { + double scale = 255 / range; + convertScaleAbs(src, dst, scale, -(globalMin * scale)); + } else { + // Monochromatic + dst = Mat(src.size(), CV_8UC1, Scalar((globalMin+globalMax)/2)); + } +} + +Mat OpenCVUtils::toMat(const QList &src, int rows) +{ + if (rows == -1) rows = src.size(); + int columns = src.isEmpty() ? 0 : src.size() / rows; + if (rows*columns != src.size()) qFatal("OpenCVUtils::toMat invalid matrix size."); + Mat dst(rows, columns, CV_32FC1); + for (int i=0; i(i/columns,i%columns) = src[i]; + return dst; +} + +Mat OpenCVUtils::toMat(const QList &src) +{ + if (src.isEmpty()) return Mat(); + + int rows = src.size(); + size_t total = src.first().total(); + int type = src.first().type(); + Mat dst(rows, total, type); + + for (int i=0; i &src) +{ + if (src.isEmpty()) return Mat(); + + int rows = 0; foreach (const Mat &m, src) rows += m.rows; + int cols = src.first().cols; + int type = src.first().type(); + Mat dst(rows, cols, type); + + int row = 0; + foreach (const Mat &m, src) { + if ((m.cols != cols) || (m.type() != type) || (!m.isContinuous())) + qFatal("OpenCVUtils::toMatByRow invalid matrix."); + memcpy(dst.ptr(row), m.ptr(), m.rows*m.cols*m.elemSize()); + row += m.rows; + } + return dst; +} + +QString OpenCVUtils::elemToString(const Mat &m, int r, int c) +{ + assert(m.channels() == 1); + switch (m.depth()) { + case CV_8U: return QString::number(m.at(r,c)); + case CV_8S: return QString::number(m.at(r,c)); + case CV_16U: return QString::number(m.at(r,c)); + case CV_16S: return QString::number(m.at(r,c)); + case CV_32S: return QString::number(m.at(r,c)); + case CV_32F: return QString::number(m.at(r,c)); + case CV_64F: return QString::number(m.at(r,c)); + default: qFatal("OpenCVUtils::elemToString unknown matrix depth"); + } + return "?"; +} + +float OpenCVUtils::elemToFloat(const Mat &m, int r, int c) +{ + assert(m.channels() == 1); + switch (m.depth()) { + case CV_8U: return float(m.at(r,c)); + case CV_8S: return float(m.at(r,c)); + case CV_16U: return float(m.at(r,c)); + case CV_16S: return float(m.at(r,c)); + case CV_32S: return float(m.at(r,c)); + case CV_32F: return float(m.at(r,c)); + case CV_64F: return float(m.at(r,c)); + default: qFatal("OpenCVUtils::elemToFloat unknown matrix depth"); + } + return 0; +} + +QStringList OpenCVUtils::matrixToStringList(const Mat &m) +{ + QStringList results; + vector mv; + split(m, mv); + foreach (const Mat &mc, mv) + for (int i=0; i OpenCVUtils::matrixToVector(const Mat &m) +{ + QList results; + vector mv; + split(m, mv); + foreach (const Mat &mc, mv) + for (int i=0; i OpenCVUtils::toPoints(const QList &qPoints) +{ + QList cvPoints; cvPoints.reserve(qPoints.size()); + foreach (const QPointF &qPoint, qPoints) + cvPoints.append(toPoint(qPoint)); + return cvPoints; +} + +QList OpenCVUtils::fromPoints(const QList &cvPoints) +{ + QList qPoints; qPoints.reserve(cvPoints.size()); + foreach (const Point2f &cvPoint, cvPoints) + qPoints.append(fromPoint(cvPoint)); + return qPoints; +} + +Rect OpenCVUtils::toRect(const QRectF &qRect) +{ + return Rect(qRect.x(), qRect.y(), qRect.width(), qRect.height()); +} + +QRectF OpenCVUtils::fromRect(const Rect &cvRect) +{ + return QRectF(cvRect.x, cvRect.y, cvRect.width, cvRect.height); +} + +QList OpenCVUtils::toRects(const QList &qRects) +{ + QList cvRects; cvRects.reserve(qRects.size()); + foreach (const QRectF &qRect, qRects) + cvRects.append(toRect(qRect)); + return cvRects; +} + +QList OpenCVUtils::fromRects(const QList &cvRects) +{ + QList qRects; qRects.reserve(cvRects.size()); + foreach (const Rect &cvRect, cvRects) + qRects.append(fromRect(cvRect)); + return qRects; +} + +QDataStream &operator<<(QDataStream &stream, const Mat &m) +{ + // Write header + int rows = m.rows; + int cols = m.cols; + int type = m.type(); + stream << rows << cols << type; + + // Write data + int len = rows*cols*m.elemSize(); + stream << len; + if (len > 0) { + if (!m.isContinuous()) qFatal("opencvutils.cpp operator<< Mat can't serialize non-continuous matrices."); + int written = stream.writeRawData((const char*)m.data, len); + + if (written != len) qFatal("opencvutils.cpp operator<< Mat serialization failure."); + } + return stream; +} + +QDataStream &operator>>(QDataStream &stream, Mat &m) +{ + // Read header + int rows, cols, type; + stream >> rows >> cols >> type; + m.create(rows, cols, type); + + // Read data + int len; + stream >> len; + if (len > 0) { + if (!m.isContinuous()) qFatal("opencvutils.cpp operator>> Mat can't deserialize non-continuous matrices."); + int written = stream.readRawData((char*)m.data, len); + if (written != len) qFatal("opencvutils.cpp operator>> Mat serialization failure."); + } + return stream; +} + +QDebug operator<<(QDebug dbg, const Mat &m) +{ + vector mv; + split(m, mv); + if (m.rows > 1) dbg.nospace() << "{ "; + for (int r=0; r 1) && (r > 0)) dbg.nospace() << " "; + if (m.cols > 1) dbg.nospace() << "["; + for (int c=0; c 1) dbg.nospace() << "("; + for (unsigned int i=0; i 1) dbg.nospace() << ")"; + if (c < m.cols - 1) dbg.nospace() << ", "; + } + if (m.cols > 1) dbg.nospace() << "]"; + if (r < m.rows-1) dbg.nospace() << "\n"; + } + if (m.rows > 1) dbg.nospace() << " }"; + return dbg; +} + +QDebug operator<<(QDebug dbg, const Point &p) +{ + dbg.nospace() << "(" << p.x << ", " << p.y << ")"; + return dbg.space(); +} + +QDebug operator<<(QDebug dbg, const Rect &r) +{ + dbg.nospace() << "(" << r.x << ", " << r.y << "," << r.width << "," << r.height << ")"; + return dbg.space(); +} + +QDataStream &operator<<(QDataStream &stream, const Rect &r) +{ + return stream << r.x << r.y << r.width << r.height; +} + +QDataStream &operator>>(QDataStream &stream, Rect &r) +{ + return stream >> r.x >> r.y >> r.width >> r.height; +} + +QDataStream &operator<<(QDataStream &stream, const Size &s) +{ + return stream << s.width << s.height; +} + +QDataStream &operator>>(QDataStream &stream, Size &s) +{ + return stream >> s.width >> s.height; +} diff --git a/sdk/core/opencvutils.h b/sdk/core/opencvutils.h new file mode 100644 index 0000000..af7a97e --- /dev/null +++ b/sdk/core/opencvutils.h @@ -0,0 +1,125 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef __OPENCVUTILS_H +#define __OPENCVUTILS_H + +#include +#include +#include +#include +#include + +namespace OpenCVUtils +{ + // Test/write/display image + void saveImage(const cv::Mat &src, const QString &file); + void showImage(const cv::Mat &src, const QString &window = "OpenBR", bool waitKey = true); + + // Convert image + void cvtGray(const cv::Mat &src, cv::Mat &dst); + void cvtUChar(const cv::Mat &src, cv::Mat &dst); + + // To image + cv::Mat toMat(const QList &src, int rows = -1); + cv::Mat toMat(const QList &src); // Data organized one matrix per row + cv::Mat toMatByRow(const QList &src); // Data organized one row per row + + // From image + QString elemToString(const cv::Mat &m, int r, int c); + float elemToFloat(const cv::Mat &m, int r, int c); + QStringList matrixToStringList(const cv::Mat &m); + QList matrixToVector(const cv::Mat &m); + + // Conversions + cv::Point2f toPoint(const QPointF &qPoint); + QPointF fromPoint(const cv::Point2f &cvPoint); + QList toPoints(const QList &qPoints); + QList fromPoints(const QList &cvPoints); + cv::Rect toRect(const QRectF &qRect); + QRectF fromRect(const cv::Rect &cvRect); + QList toRects(const QList &qRects); + QList fromRects(const QList &cvRects); +} + +QDebug operator<<(QDebug dbg, const cv::Mat &m); +QDebug operator<<(QDebug dbg, const cv::Point &p); +QDebug operator<<(QDebug dbg, const cv::Rect &r); +QDataStream &operator<<(QDataStream &stream, const cv::Mat &m); +QDataStream &operator>>(QDataStream &stream, cv::Mat &m); +QDataStream &operator<<(QDataStream &stream, const cv::Rect &r); +QDataStream &operator>>(QDataStream &stream, cv::Rect &r); +QDataStream &operator<<(QDataStream &stream, const cv::Size &s); +QDataStream &operator>>(QDataStream &stream, cv::Size &s); + + +// As described in "Modern C++ Design" Section 2.5. +template +struct Type2Type +{ + typedef T OriginalType; +}; + +/**** +OpenCVType + Templated OpenCV Mat::type creation. +****/ +template +class OpenCVType +{ + static int getDepth(Type2Type) + { + return CV_8U; + } + + static int getDepth(Type2Type) + { + return CV_8S; + } + + static int getDepth(Type2Type) + { + return CV_16U; + } + + static int getDepth(Type2Type) + { + return CV_16S; + } + + static int getDepth(Type2Type) + { + return CV_32S; + } + + static int getDepth(Type2Type) + { + return CV_32F; + } + + static int getDepth(Type2Type) + { + return CV_64F; + } + +public: + static int make() + { + return CV_MAKETYPE((getDepth(Type2Type())),(Channels)); + } +}; + +#endif // __OPENCVUTILS_H diff --git a/sdk/core/plot.cpp b/sdk/core/plot.cpp new file mode 100644 index 0000000..8ee8fd2 --- /dev/null +++ b/sdk/core/plot.cpp @@ -0,0 +1,504 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "plot.h" +#include "version.h" +#include "core/bee.h" +#include "core/common.h" +#include "core/qtutils.h" + +#undef FAR // Windows preprecessor definition + +using namespace cv; +using namespace br; + +void br::Confusion(const QString &file, float score, int &true_positives, int &false_positives, int &true_negatives, int &false_negatives) +{ + qDebug("Computing confusion matrix of %s at %f", qPrintable(file), score); + + QStringList lines = QtUtils::readLines(file); + true_positives = false_positives = true_negatives = false_negatives = 0; + foreach (const QString &line, lines) { + if (!line.startsWith("SD")) continue; + QStringList words = line.split(","); + bool ok; + float similarity = words[1].toFloat(&ok); assert(ok); + if (words[2] == "Genuine") { + if (similarity >= score) true_positives++; + else false_negatives++; + } else { + if (similarity >= score) false_positives++; + else true_negatives++; + } + } +} + +static QStringList getPivots(const QString &file, bool headers) +{ + QString str; + if (headers) str = QFileInfo(file).dir().dirName(); + else str = QFileInfo(file).completeBaseName(); + return str.split("_"); +} + +struct Comparison +{ + float score; + int target, query; + bool genuine; + + Comparison() {} + Comparison(float _score, int _target, int _query, bool _genuine) + : score(_score), target(_target), query(_query), genuine(_genuine) {} + inline bool operator<(const Comparison &other) const { return score > other.score; } +}; + +struct OperatingPoint +{ + float score, FAR, TAR; + OperatingPoint() {} + OperatingPoint(float _score, float _FAR, float _TAR) + : score(_score), FAR(_FAR), TAR(_TAR) {} +}; + +static float getTAR(const QList &operatingPoints, float FAR) +{ + int index = 0; + while (operatingPoints[index].FAR < FAR) { + index++; + if (index == operatingPoints.size()) + return 1; + } + const float x1 = (index == 0 ? 0 : operatingPoints[index-1].FAR); + const float y1 = (index == 0 ? 0 : operatingPoints[index-1].TAR); + const float x2 = operatingPoints[index].FAR; + const float y2 = operatingPoints[index].TAR; + const float m = (y2 - y1) / (x2 - x1); + const float b = y1 - m*x1; + return m * FAR + b; +} + +static float kernelDensityBandwidth(const QList &vals) +{ + double mean, stddev; + Common::MeanStdDev(vals, &mean, &stddev); + return pow(4 * pow(stddev, 5.0) / (3 * vals.size()), 0.2); +} + +static float kernelDensityEstimation(const QList &vals, double x, double h) +{ + double y = 0; + foreach (double val, vals) + y += exp(-pow((val-x)/h,2)/2)/sqrt(2*CV_PI); + return y / (vals.size() * h); +} + +float br::Evaluate(const QString &simmat, const QString &mask, const QString &csv) +{ + qDebug("Evaluating %s with %s", qPrintable(simmat), qPrintable(mask)); + + const int Max_Points = 500; + float result = -1; + + // Read files + const Mat scores = BEE::readSimmat(simmat); + File maskFile(mask); maskFile.insert("Size", scores.rows); + const Mat masks = BEE::readMask(maskFile); + if (scores.size() != masks.size()) qFatal("Simmat/Mask size mismatch."); + + // Make comparisons + QList comparisons; comparisons.reserve(scores.rows*scores.cols); + int genuineCount = 0, impostorCount = 0; + for (int i=0; i(i,j); + const BEE::Simmat_t simmat_val = scores.at(i,j); + if (mask_val == BEE::DontCare) continue; + comparisons.append(Comparison(simmat_val, j, i, mask_val == BEE::Match)); + if (comparisons.last().genuine) genuineCount++; + else impostorCount++; + } + } + + if (genuineCount == 0) qFatal("No genuine scores."); + if (impostorCount == 0) qFatal("No impostor scores."); + + qSort(comparisons); + + double genuineSum = 0, impostorSum = 0; + QList operatingPoints; + QList genuines, impostors; + QVector firstGenuineReturns(scores.rows, 0); + + int falsePositives = 0, previousFalsePositives = 0; + int truePositives = 0, previousTruePositives = 0; + int index = 0; + while (index < comparisons.size()) { + float thresh = comparisons[index].score; + while ((index < comparisons.size()) && + (comparisons[index].score == thresh)) { + const Comparison &comparison = comparisons[index]; + if (comparison.genuine) { + truePositives++; + genuineSum += comparison.score; + genuines.append(comparison.score); + if (firstGenuineReturns[comparison.query] < 1) + firstGenuineReturns[comparison.query] = abs(firstGenuineReturns[comparison.query]) + 1; + } else { + falsePositives++; + impostorSum += comparison.score; + impostors.append(comparison.score); + if (firstGenuineReturns[comparison.query] < 1) + firstGenuineReturns[comparison.query]--; + } + index++; + } + + if ((falsePositives > previousFalsePositives) && + (truePositives > previousTruePositives)) { + // Restrict the extreme ends of the curve + if ((falsePositives >= 10) && (falsePositives < impostorCount/2)) + operatingPoints.append(OperatingPoint(thresh, float(falsePositives)/impostorCount, float(truePositives)/genuineCount)); + previousFalsePositives = falsePositives; + previousTruePositives = truePositives; + } + } + + if (operatingPoints.size() <= 2) qFatal("Insufficent genuines or impostors."); + operatingPoints.takeLast(); // Remove point (1,1) + + // Write Metadata table + QStringList lines; + lines.append("Plot,X,Y"); + lines.append("Metadata,"+QString::number(scores.cols)+",Gallery"); + lines.append("Metadata,"+QString::number(scores.rows)+",Probe"); + lines.append("Metadata,"+QString::number(genuineCount)+",Genuine"); + lines.append("Metadata,"+QString::number(impostorCount)+",Impostor"); + lines.append("Metadata,"+QString::number(scores.cols*scores.rows-(genuineCount+impostorCount))+",Ignored"); + + // Write DET, PRE, REC + int points = qMin(operatingPoints.size(), Max_Points); + for (int i=0; i sampledGenuineScores; sampledGenuineScores.reserve(points); + QList sampledImpostorScores; sampledImpostorScores.reserve(points); + for (int i=0; i 0) possibleReturns++; + if (firstGenuineReturn <= i) realizedReturns++; + } + lines.append(qPrintable(QString("CMC,%1,%2").arg(QString::number(i), QString::number(float(realizedReturns)/possibleReturns)))); + } + + if (!csv.isEmpty()) QtUtils::writeFile(csv, lines); + qDebug("TAR @ FAR = 0.01: %.3f", result); + return result; +} + +static QString getScale(const QString &mode, const QString &title, int vals) +{ + if (vals > 12) return " + scale_"+mode+"_discrete(\""+title+"\")"; + else if (vals > 11) return " + scale_"+mode+"_brewer(\""+title+"\", palette=\"Set3\")"; + else if (vals > 9) return " + scale_"+mode+"_brewer(\""+title+"\", palette=\"Paired\")"; + else return " + scale_"+mode+"_brewer(\""+title+"\", palette=\"Set1\")"; +} + +// Custom sorting method to ensure datasets are ordered nicely +static bool sortFiles(const QString &fileA, const QString &fileB) +{ + QFileInfo fileInfoA(fileA); + QFileInfo fileInfoB(fileB); + + if (fileInfoA.fileName().contains("Good")) return true; + if (fileInfoB.fileName().contains("Good")) return false; + if (fileInfoA.fileName().contains("Bad")) return true; + if (fileInfoB.fileName().contains("Bad")) return false; + if (fileInfoA.fileName().contains("Ugly")) return true; + if (fileInfoB.fileName().contains("Ugly")) return false; + if (fileInfoA.fileName().contains("MEDS")) return true; + if (fileInfoB.fileName().contains("MEDS")) return false; + if (fileInfoA.fileName().contains("PCSO")) return true; + if (fileInfoB.fileName().contains("PCSO")) return false; + + return fileA < fileB; +} + +struct RPlot +{ + QString basename, suffix; + QFile file; + QStringList pivotHeaders; + QVector< QSet > pivotItems; + int majorIndex, minorIndex, majorSize, minorSize; + QString majorHeader, minorHeader; + bool flip; + + RPlot(QStringList files, const QString &destination, bool isEvalFormat = true) + { + if (files.isEmpty()) qFatal("RPlot::RPlot() empty file list."); + qSort(files.begin(), files.end(), sortFiles); + + // Parse destination + QFileInfo fileInfo(destination); + basename = fileInfo.path() + "/" + fileInfo.completeBaseName(); + suffix = fileInfo.suffix(); + if (suffix.isEmpty()) suffix = "pdf"; + + file.setFileName(basename+".R"); + bool success = file.open(QFile::WriteOnly); + if (!success) qFatal("RPlot::RPlot() failed to open %s for writing.", qPrintable(file.fileName())); + + file.write("# Load libraries\n" + "library(ggplot2)\n" + "library(gplots)\n" + "library(reshape)\n" + "library(scales)\n" + "\n" + "# Read CSVs\n" + "data <- NULL\n"); + + // Read files and retrieve pivots + pivotHeaders = getPivots(files.first(), true); + pivotItems = QVector< QSet >(pivotHeaders.size()); + foreach (const QString &fileName, files) { + QStringList pivots = getPivots(fileName, false); + if (pivots.size() != pivotHeaders.size()) qFatal("plot.cpp::initializeR pivot size mismatch."); + file.write(qPrintable(QString("tmp <- read.csv(\"%1\")\n").arg(fileName).replace("\\", "\\\\"))); + for (int i=0; i majorSize) { + minorIndex = majorIndex; + minorSize = majorSize; + majorIndex = i; + majorSize = size; + } else if (size > minorSize) { + minorIndex = i; + minorSize = size; + } + } + + if (majorIndex != -1) majorHeader = pivotHeaders[majorIndex]; + if (minorIndex != -1) minorHeader = pivotHeaders[minorIndex]; + flip = minorHeader == "Algorithm"; + } + + bool finalize(bool show = false) + { + file.write("dev.off()\n"); + if (suffix != "pdf") file.write(qPrintable(QString("unlink(\"%1.%2\")").arg(basename, suffix))); + file.close(); + + bool success = QtUtils::runRScript(file.fileName()); + if (success && show) QtUtils::showFile(basename+"."+suffix); + return success; + } + + QString subfile(const QString &name) const + { + return basename+"_"+name+"."+suffix; + } +}; + +bool br::Plot(const QStringList &files, const QString &destination, bool show) +{ + qDebug("Plotting %d file(s) to %s", files.size(), qPrintable(destination)); + + RPlot p(files, destination); + + p.file.write(qPrintable(QString("qplot(X, Y, data=DET, geom=\"line\"") + + (p.majorSize > 1 ? QString(", colour=factor(%1)").arg(p.majorHeader) : QString()) + + (p.minorSize > 1 ? QString(", linetype=factor(%1)").arg(p.minorHeader) : QString()) + + QString(", xlab=\"False Accept Rate\", ylab=\"False Reject Rate\") + geom_abline(alpha=0.5, colour=\"grey\", linetype=\"dashed\") + theme_bw()") + + (p.majorSize > 1 ? getScale("colour", p.majorHeader, p.majorSize) : QString()) + + (p.minorSize > 1 ? QString(" + scale_linetype_discrete(\"%1\")").arg(p.minorHeader) : QString()) + + QString(" + scale_x_continuous(trans=\"log10\") + scale_y_continuous(trans=\"log10\")") + + QString("\nggsave(\"%1\")\n").arg(p.subfile("DET")))); + + p.file.write(qPrintable(QString("qplot(X, 1-Y, data=DET, geom=\"line\"") + + (p.majorSize > 1 ? QString(", colour=factor(%1)").arg(p.majorHeader) : QString()) + + (p.minorSize > 1 ? QString(", linetype=factor(%1)").arg(p.minorHeader) : QString()) + + QString(", xlab=\"False Accept Rate\", ylab=\"True Accept Rate\") + theme_bw()") + + (p.majorSize > 1 ? getScale("colour", p.majorHeader, p.majorSize) : QString()) + + (p.minorSize > 1 ? QString(" + scale_linetype_discrete(\"%1\")").arg(p.minorHeader) : QString()) + + QString(" + scale_x_continuous(trans=\"log10\") + scale_y_continuous(labels=percent)") + + QString("\nggsave(\"%1\")\n").arg(p.subfile("ROC")))); + + p.file.write(qPrintable(QString("qplot(X, data=SD, geom=\"histogram\", fill=Y, position=\"identity\", alpha=I(1/2)") + + QString(", xlab=\"Score%1\"").arg((p.flip ? p.majorSize : p.minorSize) > 1 ? " / " + (p.flip ? p.majorHeader : p.minorHeader) : QString()) + + QString(", ylab=\"Frequency%1\"").arg((p.flip ? p.minorSize : p.majorSize) > 1 ? " / " + (p.flip ? p.minorHeader : p.majorHeader) : QString()) + + QString(") + scale_fill_manual(\"Ground Truth\", values=c(\"blue\", \"red\")) + theme_bw() + scale_x_continuous(minor_breaks=NULL) + scale_y_continuous(minor_breaks=NULL) + opts(axis.text.y=theme_blank(), axis.ticks=theme_blank(), axis.text.x=theme_text(angle=-90, hjust=0))") + + (p.majorSize > 1 ? (p.minorSize > 1 ? QString(" + facet_grid(%2 ~ %1, scales=\"free\")").arg((p.flip ? p.majorHeader : p.minorHeader), (p.flip ? p.minorHeader : p.majorHeader)) : QString(" + facet_wrap(~ %1, scales = \"free\")").arg(p.majorHeader)) : QString()) + + QString(" + opts(aspect.ratio=1)") + + QString("\nggsave(\"%1\")\n").arg(p.subfile("SD")))); + + p.file.write(qPrintable(QString("qplot(X, Y, data=CMC, geom=\"line\", xlab=\"Rank\", ylab=\"Retrieval Rate\"") + + (p.majorSize > 1 ? QString(", colour=factor(%1)").arg(p.majorHeader) : QString()) + + (p.minorSize > 1 ? QString(", linetype=factor(%1)").arg(p.minorHeader) : QString()) + + QString(") + theme_bw() + scale_x_continuous(limits = c(1,25), breaks = c(1,5,10,25))") + + (p.majorSize > 1 ? getScale("colour", p.majorHeader, p.majorSize) : QString()) + + (p.minorSize > 1 ? QString(" + scale_linetype_discrete(\"%1\")").arg(p.minorHeader) : QString()) + + QString(" + scale_y_continuous(labels=percent)") + + QString("\nggsave(\"%1\")\n").arg(p.subfile("CMC")))); + + p.file.write(qPrintable(QString("qplot(factor(%1), data=BC, geom=\"bar\", position=\"dodge\", weight=Y").arg(p.majorHeader) + + (p.majorSize > 1 ? QString(", fill=factor(%1)").arg(p.majorHeader) : QString()) + + QString(", xlab=\"%1False Accept Rate\"").arg(p.majorSize > 1 ? p.majorHeader + " / " : QString()) + + QString(", ylab=\"True Accept Rate%1\") + theme_bw()").arg(p.minorSize > 1 ? " / " + p.minorHeader : QString()) + + (p.majorSize > 1 ? getScale("fill", p.majorHeader, p.majorSize) : QString()) + + (p.minorSize > 1 ? QString(" + facet_grid(%2 ~ X)").arg(p.minorHeader) : QString(" + facet_wrap(~ X)")) + + QString(" + opts(legend.position=\"none\", axis.text.x=theme_text(angle=-90, hjust=0)) + geom_text(data=BC, aes(label=Y, y=0.05))") + + QString("\nggsave(\"%1\")\n").arg(p.subfile("BC")))); + + p.file.write(qPrintable(QString("qplot(X, Y, data=FAR, geom=\"line\"") + + ((p.flip ? p.majorSize : p.minorSize) > 1 ? QString(", colour=factor(%1)").arg(p.flip ? p.majorHeader : p.minorHeader) : QString()) + + QString(", xlab=\"Score%1\", ylab=\"False Accept Rate\") + theme_bw()").arg((p.flip ? p.minorSize : p.majorSize) > 1 ? " / " + (p.flip ? p.minorHeader : p.majorHeader) : QString()) + + ((p.flip ? p.majorSize : p.minorSize) > 1 ? getScale("colour", p.flip ? p.majorHeader : p.minorHeader, p.flip ? p.majorSize : p.minorSize) : QString()) + + QString(" + scale_y_continuous(trans=\"log10\")") + + ((p.flip ? p.minorSize : p.majorSize) > 1 ? QString(" + facet_wrap(~ %1, scales=\"free_x\")").arg(p.flip ? p.minorHeader : p.majorHeader) : QString()) + + QString(" + opts(aspect.ratio=1)") + + QString("\nggsave(\"%1\")\n").arg(p.subfile("FAR")))); + + p.file.write(qPrintable(QString("qplot(X, Y, data=FRR, geom=\"line\"") + + ((p.flip ? p.majorSize : p.minorSize) > 1 ? QString(", colour=factor(%1)").arg(p.flip ? p.majorHeader : p.minorHeader) : QString()) + + QString(", xlab=\"Score%1\", ylab=\"False Reject Rate\") + theme_bw()").arg((p.flip ? p.minorSize : p.majorSize) > 1 ? " / " + (p.flip ? p.minorHeader : p.majorHeader) : QString()) + + ((p.flip ? p.majorSize : p.minorSize) > 1 ? getScale("colour", p.flip ? p.majorHeader : p.minorHeader, p.flip ? p.majorSize : p.minorSize) : QString()) + + QString(" + scale_y_continuous(trans=\"log10\")") + + ((p.flip ? p.minorSize : p.majorSize) > 1 ? QString(" + facet_wrap(~ %1, scales=\"free_x\")").arg(p.flip ? p.minorHeader : p.majorHeader) : QString()) + + QString(" + opts(aspect.ratio=1)") + + QString("\nggsave(\"%1\")\n").arg(p.subfile("FRR")))); + + return p.finalize(show); +} + +bool br::PlotMetadata(const QStringList &files, const QString &columns, bool show) +{ + qDebug("Plotting %d metadata file(s) for columns %s", files.size(), qPrintable(columns)); + + RPlot p(files, "PlotMetadata", false); + foreach (const QString &column, columns.split(";")) + p.file.write(qPrintable(QString("qplot(%1, %2, data=data, geom=\"violin\", fill=%1) + coord_flip() + theme_bw()\nggsave(\"%2.pdf\")\n").arg(p.majorHeader, column))); + return p.finalize(show); +} diff --git a/sdk/core/plot.h b/sdk/core/plot.h new file mode 100644 index 0000000..c51c92a --- /dev/null +++ b/sdk/core/plot.h @@ -0,0 +1,34 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef __PLOT_H +#define __PLOT_H + +#include +#include +#include + +namespace br +{ + +void Confusion(const QString &file, float score, int &true_positives, int &false_positives, int &true_negatives, int &false_negatives); +float Evaluate(const QString &simmat, const QString &mask, const QString &csv = ""); // Returns TAR @ FAR = 0.01 +bool Plot(const QStringList &files, const QString &destination, bool show = false); +bool PlotMetadata(const QStringList &files, const QString &destination, bool show = false); + +} + +#endif // __PLOT_H diff --git a/sdk/core/qtutils.cpp b/sdk/core/qtutils.cpp new file mode 100644 index 0000000..61a8d08 --- /dev/null +++ b/sdk/core/qtutils.cpp @@ -0,0 +1,293 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include +#ifndef BR_EMBEDDED +#include +#endif // BR_EMBEDDED +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qtutils.h" + +using namespace br; + +QStringList QtUtils::getFiles(QDir dir, bool recursive) +{ + dir = QDir(dir.canonicalPath()); + + QStringList files; + foreach (const QString &file, dir.entryList(QDir::Files)) + files.append(QDir::cleanPath(dir.absoluteFilePath(file))); + + if (!recursive) return files; + + foreach (const QString &folder, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { + QDir subdir(dir); + bool success = subdir.cd(folder); if (!success) qFatal("QtUtils::getFiles cd failure."); + files.append(getFiles(subdir, true)); + } + return files; +} + +QStringList QtUtils::getFiles(const QString ®exp) +{ + QFileInfo fileInfo(regexp); + QDir dir(fileInfo.dir()); + QRegExp re(fileInfo.fileName()); + re.setPatternSyntax(QRegExp::Wildcard); + + QStringList files; + foreach (const QString &fileName, dir.entryList(QDir::Files)) + if (re.exactMatch(fileName)) + files.append(dir.filePath(fileName)); + return files; +} + +QStringList QtUtils::readLines(const QString &file) +{ + QStringList lines; + readFile(file, lines); + return lines; +} + +void QtUtils::readFile(const QString &file, QStringList &lines) +{ + QFile f(file); + if (!f.open(QFile::ReadOnly)) qFatal("QtUtils::readFile unable to open %s for reading.", qPrintable(file)); + lines = QString(f.readAll()).split('\n', QString::SkipEmptyParts); + for (int i=0; i QtUtils::toFloats(const QStringList &strings) +{ + QList floats; + bool ok; + foreach (const QString &string, strings) { + floats.append(string.toFloat(&ok)); + if (!ok) qFatal("QtUtils::toFloats failed to convert %s to floating point format.", qPrintable(string)); + } + return floats; +} + +QStringList QtUtils::toStringList(const std::vector &string_list) +{ + QStringList result; + foreach (const std::string &string, string_list) + result.append(QString::fromStdString(string)); + return result; +} + +QStringList QtUtils::toStringList(int num_strings, const char *strings[]) +{ + QStringList result; + for (int i=0; i&]")); + return QString(QCryptographicHash::hash(qPrintable(string), QCryptographicHash::Md5).toBase64()).remove(QRegExp("[^a-zA-Z1-9]")).left(6); +} + +QStringList QtUtils::parse(QString args, char split) +{ + if (args.isEmpty()) return QStringList(); + + QStringList words; + int start = 0; + bool inQuote = false; + QStack subexpressions; + for (int i=0; i') { + if (subexpressions.pop() != '<') qFatal("QtUtils::parse unexpected '>'."); + } else if (args[i] == '}') { + if (subexpressions.pop() != '{') qFatal("QtUtils::parse unexpected '}'."); + } else if (subexpressions.isEmpty() && (args[i] == split)) { + words.append(args.mid(start, i-start).trimmed()); + start = i+1; + } + } + } + + words.append(args.mid(start).trimmed()); + return words; +} + +void QtUtils::checkArgsSize(const QString &name, const QStringList &args, int min, int max) +{ + if (max == -1) max = std::numeric_limits::max(); + if (max == 0) max = min; + if (args.size() < min) qFatal("%s expects at least %d arguments, got %d", qPrintable(name), min, args.size()); + if (args.size() > max) qFatal("%s expects no more than %d arguments, got %d", qPrintable(name), max, args.size()); +} + +bool QtUtils::runRScript(const QString &file) +{ + QString RScriptExecutable = Globals->sdkPath + "/R/bin/Rscript.exe"; + if (!QFileInfo(RScriptExecutable).exists()) RScriptExecutable = "RScript"; // Let the system resolve it + + QStringList args; args << file; + QProcess RScript; + RScript.start(RScriptExecutable, args); + RScript.waitForFinished(-1); + return ((RScript.exitCode() == 0) && (RScript.error() == QProcess::UnknownError)); +} + +bool QtUtils::runDot(const QString &file) +{ + QProcess dot; + dot.start("dot -Tpdf -O " + file); + dot.waitForFinished(-1); + return ((dot.exitCode() == 0) && (dot.error() == QProcess::UnknownError)); +} + +void QtUtils::showFile(const QString &file) +{ +#ifndef BR_EMBEDDED + QDesktopServices::openUrl(QUrl("file:///" + QFileInfo(file).absoluteFilePath())); +#else // BR_EMBEDDED + (void) file; +#endif // BR_EMBEDDED +} diff --git a/sdk/core/qtutils.h b/sdk/core/qtutils.h new file mode 100644 index 0000000..332d9cc --- /dev/null +++ b/sdk/core/qtutils.h @@ -0,0 +1,67 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef __QTUTILS_H +#define __QTUTILS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace QtUtils +{ + /**** File Utilities ****/ + QStringList getFiles(QDir dir, bool recursive); + QStringList getFiles(const QString ®exp); + QStringList readLines(const QString &file); + void readFile(const QString &file, QStringList &lines); + void readFile(const QString &file, QByteArray &data); + void writeFile(const QString &file, const QStringList &lines); + void writeFile(const QString &file, const QString &data); + void writeFile(const QString &file, const QByteArray &data, int compression = -1); + + /**** Directory Utilities ****/ + void touchDir(const QDir &dir); + void touchDir(const QFile &file); + void touchDir(const QFileInfo &fileInfo); + void emptyDir(QDir &dir); + void deleteDir(QDir &dir); + QString find(const QString &file, const QString &alt); + + /**** String Utilities ****/ + bool toBool(const QString &string); + int toInt(const QString &string); + float toFloat(const QString &string); + QList toFloats(const QStringList &strings); + QStringList toStringList(const std::vector &string_list); + QStringList toStringList(int num_strings, const char* strings[]); + QString shortTextHash(QString string); + QStringList parse(QString args, char split = ','); + void checkArgsSize(const QString &name, const QStringList &args, int min, int max); + + /**** Process Utilities ****/ + bool runRScript(const QString &file); + bool runDot(const QString &file); + void showFile(const QString &file); +} + +#endif // __QTUTILS_H diff --git a/sdk/core/resource.cpp b/sdk/core/resource.cpp new file mode 100644 index 0000000..acc3311 --- /dev/null +++ b/sdk/core/resource.cpp @@ -0,0 +1,17 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "resource.h" diff --git a/sdk/core/resource.h b/sdk/core/resource.h new file mode 100644 index 0000000..ae18d36 --- /dev/null +++ b/sdk/core/resource.h @@ -0,0 +1,96 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef __RESOURCE_H +#define __RESOURCE_H + +#include +#include +#include +#include +#include +#include +#include + +template +class ResourceMaker +{ +public: + virtual ~ResourceMaker() {} + virtual T *make() const = 0; +}; + +template +class DefaultResourceMaker : public ResourceMaker +{ + T *make() const { return new T(); } +}; + +template +class Resource +{ + QSharedPointer< ResourceMaker > resourceMaker; + QSharedPointer< QList > availableResources; + QSharedPointer lock; + QSharedPointer totalResources; + +public: + Resource(ResourceMaker *rm = new DefaultResourceMaker()) + : resourceMaker(rm) + , availableResources(new QList()) + , lock(new QMutex()) + , totalResources(new QSemaphore(QThread::idealThreadCount())) + {} + + ~Resource() + { + qDeleteAll(*availableResources); + } + + T *acquire() const + { + totalResources->acquire(); + lock->lock(); + + if (availableResources->isEmpty()) + availableResources->append(resourceMaker->make()); + T* resource = availableResources->takeFirst(); + + lock->unlock(); + + return resource; + } + + void release(T *resource) const + { + lock->lock(); + availableResources->append(resource); + lock->unlock(); + totalResources->release(); + } + + void setResourceMaker(ResourceMaker *maker) + { + resourceMaker = QSharedPointer< ResourceMaker >(maker); + } + + void setMaxResources(int max) + { + totalResources = QSharedPointer(new QSemaphore(max)); + } +}; + +#endif //__RESOURCE_H diff --git a/sdk/openbr.cpp b/sdk/openbr.cpp new file mode 100644 index 0000000..3a64fa5 --- /dev/null +++ b/sdk/openbr.cpp @@ -0,0 +1,250 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +#include "core/bee.h" +#include "core/classify.h" +#include "core/cluster.h" +#include "core/fuse.h" +#include "core/plot.h" +#include "core/qtutils.h" + +using namespace br; + +const char *br_about() +{ + static QByteArray about = Context::about().toLocal8Bit(); + return about.data(); +} + +void br_cluster(int num_simmats, const char *simmats[], float aggressiveness, const char *csv) +{ + ClusterGallery(QtUtils::toStringList(num_simmats, simmats), aggressiveness, csv); +} + +void br_combine_masks(int num_input_masks, const char *input_masks[], const char *output_mask, const char *method) +{ + BEE::combineMasks(QtUtils::toStringList(num_input_masks, input_masks), output_mask, method); +} + +void br_compare(const char *target_gallery, const char *query_gallery, const char *output) +{ + Compare(File(target_gallery), File(query_gallery), File(output)); +} + +void br_confusion(const char *file, float score, int *true_positives, int *false_positives, int *true_negatives, int *false_negatives) +{ + return Confusion(file, score, *true_positives, *false_positives, *true_negatives, *false_negatives); +} + +void br_convert(const char *input_matrix, const char *output_matrix) +{ + QString inputSuffix = QFileInfo(input_matrix).suffix(); + QString outputSuffix = QFileInfo(output_matrix).suffix(); + if (inputSuffix == "csv") { + if (outputSuffix == "mtx") BEE::CSVToSimmat(input_matrix, output_matrix); + else BEE::CSVToMask(input_matrix, output_matrix); + } else { + if (inputSuffix == "mtx") BEE::simmatToCSV(input_matrix, output_matrix); + else BEE::maskToCSV(input_matrix, output_matrix); + } +} + +void br_enroll(const char *input, const char *gallery) +{ + Enroll(File(input), File(gallery)); +} + +void br_enroll_n(int num_inputs, const char *inputs[], const char *gallery) +{ + if (num_inputs > 1) Enroll(QtUtils::toStringList(num_inputs, inputs).join(";")+"(separator=;)", File(gallery)); + else Enroll(File(inputs[0]), gallery); +} + +float br_eval(const char *simmat, const char *mask, const char *csv) +{ + return Evaluate(simmat, mask, csv); +} + +void br_eval_classification(const char *predicted_input, const char *truth_input) +{ + EvalClassification(predicted_input, truth_input); +} + +void br_eval_clustering(const char *csv, const char *input) +{ + EvalClustering(csv, input); +} + +void br_eval_regression(const char *predicted_input, const char *truth_input) +{ + EvalRegression(predicted_input, truth_input); +} + +void br_finalize() +{ + Context::finalize(); +} + +void br_fuse(int num_input_simmats, const char *input_simmats[], const char *mask, + const char *normalization, const char *fusion, const char *output_simmat) +{ + Fuse(QtUtils::toStringList(num_input_simmats, input_simmats), mask, normalization, fusion, output_simmat); +} + +void br_initialize(int argc, char *argv[], const char *sdk_path) +{ + Context::initialize(argc, argv, sdk_path); +} + +void br_initialize_qt(const char *sdk_path) +{ + Context::initializeQt(sdk_path); +} + +bool br_is_classifier(const char *algorithm) +{ + return IsClassifier(algorithm); +} + +void br_make_mask(const char *target_input, const char *query_input, const char *mask) +{ + BEE::makeMask(target_input, query_input, mask); +} + +const char *br_most_recent_message() +{ + static QByteArray byteArray; + byteArray = Globals->mostRecentMessage.toLocal8Bit(); + return byteArray.data(); +} + +const char *br_objects(const char *abstractions, const char *implementations, bool parameters) +{ + static QByteArray objects; + + QStringList objectList; + QRegExp abstractionsRegExp(abstractions); + QRegExp implementationsRegExp(implementations); + + if (abstractionsRegExp.exactMatch("Abbreviation")) + foreach (const QString &name, Globals->abbreviations.keys()) + if (implementationsRegExp.exactMatch(name)) + objectList.append(name + (parameters ? "\t" + Globals->abbreviations[name] : "")); + + if (abstractionsRegExp.exactMatch("Distance")) + foreach (const QString &name, Factory::names()) + if (implementationsRegExp.exactMatch(name)) + objectList.append(name + (parameters ? "\t" + Factory::parameters(name) : "")); + + if (abstractionsRegExp.exactMatch("Format")) + foreach (const QString &name, Factory::names()) + if (implementationsRegExp.exactMatch(name)) + objectList.append(name + (parameters ? "\t" + Factory::parameters(name) : "")); + + if (abstractionsRegExp.exactMatch("Initializer")) + foreach (const QString &name, Factory::names()) + if (implementationsRegExp.exactMatch(name)) + objectList.append(name + (parameters ? "\t" + Factory::parameters(name) : "")); + + if (abstractionsRegExp.exactMatch("Output")) + foreach (const QString &name, Factory::names()) + if (implementationsRegExp.exactMatch(name)) + objectList.append(name + (parameters ? "\t" + Factory::parameters(name) : "")); + + if (abstractionsRegExp.exactMatch("Transform")) + foreach (const QString &name, Factory::names()) + if (implementationsRegExp.exactMatch(name)) + objectList.append(name + (parameters ? "\t" + Factory::parameters(name) : "")); + + objects = objectList.join("\n").toLocal8Bit(); + return objects.data(); +} + +bool br_plot(int num_files, const char *files[], const char *destination, bool show) +{ + return Plot(QtUtils::toStringList(num_files, files), destination, show); +} + +bool br_plot_metadata(int num_files, const char *files[], const char *columns, bool show) +{ + return PlotMetadata(QtUtils::toStringList(num_files, files), columns, show); +} + +float br_progress() +{ + return Globals->progress(); +} + +void br_read_line(int *argc, const char ***argv) +{ + static QList byteArrayList; + static QVector rawCharArrayList; + + byteArrayList.clear(); rawCharArrayList.clear(); + foreach (const QString &string, QtUtils::parse(QTextStream(stdin).readLine(), ' ')) { + byteArrayList.append(string.toLocal8Bit()); + rawCharArrayList.append(byteArrayList.last().data()); + } + + *argc = byteArrayList.size(); + *argv = rawCharArrayList.data(); +} + +void br_reformat(const char *target_input, const char *query_input, const char *simmat, const char *output) +{ + Output::reformat(TemplateList::fromInput(target_input).files(), TemplateList::fromInput(query_input).files(), simmat, output); +} + +const char *br_scratch_path() +{ + static QByteArray byteArray; + byteArray = Context::scratchPath().toLocal8Bit(); + return byteArray.data(); +} + +const char *br_sdk_path() +{ + static QByteArray sdkPath = QDir(Globals->sdkPath).absolutePath().toLocal8Bit(); + return sdkPath.data(); +} + +void br_set_property(const char *key, const char *value) +{ + Globals->setProperty(key, value); +} + +int br_time_remaining() +{ + return Globals->timeRemaining(); +} + +void br_train(const char *input, const char *model) +{ + Train(input, model); +} + +void br_train_n(int num_inputs, const char *inputs[], const char *model) +{ + Train(QtUtils::toStringList(num_inputs, inputs).join(";"), model); +} + +const char *br_version() +{ + static QByteArray version = Context::version().toLocal8Bit(); + return version.data(); +} diff --git a/sdk/openbr.h b/sdk/openbr.h new file mode 100644 index 0000000..09d6691 --- /dev/null +++ b/sdk/openbr.h @@ -0,0 +1,369 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef __OPENBR_H +#define __OPENBR_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + /*! + * \defgroup c_sdk C SDK + * \brief High-level API for running algorithms and evaluating results. Wrappers are available for other programming languages. + * + * In order to provide a high-level interface that is usable from the command line and callable from other programming languages, + * the API is designed to operate at the "file system" level. + * In other words, arguments to many functions are file paths that specify either a source of input or a desired output. + * File extensions are relied upon to determine \em how files should be interpreted in the context of the function being called. + * The \ref cpp_plugin_sdk should be used if more fine-grained control is required. + * + * \code + * #include + * \endcode + * CMake developers may wish to use share/openbr/cmake/OpenBRConfig.cmake. + * + * \section python_api Python API + * A Python API is available via SWIG. + * \code + * $ ls include/br/python + * \endcode + * + * \section java_api Java API + * A Java API is available via SWIG. + * \code + * $ ls include/br/java + * \endcode + */ + +/*! + * \addtogroup c_sdk + * @{ + */ + +/*! + * \brief Wraps br::Context::about() + * \note \ref managed_return_value + * \see br_version + */ +BR_EXPORT const char *br_about(); + +/*! + * \brief Clusters one or more similarity matrices into a list of subjects. + * + * A similarity matrix is a type of br::Output. The current clustering algorithm is a simplified implementation of \cite zhu11. + * \param num_simmats Size of \c simmats. + * \param simmats Array of \ref simmat composing one large self-similarity matrix arranged in row major order. + * \param aggressiveness The higher the aggressiveness the larger the clusters. Suggested range is [0,10]. + * \param csv The cluster results file to generate. Results are stored one row per cluster and use gallery indices. + */ +BR_EXPORT void br_cluster(int num_simmats, const char *simmats[], float aggressiveness, const char *csv); + +/*! + * \brief Combines several equal-sized mask matrices. + * \param num_input_masks Size of \c input_masks + * \param input_masks Array of \ref mask to combine. + * All matrices must have the same dimensions. + * \param output_mask The file to contain the resulting \ref mask. + * \param method Either: + * - \c And - Ignore comparison if \em any input masks ignore. + * - \c Or - Ignore comparison if \em all input masks ignore. + * \note A comparison may not be simultaneously identified as both a genuine and an impostor by different input masks. + * \see br_make_mask + */ +BR_EXPORT void br_combine_masks(int num_input_masks, const char *input_masks[], const char *output_mask, const char *method); + +/*! + * \brief Compares each template in the query gallery to each template in the target gallery. + * \param target_gallery The br::Gallery file whose templates make up the columns of the output. + * \param query_gallery The br::Gallery file whose templates make up the rows of the output. + * A value of '.' reuses the target gallery as the query gallery. + * \param output Optional br::Output file to contain the results of comparing the templates. + * The default behavior is to print scores to the terminal. + * \see br_enroll + */ +BR_EXPORT void br_compare(const char *target_gallery, const char *query_gallery, const char *output = ""); + +/*! + * \brief Computes the confusion matrix for a dataset at a particular threshold. + * + * Wikipedia Explanation + * \param file .csv file created using \ref br_eval. + * \param score The similarity score to threshold at. + * \param[out] true_positives The true positive count. + * \param[out] false_positives The false positive count. + * \param[out] true_negatives The true negative count. + * \param[out] false_negatives The false negative count. + */ +BR_EXPORT void br_confusion(const char *file, float score, + int *true_positives, int *false_positives, int *true_negatives, int *false_negatives); + +/*! + * \brief Converts a .csv file to/from a \ref simmat or \ref mask. + * \param input_matrix The input matrix. + * \param output_matrix The output matrix. + */ +BR_EXPORT void br_convert(const char *input_matrix, const char *output_matrix); + +/*! + * \brief Constructs template(s) from an input. + * \param input The br::Input set of images to enroll. + * \param gallery The br::Gallery file to contain the enrolled templates. + * By default the gallery will be held in memory and \em input can used as a gallery in \ref br_compare. + * \see br_enroll_n + */ +BR_EXPORT void br_enroll(const char *input, const char *gallery = ""); + +/*! + * \brief Convenience function for enrolling multiple inputs. + * \see br_enroll + */ +BR_EXPORT void br_enroll_n(int num_inputs, const char *inputs[], const char *gallery = ""); + +/*! + * \brief Creates a \c .csv file containing performance metrics from evaluating the similarity matrix using the mask matrix. + * \param simmat The \ref simmat to use. + * \param mask The \ref mask to use. + * \param csv Optional \c .csv file to contain performance metrics. + * \return True accept rate at a false accept rate of one in one hundred. + * \see br_plot + */ +BR_EXPORT float br_eval(const char *simmat, const char *mask, const char *csv = ""); + +/*! + * \brief Evaluates and prints classification accuracy to terminal. + * \param predicted_input The predicted br::Input. + * \param truth_input The ground truth br::Input. + * \see br_enroll + */ +BR_EXPORT void br_eval_classification(const char *predicted_input, const char *truth_input); + +/*! + * \brief Evaluates and prints clustering accuracy to the terminal. + * \param csv The cluster results file. + * \param input The br::input used to generate the \ref simmat that was clustered. + * \see br_cluster + */ +BR_EXPORT void br_eval_clustering(const char *csv, const char *input); + +/*! + * \brief Evaluates regression accuracy to disk. + * \param predicted_input The predicted br::Input. + * \param truth_input The ground truth br::Input. + * \see br_enroll + */ +BR_EXPORT void br_eval_regression(const char *predicted_input, const char *truth_input); + +/*! + * \brief Wraps br::Context::finalize() + * \see br_initialize + */ +BR_EXPORT void br_finalize(); + +/*! + * \brief Perform score level fusion on similarity matrices. + * \param num_input_simmats Size of \em input_simmats. + * \param input_simmats Array of \ref simmat. All simmats must have the same dimensions. + * \param mask \ref mask used to indicate which, if any, values to ignore. + * \param normalization Valid options are: + * - \c None - No score normalization. + * - \c MinMax - Scores normalized to [0,1]. + * - \c ZScore - Scores normalized to a standard normal curve. + * \param fusion Valid options are: + * - \c Min - Uses the minimum score. + * - \c Max - Uses the maximum score. + * - \c Sum - Sums the scores. Sums can also be weighted: SumW1:W2:...:Wn. + * - \c Replace - Replaces scores in the first matrix with scores in the second matrix when the mask is set. + * \param output_simmat \ref simmat to contain the fused scores. + */ +BR_EXPORT void br_fuse(int num_input_simmats, const char *input_simmats[], const char *mask, + const char *normalization, const char *fusion, const char *output_simmat); + +/*! + * \brief Wraps br::Context::initialize() + * \see br_initialize_qt br_finalize + */ +BR_EXPORT void br_initialize(int argc, char *argv[], const char *sdk_path = ""); + +/*! + * \brief Wraps br::Context::initializeQt() + * \see br_initialize br_finalize + */ +BR_EXPORT void br_initialize_qt(const char *sdk_path = ""); + +/*! + * \brief Wraps br::IsClassifier() + */ +BR_EXPORT bool br_is_classifier(const char *algorithm); + +/*! + * \brief Constructs a \ref mask from target and query inputs. + * \param target_input The target br::Input. + * \param query_input The query br::Input. + * \param mask The file to contain the resulting \ref mask. + * \see br_combine_masks + */ +BR_EXPORT void br_make_mask(const char *target_input, const char *query_input, const char *mask); + +/*! + * \brief Returns the most recent line sent to stderr. + * \note \ref managed_return_value + * \see br_progress br_time_remaining + */ +BR_EXPORT const char *br_most_recent_message(); + +/*! + * \brief Returns names and parameters for the requested objects. + * + * Each object is \c \\n seperated. Arguments are seperated from the object name with a \c \\t. + * \param abstractions Regular expression of the abstractions to search. + * \param implementations Regular expression of the implementations to search. + * \param parameters Include parameters after object name. + * \note \ref managed_return_value + * \note This function uses Qt's QRegExp syntax. + */ +BR_EXPORT const char *br_objects(const char *abstractions = ".*", const char *implementations = ".*", bool parameters = true); + +/*! + * \brief Renders performance figures for a set of .csv files. + * + * In order of their output, the figures are: + * -# Metadata table + * -# Detection Error Tradeoff (DET) + * -# Receiver Operating Characteristic (ROC) + * -# Score Distribution (SD) histogram + * -# True Accept Rate Bar Chart (BC) + * -# Cumulative Match Characteristic (CMC) + * -# False Accept Rate (FAR) curve + * -# False Reject Rate (FRR) curve + * + * Several files will be created: + * - destination.R which is the auto-generated R script used to render the figures. + * - destination.pdf which has all of the figures in one file (convenient for attaching in an email). + * - destination_DET.pdf, ..., destination_FAR.pdf which has each figure in a separate file (convenient for including in a presentation). + * + * \param num_files Number of .csv files. + * \param files .csv files created using \ref br_eval. + * \param destination Basename for the resulting figures. + * \param show Open destination.pdf using the system's default PDF viewer. + * \return Returns \c true on success. Returns false on a failure to compile the figures due to a missing, out of date, or incomplete \c R installation. + * \note + * - See \ref installing_r + * - End destination with .png to render as images. + * - Run Rcript on destination.R to recompile the figures. + * \see br_plot_metadata + */ +BR_EXPORT bool br_plot(int num_files, const char *files[], const char *destination, bool show = false); + +/*! + * \brief Renders metadata figures for a set of .csv files with specified columns. + * + * Several files will be created: + * - PlotMetadata.R which is the auto-generated R script used to render the figures. + * - PlotMetadata.pdf which has all of the figures in one file (convenient for attaching in an email). + * - column.pdf, ..., column.pdf which has each figure in a separate file (convenient for including in a presentation). + * + * \param num_files Number of .csv files. + * \param files .csv files created by enrolling templates to .csv metadata files. + * \param columns ';' seperated list of columns to plot. + * \param show Open PlotMetadata.pdf using the system's default PDF viewer. + * \return See \ref br_plot + */ +BR_EXPORT bool br_plot_metadata(int num_files, const char *files[], const char *columns, bool show = false); + +/*! + * \brief Wraps br::Context::progress() + * \see br_most_recent_message br_time_remaining + */ +BR_EXPORT float br_progress(); + +/*! + * \brief Read and parse a line from the terminal. + * + * Used by the \ref cli to implement \c -shell. + * Generally not useful otherwise. + * \param[out] argc argument count + * \param[out] argv argument list + * \note \ref managed_return_value + */ +BR_EXPORT void br_read_line(int *argc, const char ***argv); + +/*! + * \brief Converts a simmat to a new output format. + * \param target_input The target br::Input used to make \em simmat. + * \param query_input The query br::Input used to make \em simmat. + * \param simmat The \ref simmat to reformat. + * \param output The br::Output to create. + */ +BR_EXPORT void br_reformat(const char *target_input, const char *query_input, const char *simmat, const char *output); + +/*! + * \brief Wraps br::Context::scratchPath() + * \note \ref managed_return_value + * \see br_version + */ +BR_EXPORT const char *br_scratch_path(); + +/*! + * \brief Returns the full path to the root of the SDK. + * \note \ref managed_return_value + * \see br_initialize + */ +BR_EXPORT const char *br_sdk_path(); + +/*! + *\brief Wraps br::Context::setProperty() + */ +BR_EXPORT void br_set_property(const char *key, const char *value); + +/*! + * \brief Wraps br::Context::timeRemaining() + * \see br_most_recent_message br_progress + */ +BR_EXPORT int br_time_remaining(); + +/*! + * \brief Trains the br::Transform and br::Comparer on the input. + * \param input The br::Input set of images to train on. + * \param model Optional string specifying the binary file to serialize training results to. + * The trained algorithm can be recovered by using this file as the algorithm. + * By default the trained algorithm will not be serialized to disk. + * \see br_train_n + */ +BR_EXPORT void br_train(const char *input, const char *model = ""); + +/*! + * \brief Convenience function for training on multiple inputs. + * \see br_train + */ +BR_EXPORT void br_train_n(int num_inputs, const char *inputs[], const char *model = ""); + +/*! + * \brief Wraps br::Context::version() + * \note \ref managed_return_value + * \see br_about br_scratch_path + */ +BR_EXPORT const char *br_version(); + +/*! @}*/ + +#ifdef __cplusplus +} +#endif + +#endif // __OPENBR_H diff --git a/sdk/openbr_export.cpp b/sdk/openbr_export.cpp new file mode 100644 index 0000000..4a2b380 --- /dev/null +++ b/sdk/openbr_export.cpp @@ -0,0 +1,386 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/*! + * \mainpage + * \section overview Overview + * \em OpenBR \cite openbr is a toolkit for biometric recognition and evaluation. + * Supported use cases include training new recognition algorithms, interfacing with commercial systems, and measuring algorithm performance. + * Free algorithms are also available for specific modalities including face recognition, face gender \& age estimation, face quality, and document classification. + * + * There are three modules users may interact with: + * - \ref cli - \copybrief cli + * - \ref c_sdk - \copybrief c_sdk + * - \ref cpp_plugin_sdk - \copybrief cpp_plugin_sdk + * + * \section get_started Get Started + * - \ref about + * - \ref installation + * - \ref examples + * - \ref tutorial + */ + +/*! + * \page about About + * + * \em OpenBR is a MITRE internal research project to facilitate prototyping new biometric algorithms and evaluating commercial systems. + * + * OpenBR is written entirely in C/C++ and follows the Semantic Versioning convention for publishing releases. + * The project uses the CMake build system and depends on Qt 4.8 and OpenCV 2.4.3. + * The \ref bee and the conventions established in the MBGC File Overview for experimental setup are used for evaluating algorithm performance. + * + * - Developer mailing list: openbr-dev@googlegroups.com + * - Continuous integration server: CDash + * - BTS POC: Emma Taborsky \, Nick Orlans \ + * + * \authors Josh Klontz \cite jklontz + * \authors Mark Burge \cite mburge + * \authors Brendan Klare \cite bklare + * \authors E. Taborsky \cite etaborsky + * + * Please submit a pull request to add yourself to the authors list! + */ + +/*! + * \page installation Installation + * + * \section installation_from_source From Source + * Installation from source is the recommended method for getting OpenBR on your machine. + * If you need a little help getting started, choose from the list of build instructions for free C++ compilers below: + * - \subpage windows_msvc + * - \subpage windows_mingw + * - \subpage osx_clang + * - \subpage linux_gcc + * - \subpage linux_icc + * + * \section installation_pre_built Pre-Built + * Following your operating system's best practices is recommended for installing the pre-built compressed archives. + * + * However, for temporary evaluation, one simple configuration approach is: + * \par Linux +\verbatim +$ cd bin +$ export LD_LIBRARY_PATH=../lib:${LD_LIBRARY_PATH} +$ sudo ldconfig +$ sudo cp ../share/70-yubikey.rules /etc/udev/rules.d # Only needed if you were given a license dongle. +\endverbatim + * \par OS X +\verbatim +$ cd bin +$ export DYLD_LIBRARY_PATH=../lib:${DYLD_LIBRARY_PATH} +$ export DYLD_FRAMEWORK_PATH=../lib:${DYLD_FRAMEWORK_PATH} +\endverbatim + * \par Windows + * No configuration is necessary! + * + * \section installation_license_dongle License Dongle + * If you were given a USB License Dongle, then dongle must be in the computer in order to use the SDK. + * No configuration of the dongle is needed. + * + * \section installation_done Start Working + * To test for successful installation: +\verbatim +$ cd bin +$ br -help +\endverbatim + */ + +/*! + * \page windows_msvc Windows 7 - Visual Studio Express Edition 2012 - x64 + * -# Download and install Visual Studio 2012 Express Edition for Windows Desktop + * -# If you need a program to mount ISO images then consider the free open source program WinCDEmu. + * -# You will have to register with Microsoft after installation, but it's free. + * -# Grab any available Visual Studio Updates. + * -# Download and install CMake 2.8.10.2 + * -# During installation setup select "add CMake to PATH". + * -# Download and unarchive OpenCV 2.4.3 + * -# If you need a program to unarchive tarballs then consider the free open source program 7-Zip. + * -# Copy the "OpenCV-2.4.3" folder to "C:\" and rename it "OpenCV-2.4.3-msvc2012". + * -# From the Start Menu, select "All Programs" -> "Microsoft Visual Studio 2012" -> "Visual Studio Tools" -> "VS2012 x64 Cross Tools Command Prompt" and enter: + * \code + * $ cd C:\OpenCV-2.4.3-msvc2012 + * $ mkdir build + * $ cd build + * $ cmake -G "Visual Studio 11 Win64" -D WITH_FFMPEG=OFF .. + * \endcode + * -# Open "C:\OpenCV-2.4.3-msvc2012\build\OpenCV.sln" + * -# Under the "BUILD" menu, select "Build Solution". + * -# Switch from "Debug" to "Release" and repeat the above step. + * -# Download and install Qt 4.8.4 + * -# From the Start Menu, select "All Programs" -> "Microsoft Visual Studio 2012" -> "Visual Studio Tools" -> "VS2012 x64 Cross Tools Command Prompt" and enter: + * \code + * $ cd C:\Qt\4.8.4 + * $ configure.exe -platform win32-msvc2012 -no-webkit + * $ nmake + * \endcode + * -# Select the Open Source Edition. + * -# Accept the license offer. + * -# configure.exe will take several minutes to finish. + * -# nmake will take several hours to finish. + * -# Create a GitHub account and follow their instructions for setting up Git. + * -# Launch "Git Bash" and clone OpenBR: + * \code + * $ cd /c + * $ git clone https://github.com/biometrics/openbr.git + * \endcode + * -# Finally time to actually build OpenBR: + * -# From the Start Menu, select "All Programs" -> "Microsoft Visual Studio 2012" -> "Visual Studio Tools" -> "VS2012 x64 Cross Tools Command Prompt" and enter: + * \code + * $ cd C:\mm + * $ mkdir build-msvc2012 + * $ cd build-msvc2012 + * $ cmake -G "NMake Makefiles" -D OpenCV_DIR="C:\OpenCV-2.4.3-msvc2012\build" -D QT_QMAKE_EXECUTABLE="C:\Qt\4.8.4\bin\qmake" .. + * $ nmake + * \endcode + * -# To package OpenBR: + * -# From the Start Menu, select "All Programs" -> "Microsoft Visual Studio 2012" -> "Visual Studio Tools" -> "VS2012 x64 Cross Tools Command Prompt" and enter: + * \code + * $ cd C:\mm + * $ nmake package + * \endcode + */ + +/*! + * \page windows_mingw Windows 7 - MingGW-w64 2.0 - x64 + * -# Download MinGW-w64 GCC 4.7.2 and unarchive. + * -# Use the free open source program 7-Zip to unarchive. + * -# Copy "x86_64-w64-mingw32-gcc-4.7.2-release-win64_rubenvb\mingw64" to "C:\". + * -# Download CMake 2.8.10.2 and install. + * -# During installation setup select "add CMake to PATH". + * -# Download OpenCV 2.4.3 and unarchive. + * -# If you need a program to unarchive tarballs then consider the free open source program 7-Zip. + * -# Copy the "OpenCV-2.4.3" folder to "C:\" and rename it "OpenCV-2.4.3-mingw64". + * -# Double-click "C:\mingw64\mingw64env.cmd" and enter: + * \code + * $ cd C:\OpenCV-2.4.3-mingw64 + * $ mkdir build + * $ cd build + * $ cmake -G "MinGW Makefiles" -D WITH_FFMPEG=OFF -D CMAKE_BUILD_TYPE=Release .. + * $ mingw32-make + * \endcode + * -# Download Qt 4.8.4 and unzip. + * -# Copy "qt-everywhere-opensource-src-4.8.4" to "C:\". + * -# Double-click "C:\mingw64\mingw64env.cmd" and enter: + * \code + * $ cd C:\qt-everywhere-opensource-src-4.8.4 + * $ configure.exe + * $ mingw32-make + * \endcode + * -# Select the Open Source Edition. + * -# Accept the license offer. + * -# configure.exe will take several minutes to finish. + * -# mingw32-make will take several hours to finish. + * -# Create a GitHub account and follow their instructions for setting up Git. + * -# Launch "Git Bash" and clone OpenBR: + * \code + * $ git clone https://github.com/biometrics/openbr.git + * $ cd /c + * \endcode + * -# Finally time to build OpenBR: + * -# Double-click "C:\mingw64\mingw64env.cmd" and enter: + * \code + * $ cd C:\mm + * $ mkdir build-mingw64 + * $ cd build-mingw64 + * $ cmake -G "MinGW Makefiles" -D CMAKE_RC_COMPILER="C:/mingw64/bin/windres.exe" -D OpenCV_DIR="C:\OpenCV-2.4.3-mingw64\build" -D QT_QMAKE_EXECUTABLE="C:\qt-everywhere-opensource-src-4.8.4\bin\qmake" .. + * $ mingw32-make + * \endcode + * -# To package OpenBR: + * -# Download NSIS 2.46 and install. + * -# Double-click "C:\mingw64\mingw64env.cmd" and enter: + * \code + * $ cd C:\mm\build-mingw64 + * $ mingw32-make package + * \endcode + */ + +/*! + * \page osx_clang OS X Mountain Lion - Clang/LLVM 3.1 - x64 + * -# Download and install the latest "Xcode" and "Command Line Tools" from the Apple Developer Downloads page. + * -# Download CMake 2.8.10.2 and install: + * \code + * $ cd ~/Downloads + * $ tar -xf cmake-2.8.10.2.tar.gz + * $ cd cmake-2.8.10.2 + * $ ./configure + * $ make + * $ sudo make install + * \endcode + * -# Download OpenCV 2.4.3 and install: + * \code + * $ cd ~/Downloads + * $ tar -xf OpenCV-2.4.3.tar.bz2 + * $ cd OpenCV-2.4.3 + * $ mkdir build + * $ cd build + * $ cmake .. + * $ make + * $ sudo make install + * \endcode + * -# Download Qt 4.8.4 and install. + * -# Create a GitHub account, follow their instructions for setting up Git, then clone: + * \code + * $ git clone https://github.com/biometrics/openbr.git + * \endcode + * -# Finally time to build OpenBR: + * \code + * $ cd mm + * $ mkdir build + * $ cd build + * $ cmake .. + * $ make + * \endcode + * -# To package OpenBR Download Auxilary Tools for Xcode, drag "PackageMaker" to "Applications", then package: + * \code + * $ cd mm/build + * $ sudo make package # PackageMaker requires sudo + * \endcode + */ + +/*! + * \page linux_gcc Ubuntu 12.04 LTS - GCC 4.6.3 - x64 + * -# Install GCC 4.6.3: + * \code + * $ sudo apt-get update + * $ sudo apt-get install build-essential + * \endcode + * -# Install CMake 2.8.7: + * \code + * $ sudo apt-get install cmake + * \endcode + * -# Download OpenCV 2.4.3 and install: + * \code + * $ cd ~/Downloads + * $ tar -xf OpenCV-2.4.3.tar.bz2 + * $ cd OpenCV-2.4.3 + * $ mkdir build + * $ cd build + * $ cmake -D CMAKE_BUILD_TYPE=Release .. + * $ make + * $ sudo make install + * \endcode + * -# Install Qt 4.8.1: + * \code + * $ sudo apt-get install libqt4-dev + * \endcode + * -# Create a GitHub account, follow their instructions for setting up Git, then clone: + * \code + * $ git clone https://github.com/biometrics/openbr.git + * \endcode + * -# Finally time to build OpenBR: + * \code + * $ cd mm + * $ mkdir build + * $ cd build + * $ cmake .. + * $ make + * \endcode + * -# To package OpenBR: + * \code + * $ cd mm/build + * $ make package + * \endcode + */ + +/*! + * \page linux_icc Ubuntu 12.04 LTS - Intel C++ Studio XE 2013 - x64 + * -# Download Intel C++ Studio XE 2013 and install. + * -# Install CMake 2.8.7: + * \code + * $ sudo apt-get install cmake + * \endcode + * -# Download OpenCV 2.4.3 and install: + * \code + * $ cd ~/Downloads + * $ tar -xf OpenCV-2.4.3.tar.bz2 + * $ cd OpenCV-2.4.3 + * $ mkdir build + * $ cd build + * $ cmake -D CMAKE_BUILD_TYPE=Release .. + * $ make + * $ sudo make install + * \endcode + * -# Install Qt 4.8.1: + * \code + * $ sudo apt-get install libqt4-dev + * \endcode + * -# Create a GitHub account, follow their instructions for setting up Git, then clone: + * \code + * $ git clone https://github.com/biometrics/openbr.git + * \endcode + */ + +/*! + * \page examples Examples + * \brief Source code example applications and their equivalent \ref cli expressions. + * + * Many examples make heavy use of the \ref bee and the conventions established in the MBGC File Overview for experimental setup. + * - \ref compare_faces + * - \ref compare_face_galleries + * - \ref evaluate_face_recognition + * + * \section compare_faces Compare Faces + * \snippet app/examples/compare_faces.cpp compare_faces + * \section compare_face_galleries Compare Face Galleries + * \snippet app/examples/compare_face_galleries.cpp compare_face_galleries + * \section evaluate_face_recognition Evaluate Face Recognition + * \snippet app/examples/evaluate_face_recognition.cpp evaluate_face_recognition + */ + +/*! + * \page tutorial Tutorial + * \brief An end-to-end example covering experimental setup, algorithm development, and performance evaluation on the MNIST Handwritten Digits Dataset. + */ + +/*! + * \page installing_r Installing R + * The \c br reporting framework requires a valid \c R installation in order to generate performance figures. Please follow the instructions below. + * -# Download and Install R + * -# Run \c R + * -# Enter the command: + * \code install.packages(c("ggplot2", "gplots", "reshape", "scales")) \endcode + * -# When prompted, select a \c mirror near you. + * -# Wait for the package installation to complete. + * -# Exit \c R + * \note Installation process requires internet access. + */ + +/*! + * \page bee Biometric Evaluation Environment + * \brief The Biometric Evaluation Environment (BEE) is a NIST standard for evaluating biometric algorithms. + * + * OpenBR implements the following portions of the BEE specification: + * + * \section sigset Signature Set + * A signature set (or \em sigset) is a br::Gallery compliant \c XML file-list specified on page 9 of MBGC File Overview and implemented in xmlGallery. + * Sigsets are identified with a .xml extension. + * + * \section simmat Similarity Matrix + * A similarity matrix (or \em simmat) is a br::Output compliant binary score matrix specified on page 12 of MBGC File Overview and implemented in mtxOutput. + * Simmats are identified with a .mtx extension. + * \see br_eval + * + * \section mask Mask Matrix + * A mask matrix (or \em mask) is a binary matrix specified on page 14 of MBGC File Overview identifying the ground truth genuines and impostors of a corresponding \ref simmat. + * Masks are identified with a .mask extension. + * \see br_make_mask br_combine_masks + */ + +/*! + * \page managed_return_value Managed Return Value + * Memory for the returned value is managed internally and guaranteed until the next call to this function. + */ diff --git a/sdk/openbr_export.h b/sdk/openbr_export.h new file mode 100644 index 0000000..7c22469 --- /dev/null +++ b/sdk/openbr_export.h @@ -0,0 +1,52 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef __OPENBR_EXPORT_H +#define __OPENBR_EXPORT_H + +#if defined SWIG +# define BR_EXPORT extern +# define BR_EXPORT_GUI extern +#else +# if defined BR_LIBRARY +# if defined _WIN32 || defined __CYGWIN__ +# define BR_EXPORT __declspec(dllexport) +# else +# define BR_EXPORT __attribute__((visibility("default"))) +# endif +# else +# if defined _WIN32 || defined __CYGWIN__ +# define BR_EXPORT __declspec(dllimport) +# else +# define BR_EXPORT +# endif +# endif +# if defined BR_LIBRARY_GUI +# if defined _WIN32 || defined __CYGWIN__ +# define BR_EXPORT_GUI __declspec(dllexport) +# else +# define BR_EXPORT_GUI __attribute__((visibility("default"))) +# endif +# else +# if defined _WIN32 || defined __CYGWIN__ +# define BR_EXPORT_GUI __declspec(dllimport) +# else +# define BR_EXPORT_GUI +# endif +# endif +#endif + +#endif // __OPENBR_EXPORT_H diff --git a/sdk/openbr_plugin.cpp b/sdk/openbr_plugin.cpp new file mode 100644 index 0000000..cf06d21 --- /dev/null +++ b/sdk/openbr_plugin.cpp @@ -0,0 +1,1215 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include +#include +#include +#include +#include +#include +#ifdef BR_DISTRIBUTED +#include +#endif // BR_DISTRIBUTED +#include + +#include "version.h" +#include "core/bee.h" +#include "core/common.h" +#include "core/qtutils.h" + +using namespace br; +using namespace cv; + +/* File - public methods */ +QString File::flat() const +{ + QStringList values; + QStringList keys = this->localKeys(); qSort(keys); + foreach (const QString &key, keys) { + const QVariant value = this->value(key); + if (value.isNull()) values.append(key); + else values.append(key + "=" + value.toString()); + } + + QString flat = name; + if (!values.isEmpty()) flat += "[" + values.join(", ") + "]"; + return flat; +} + +QString File::hash() const +{ + return QtUtils::shortTextHash(flat()); +} + +void File::append(const QHash &metadata) +{ + foreach (const QString &key, metadata.keys()) + insert(key, metadata[key]); +} + +void File::append(const File &other) +{ + if (!other.name.isEmpty() && name != other.name) { + if (name.isEmpty()) { + name = other.name; + } else { + if (!contains("separator")) insert("separator", ";"); + name += value("separator").toString() + other.name; + } + } + append(other.m_metadata); +} + +QList File::split() const +{ + if (!contains("separator")) return QList() << *this; + return split(value("separator").toString()); +} + +QList File::split(const QString &separator) const +{ + QList files; + foreach (const QString &word, name.split(separator)) { + File file(word); + file.append(m_metadata); + files.append(file); + } + return files; +} + +bool File::contains(const QString &key) const +{ + return m_metadata.contains(key) || Globals->contains(key); +} + +QVariant File::value(const QString &key) const +{ + return m_metadata.contains(key) ? m_metadata.value(key) : Globals->property(qPrintable(key)); +} + +QString File::subject(int label) +{ + return Globals->classes.key(label, QString::number(label)); +} + +float File::label() const +{ + const QVariant variant = value("Label"); + if (variant.isNull()) return -1; + + if (variant.canConvert(QVariant::Double)) { + bool ok; + float val = variant.toFloat(&ok); + if (ok) return val; + } + + return Globals->classes.value(variant.toString(), -1); +} + +void File::set(const QString &key, const QVariant &value) +{ + if (key == "Label") { + bool ok = false; + if (value.canConvert(QVariant::Double)) + value.toFloat(&ok); + if (!ok && !Globals->classes.contains(value.toString())) + Globals->classes.insert(value.toString(), Globals->classes.size()); + } + + m_metadata.insert(key, value); +} + +QVariant File::get(const QString &key) const +{ + if (!contains(key)) qFatal("File::get missing key: %s", qPrintable(key)); + return value(key); +} + +QVariant File::get(const QString &key, const QVariant &defaultValue) const +{ + if (!contains(key)) return defaultValue; + return value(key); +} + +bool File::getBool(const QString &key) const +{ + if (!contains(key)) return false; + QString v = value(key).toString(); + if (v.isEmpty() || (v == "true")) return true; + if (v == "false") return false; + return v.toInt(); +} + +void File::setBool(const QString &key, bool value) +{ + if (value) m_metadata.insert(key, QVariant()); + else m_metadata.remove(key); +} + +int File::getInt(const QString &key) const +{ + if (!contains(key)) qFatal("File::getInt missing key: %s", qPrintable(key)); + bool ok; int result = value(key).toInt(&ok); + if (!ok) qFatal("File::getInt invalid conversion from: %s", qPrintable(getString(key))); + return result; +} + +int File::getInt(const QString &key, int defaultValue) const +{ + if (!contains(key)) return defaultValue; + bool ok; int result = value(key).toInt(&ok); + if (!ok) return defaultValue; + return result; +} + +float File::getFloat(const QString &key) const +{ + if (!contains(key)) qFatal("File::getFloat missing key: %s", qPrintable(key)); + bool ok; float result = value(key).toFloat(&ok); + if (!ok) qFatal("File::getFloat invalid conversion from: %s", qPrintable(getString(key))); + return result; +} + +float File::getFloat(const QString &key, float defaultValue) const +{ + if (!contains(key)) return defaultValue; + bool ok; float result = value(key).toFloat(&ok); + if (!ok) return defaultValue; + return result; +} + +QString File::getString(const QString &key) const +{ + if (!contains(key)) qFatal("File::getString missing key: %s", qPrintable(key)); + return value(key).toString(); +} + +QString File::getString(const QString &key, const QString &defaultValue) const +{ + if (!contains(key)) return defaultValue; + return value(key).toString(); +} + +QList File::landmarks() const +{ + QList landmarks; + foreach (const QVariant &landmark, value("Landmarks").toList()) + landmarks.append(landmark.toPointF()); + return landmarks; +} + +void File::appendLandmark(const QPointF &landmark) +{ + QList newLandmarks = m_metadata["Landmarks"].toList(); + newLandmarks.append(landmark); + m_metadata["Landmarks"] = newLandmarks; +} + +void File::appendLandmarks(const QList &landmarks) +{ + QList newLandmarks = m_metadata["Landmarks"].toList(); + foreach (const QPointF &landmark, landmarks) + newLandmarks.append(landmark); + m_metadata["Landmarks"] = newLandmarks; +} + +void File::setLandmarks(const QList &landmarks) +{ + QList landmarkList; landmarkList.reserve(landmarks.size()); + foreach (const QPointF &landmark, landmarks) + landmarkList.append(landmark); + m_metadata["Landmarks"] = landmarkList; +} + +QList File::ROIs() const +{ + QList ROIs; + foreach (const QVariant &ROI, value("ROIs").toList()) + ROIs.append(ROI.toRect()); + return ROIs; +} + +void File::appendROI(const QRectF &ROI) +{ + QList newROIs = m_metadata["ROIs"].toList(); + newROIs.append(ROI); + m_metadata["ROIs"] = newROIs; +} + +void File::appendROIs(const QList &ROIs) +{ + QList newROIs = m_metadata["ROIs"].toList(); + foreach (const QRectF &ROI, ROIs) + newROIs.append(ROI); + m_metadata["ROIs"] = newROIs; +} + +void File::setROIs(const QList &ROIs) +{ + QList ROIList; ROIList.reserve(ROIs.size()); + foreach (const QRectF &ROI, ROIs) + ROIList.append(ROI); + m_metadata["ROIs"] = ROIList; +} + +/* File - private methods */ +void File::init(const QString &file) +{ + name = file; + + while (name.endsWith(']') || name.endsWith(')')) { + const bool unnamed = name.endsWith(')'); + + int index, depth = 0; + for (index = name.size()-1; index >= 0; index--) { + if (name[index] == (unnamed ? ')' : ']')) depth--; + else if (name[index] == (unnamed ? '(' : '[')) depth++; + if (depth == 0) break; + } + if (depth != 0) qFatal("Unable to parse: %s", qPrintable(file)); + + const QStringList parameters = QtUtils::parse(name.mid(index+1, name.size()-index-2)); + for (int i=0; ipropertyCount(); i++) { + QMetaProperty property = metaObject()->property(i); + if (!property.isStored(this)) + continue; + + const QString type = property.typeName(); + if (type == "QList") { + foreach (Transform *transform, property.read(this).value< QList >()) + transform->load(stream); + } else if (type == "br::Transform*") { + property.read(this).value()->load(stream); + } else if (type == "bool") { + bool value; + stream >> value; + property.write(this, value); + } else if (type == "int") { + int value; + stream >> value; + property.write(this, value); + } else if (type == "float") { + float value; + stream >> value; + property.write(this, value); + } else if (type == "double") { + double value; + stream >> value; + property.write(this, value); + } else { + qFatal("Can't serialize value of type: %s", qPrintable(type)); + } + } + + init(); +} + +void Object::setProperty(const QString &name, const QString &value) +{ + QString type; + int index = metaObject()->indexOfProperty(qPrintable(name)); + if (index != -1) type = metaObject()->property(index).typeName(); + else return; + + QVariant variant; + if (type.startsWith("QList<") && type.endsWith(">")) { + if (!value.startsWith('[')) qFatal("Object::setProperty expected a list."); + const QStringList strings = parse(value.mid(1, value.size()-2)); + + if (type == "QList") { + QList values; + foreach (const QString &string, strings) + values.append(string.toFloat()); + variant.setValue(values); + } else if (type == "QList") { + QList values; + foreach (const QString &string, strings) + values.append(string.toInt()); + variant.setValue(values); + } else if (type == "QList") { + QList values; + foreach (const QString &string, strings) + values.append(Transform::make(string, this)); + variant.setValue(values); + } else { + qFatal("Unrecognized type: %s", qPrintable(type)); + } + } else if (type == "br::Transform*") { + variant.setValue(Transform::make(value, this)); + } else if (type == "bool") { + if (value.isEmpty()) variant = true; + else if (value == "false") variant = false; + else if (value == "true") variant = true; + else variant = value; + } else { + variant = value; + } + + if (!QObject::setProperty(qPrintable(name), variant)) + qFatal("Failed to set %s::%s to: %s %s", + metaObject()->className(), qPrintable(name), qPrintable(value), qPrintable(type)); +} + +QStringList br::Object::parse(const QString &string, char split) +{ + return QtUtils::parse(string, split); +} + +/* Object - private methods */ +void Object::init(const File &file_) +{ + for (int i=0; ipropertyCount(); i++) { + QMetaProperty property = metaObject()->property(i); + if (property.isResettable()) + if (!property.reset(this)) + qFatal("Failed to reset %s::%s", metaObject()->className(), property.name()); + } + + this->file = file_; + foreach (QString name, file.localKeys()) { + const QString value = file.value(name).toString(); + if (name.startsWith("_Arg")) + name = metaObject()->property(metaObject()->propertyOffset()+name.mid(4).toInt()).name(); + setProperty(name, value); + } + init(); +} + +/* Context - public methods */ +br::Context::Context() +{ + QCoreApplication::setOrganizationName(COMPANY_NAME); + QCoreApplication::setApplicationName(PRODUCT_NAME); + QCoreApplication::setApplicationVersion(PRODUCT_VERSION); + + parallelism = std::max(1, QThread::idealThreadCount()); + blockSize = parallelism * ((sizeof(void*) == 4) ? 128 : 1024); + profiling = quiet = verbose = false; + currentStep = totalSteps = 0; + forceEnrollment = false; +} + +int br::Context::blocks(int size) const +{ + return std::ceil(1.f*size/blockSize); +} + +bool br::Context::contains(const QString &name) +{ + const char *c_name = qPrintable(name); + for (int i=0; ipropertyCount(); i++) + if (!strcmp(c_name, metaObject()->property(i).name())) + return true; + return false; +} + +void br::Context::printStatus() +{ + if (verbose || quiet || (totalSteps < 2)) return; + const float p = progress(); + if (p < 1) { + int s = timeRemaining(); + int h = s / (60*60); + int m = (s - h*60*60) / 60; + s = (s - h*60*60 - m*60); + fprintf(stderr, "%05.2f%% REMAINING=%02d:%02d:%02d COUNT=%g \r", 100 * p, h, m, s, totalSteps); + } +} + +float br::Context::progress() const +{ + if (totalSteps == 0) return -1; + return currentStep / totalSteps; +} + +void br::Context::setProperty(const QString &key, const QString &value) +{ + Object::setProperty(key, value); + qDebug("Set %s%s", qPrintable(key), value.isEmpty() ? "" : qPrintable(" to " + value)); + + if (key == "parallelism") { + const int maxThreads = std::max(1, QThread::idealThreadCount()); + QThreadPool::globalInstance()->setMaxThreadCount(parallelism ? std::min(maxThreads, abs(parallelism)) : maxThreads); + } else if (key == "log") { + logFile.close(); + if (log.isEmpty()) return; + + logFile.setFileName(log); + QtUtils::touchDir(logFile); + logFile.open(QFile::Append); + logFile.write("================================================================================\n"); + } +} + +int br::Context::timeRemaining() const +{ + const float p = progress(); + if (p < 0) return -1; + return std::max(0, int((1 - p) / p * startTime.elapsed())) / 1000; +} + +void br::Context::trackFutures(QList< QFuture > &futures) +{ + foreach (QFuture future, futures) { + QCoreApplication::processEvents(); + future.waitForFinished(); + } +} + +bool br::Context::checkSDKPath(const QString &sdkPath) +{ + return QFileInfo(sdkPath + "/share/openbr/openbr.bib").exists(); +} + +void br::Context::initialize(int argc, char *argv[], const QString &sdkPath) +{ + qRegisterMetaType< QList >(); + qRegisterMetaType< QList >(); + qRegisterMetaType< br::Transform* >(); + qRegisterMetaType< QList >(); + qRegisterMetaType< cv::Mat >(); + + if (Globals == NULL) Globals = new Context(); + Globals->coreApplication = QSharedPointer(new QCoreApplication(argc, argv)); + initializeQt(sdkPath); + +#ifdef BR_DISTRIBUTED + int rank, size; + MPI_Init(&argc, &argv); + MPI_Cobr_rank(MPI_CObr_WORLD, &rank); + MPI_Cobr_size(MPI_CObr_WORLD, &size); + if (!Quiet) qDebug() << "OpenBR distributed process" << rank << "of" << size; +#endif // BR_DISTRIBUTED +} + +void br::Context::initializeQt(QString sdkPath) +{ + if (Globals == NULL) Globals = new Context(); + qInstallMsgHandler(messageHandler); + + // Search for SDK + if (sdkPath.isEmpty()) { + QStringList checkPaths; checkPaths << QDir::currentPath() << QCoreApplication::applicationDirPath(); + + bool foundSDK = false; + foreach (const QString &path, checkPaths) { + if (foundSDK) break; + QDir dir(path); + do { + sdkPath = dir.absolutePath(); + foundSDK = checkSDKPath(sdkPath); + dir.cdUp(); + } while (!foundSDK && !dir.isRoot()); + } + + if (!foundSDK) qFatal("Unable to locate SDK automatically."); + } else { + if (!checkSDKPath(sdkPath)) qFatal("Unable to locate SDK from %s.", qPrintable(sdkPath)); + } + + Globals->sdkPath = sdkPath; + + // Trigger registered initializers + QList< QSharedPointer > initializers = Factory::makeAll(); + foreach (const QSharedPointer &initializer, initializers) + initializer->initialize(); +} + +void br::Context::finalize() +{ + // Trigger registerd finalizers + QList< QSharedPointer > initializers = Factory::makeAll(); + foreach (const QSharedPointer &initializer, initializers) + initializer->finalize(); + +#ifdef BR_DISTRIBUTED + MPI_Finalize(); +#endif // BR_DISTRIBUTED + + delete Globals; + Globals = NULL; +} + +QString br::Context::about() +{ + return QString("%1 %2 %3").arg(PRODUCT_NAME, PRODUCT_VERSION, LEGAL_COPYRIGHT); +} + +QString br::Context::version() +{ + return PRODUCT_VERSION; +} + +QString br::Context::scratchPath() +{ + return QString("%1/%2-%3.%4").arg(QDir::homePath(), PRODUCT_NAME, QString::number(PRODUCT_VERSION_MAJOR), QString::number(PRODUCT_VERSION_MINOR)); +} + +void br::Context::messageHandler(QtMsgType type, const char *msg) +{ + QString txt; + switch (type) { + case QtDebugMsg: + if (Globals->quiet) return; + txt = QString("%1\n").arg(msg); + break; + case QtWarningMsg: + txt = QString("Warning: %1\n").arg(msg); + break; + case QtCriticalMsg: + txt = QString("Critical: %1\n").arg(msg); + break; + case QtFatalMsg: + txt = QString("Fatal: %1\n").arg(msg); + break; + } + + fprintf(stderr, "%s", qPrintable(txt)); + Globals->mostRecentMessage = txt; + + if (Globals->logFile.isWritable()) { + static QMutex logLock; + logLock.lock(); + Globals->logFile.write(qPrintable(txt)); + Globals->logFile.flush(); + logLock.unlock(); + } + + if (type == QtFatalMsg) { + Globals->finalize(); + abort(); + } + + QCoreApplication::processEvents(); // Used to retrieve messages before event loop starts +} + +Context *br::Globals = NULL; + +/* Output - public methods */ +void Output::setBlock(int rowBlock, int columnBlock) +{ + offset = QPoint((columnBlock == -1) ? 0 : Globals->blockSize*columnBlock, + (rowBlock == -1) ? 0 : Globals->blockSize*rowBlock); + if (!next.isNull()) next->setBlock(rowBlock, columnBlock); +} + +void Output::setRelative(float value, int i, int j) +{ + set(value, i+offset.y(), j+offset.x()); + if (!next.isNull()) next->setRelative(value, i, j); +} + +Output *Output::make(const File &file, const FileList &targetFiles, const FileList &queryFiles) +{ + Output *output = NULL; + foreach (const File &subfile, file.split()) { + Output *newOutput = Factory::make(subfile); + newOutput->initialize(targetFiles, queryFiles); + newOutput->next = QSharedPointer(output); + output = newOutput; + } + return output; +} + +void Output::reformat(const FileList &targetFiles, const FileList &queryFiles, const File &simmat, const File &output) +{ + qDebug("Reformating %s to %s", qPrintable(simmat.flat()), qPrintable(output.flat())); + + Mat m = BEE::readSimmat(simmat); + + QSharedPointer o(Factory::make(output)); + o->initialize(targetFiles, queryFiles); + + const int rows = queryFiles.size(); + const int columns = targetFiles.size(); + for (int i=0; isetRelative(m.at(i,i), i, j); +} + +/* Output - protected methods */ +void Output::initialize(const FileList &targetFiles, const FileList &queryFiles) +{ + this->targetFiles = targetFiles; + this->queryFiles = queryFiles; + selfSimilar = (queryFiles == targetFiles) && (targetFiles.size() > 1) && (queryFiles.size() > 1); +} + +/* MatrixOutput - public methods */ +void MatrixOutput::initialize(const FileList &targetFiles, const FileList &queryFiles) +{ + Output::initialize(targetFiles, queryFiles); + data.create(queryFiles.size(), targetFiles.size(), CV_32FC1); +} + +QString MatrixOutput::toString(int row, int column) const +{ + if (targetFiles[column] == "Label") + return File::subject(data.at(row,column)); + return QString::number(data.at(row,column)); +} + +/* MatrixOutput - private methods */ +void MatrixOutput::set(float value, int i, int j) +{ + data.at(i,j) = value; +} + +/* Gallery - public methods */ +TemplateList Gallery::read() +{ + TemplateList templates; + bool done = false; + while (!done) templates.append(readBlock(&done)); + return templates; +} + +FileList Gallery::files() +{ + FileList files; + bool done = false; + while (!done) files.append(readBlock(&done).files()); + return files; +} + +void Gallery::writeBlock(const TemplateList &templates) +{ + foreach (const Template &t, templates) write(t); + if (!next.isNull()) next->writeBlock(templates); +} + +Gallery *Gallery::make(const File &file) +{ + Gallery *gallery = NULL; + foreach (const File &f, file.split()) { + Gallery *next = gallery; + gallery = Factory::make(f); + gallery->next = QSharedPointer(next); + } + return gallery; +} + +static TemplateList Downsample(const TemplateList &templates, const Transform *transform) +{ + // Return early when no downsampling is required + if ((transform->classes == std::numeric_limits::max()) && + (transform->instances == std::numeric_limits::max()) && + (transform->fraction >= 1)) + return templates; + + const bool atLeast = transform->instances < 0; + const int instances = abs(transform->instances); + + QList allLabels = templates.labels(); + QList uniqueLabels = allLabels.toSet().toList(); + qSort(uniqueLabels); + + QMap counts = templates.labelCounts(instances != std::numeric_limits::max()); + if ((instances != std::numeric_limits::max()) && (transform->classes != std::numeric_limits::max())) + foreach (int label, counts.keys()) + if (counts[label] < instances) + counts.remove(label); + uniqueLabels = counts.keys(); + if ((transform->classes != std::numeric_limits::max()) && (uniqueLabels.size() < transform->classes)) + qWarning("Downsample requested %d classes but only %d are available.", transform->classes, uniqueLabels.size()); + + Common::seedRNG(); + QList selectedLabels = uniqueLabels; + if (transform->classes < uniqueLabels.size()) { + std::random_shuffle(selectedLabels.begin(), selectedLabels.end()); + selectedLabels = selectedLabels.mid(0, transform->classes); + } + + TemplateList downsample; + for (int i=0; i indices; + for (int j=0; jrelabel) downsample.last().file.insert("Label", i); + } + } + + if (transform->fraction < 1) { + std::random_shuffle(downsample.begin(), downsample.end()); + downsample = downsample.mid(0, downsample.size()*transform->fraction); + } + + return downsample; +} + +/*! + * \ingroup transforms + * \brief Clones the transform so that it can be applied independently. + * + * \em Independent transforms expect single-matrix templates. + */ +class Independent : public MetaTransform +{ + Q_PROPERTY(QList transforms READ get_transforms WRITE set_transforms STORED false) + BR_PROPERTY(QList, transforms, QList()) + +public: + /*! + * \brief Independent + * \param transform + */ + Independent(Transform *transform) + { + transform->setParent(this); + transforms.append(transform); + file = transform->file; + } + +private: + QString name() const + { + return transforms.first()->name(); + } + + Transform *clone() const + { + return new Independent(transforms.first()->clone()); + } + + static void _train(Transform *transform, const TemplateList *data) + { + transform->train(*data); + } + + void train(const TemplateList &data) + { + // Don't bother constructing datasets if the transform is untrainable + if (dynamic_cast(transforms.first())) + return; + + QList templatesList; + foreach (const Template &t, data) { + if ((templatesList.size() != t.size()) && !templatesList.isEmpty()) + qWarning("Independent::train template %s of size %d differs from expected size %d.", qPrintable((QString)t.file), t.size(), templatesList.size()); + while (templatesList.size() < t.size()) + templatesList.append(TemplateList()); + for (int i=0; iclone()); + + for (int i=0; i > futures; + const bool threaded = Globals->parallelism && (templatesList.size() > 1); + for (int i=0; itrackFutures(futures); + } + + void project(const Template &src, Template &dst) const + { + dst.file = src.file; + for (int i=0; iproject(Template(src.file, src[i]), m); + dst.merge(m); + } + } + + void store(QDataStream &stream) const + { + const int size = transforms.size(); + stream << size; + for (int i=0; istore(stream); + } + + void load(QDataStream &stream) + { + int size; + stream >> size; + while (transforms.size() < size) + transforms.append(transforms.first()->clone()); + for (int i=0; iload(stream); + } +}; + +/* Transform - public methods */ +Transform::Transform(bool independent) +{ + this->independent = independent; + relabel = false; + classes = std::numeric_limits::max(); + instances = std::numeric_limits::max(); + fraction = 1; +} + +Transform *Transform::make(QString str, QObject *parent) +{ + // Check for custom transforms + if (Globals->abbreviations.contains(str)) + return make(Globals->abbreviations[str], parent); + + { // Check for use of '!' as shorthand for Chain(...) + QStringList words = parse(str, '!'); + if (words.size() > 1) + return make("Chain([" + words.join(",") + "])", parent); + } + + { // Check for use of '+' as shorthand for Pipe(...) + QStringList words = parse(str, '+'); + if (words.size() > 1) + return make("Pipe([" + words.join(",") + "])", parent); + } + + { // Check for use of '/' as shorthand for Fork(...) + QStringList words = parse(str, '/'); + if (words.size() > 1) + return make("Fork([" + words.join(",") + "])", parent); + } + + // Check for use of '{...}' as shorthand for Cache(...) + if (str.startsWith('{') && str.endsWith('}')) + return make("Cache(" + str.mid(1, str.size()-2) + ")", parent); + + // Check for use of '<...>' as shorthand for LoadStore(...) + if (str.startsWith('<') && str.endsWith('>')) + return make("LoadStore(" + str.mid(1, str.size()-2) + ")", parent); + + // Check for use of '(...)' to change order of operations + if (str.startsWith('(') && str.endsWith(')')) + return make(str.mid(1, str.size()-2), parent); + + File f = "." + str; + Transform *transform = Factory::make(f); + + if (transform->independent) + transform = new Independent(transform); + transform->setParent(parent); + return transform; +} + +Transform *Transform::clone() const +{ + Transform *clone = Factory::make(file.flat()); + clone->relabel = relabel; + clone->classes = classes; + clone->instances = instances; + clone->fraction = fraction; + return clone; +} + +static void _project(const Transform *transform, const Template *src, Template *dst) +{ + try { + transform->project(*src, *dst); + } catch (...) { + qWarning("Exception triggered when processing %s with transform %s", qPrintable(src->file.flat()), qPrintable(transform->name())); + *dst = Template(src->file); + dst->file.setBool("FTE"); + } +} + +void Transform::project(const TemplateList &src, TemplateList &dst) const +{ + dst.reserve(src.size()); + for (int i=0; i > futures; + if (Globals->parallelism) futures.reserve(src.size()); + for (int i=0; iparallelism) futures.append(QtConcurrent::run(_project, this, &src[i], &dst[i])); + else _project (this, &src[i], &dst[i]); + if (Globals->parallelism) Globals->trackFutures(futures); +} + +/* Distance - public methods */ +void Distance::train(const TemplateList &templates) +{ + const TemplateList samples = templates.mid(0, 2000); + const QList sampleLabels = samples.labels(); + QSharedPointer memoryOutput((MatrixOutput*)Output::make("Matrix", FileList(samples.size()), FileList(samples.size()))); + compare(samples, samples, memoryOutput.data()); + + double genuineAccumulator, impostorAccumulator; + int genuineCount, impostorCount; + genuineAccumulator = impostorAccumulator = genuineCount = impostorCount = 0; + + for (int i=0; idata.at(i, j); + if (sampleLabels[i] == sampleLabels[j]) { + genuineAccumulator += val; + genuineCount++; + } else { + impostorAccumulator += val; + impostorCount++; + } + } + } + + if (genuineCount == 0) { qWarning("No genuine matches."); return; } + if (impostorCount == 0) { qWarning("No impostor matches."); return; } + + double genuineMean = genuineAccumulator / genuineCount; + double impostorMean = impostorAccumulator / impostorCount; + + if (genuineMean == impostorMean) { qWarning("Genuines and impostors are indistinguishable."); return; } + + a = 1.0/(genuineMean-impostorMean); + b = impostorMean; + + qDebug("a = %f, b = %f", a, b); +} + +void Distance::compare(const TemplateList &target, const TemplateList &query, Output *output) const +{ + const bool stepTarget = target.size() > query.size(); + const int totalSize = std::max(target.size(), query.size()); + int stepSize = ceil(float(totalSize) / float(std::max(1, abs(Globals->parallelism)))); + QList< QFuture > futures; futures.reserve(ceil(float(totalSize)/float(stepSize))); + for (int i=0; iparallelism) futures.append(QtConcurrent::run(this, &Distance::compareBlock, targets, queries, output, targetOffset, queryOffset)); + else compareBlock (targets, queries, output, targetOffset, queryOffset); + } + if (Globals->parallelism) Globals->trackFutures(futures); +} + +void Distance::compareBlock(const TemplateList &target, const TemplateList &query, Output *output, int targetOffset, int queryOffset) const +{ + for (int i=0; isetRelative(a * (compare(target[j], query[i]) - b), i+queryOffset, j+targetOffset); +} diff --git a/sdk/openbr_plugin.h b/sdk/openbr_plugin.h new file mode 100644 index 0000000..245365d --- /dev/null +++ b/sdk/openbr_plugin.h @@ -0,0 +1,1019 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2012 The MITRE Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef __OPENBR_PLUGIN_H +#define __OPENBR_PLUGIN_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef BR_EXCEPTIONS +# define try if (true) +# define catch(X) if (false) +#endif // BR_EXCEPTIONS + +/*! + * \defgroup cpp_plugin_sdk C++ Plugin SDK + * \brief Plugin API for developing new algorithms. + * + * \code + * #include + * \endcode + * + * Plugins should be developed in sdk/plugins/. + * They may optionally include a .cmake file to control build configuration. + * See sdk/plugins/misc.cpp for examples of simple plugins. + */ + +namespace br +{ + +/*! + * \addtogroup cpp_plugin_sdk + * @{ + */ + +/*! + * Helper macro for use with Q_PROPERTY. + * + * \b Example:
+ * Note the symmetry between \c BR_PROPERTY and \c Q_PROPERTY. + * \snippet sdk/plugins/misc.cpp example_transform + */ +#define BR_PROPERTY(TYPE,NAME,DEFAULT) \ +TYPE NAME; \ +TYPE get_##NAME() const { return NAME; } \ +void set_##NAME(TYPE the_##NAME) { NAME = the_##NAME; } \ +void reset_##NAME() { NAME = DEFAULT; } + +/*! + * \brief A file path with associated metadata. + * + * The br::File is one of the workhorse classes in OpenBR. + * It is typically used to store the path to a file on disk with associated metadata. + * The ability to associate a hashtable of metadata with the file helps keep the API simple and stable while providing customizable behavior when appropriate. + * + * When querying the value of a metadata key, the value will first try to be resolved using the file's private metadata table. + * If the key does not exist in the local hashtable then it will be resolved using the properities in the global br::Context. + * This has the desirable effect that file metadata may optionally set globally using br::Context::set to operate on all files. + * + * Files have a simple grammar that allow them to be converted to and from strings. + * If a string ends with a \c ] or \c ) then the text within the final \c [] or \c () are parsed as comma sperated metadata fields. + * Fields within \c [] are expected to have the format [key1=value1, key2=value2, ..., keyN=valueN]. + * Fields within \c () are expected to have the format (value1, value2, ..., valueN) with the keys determined from the order of \c Q_PROPERTY. + * The rest of the string is assigned to #name. + * + * Metadata keys fall into one of two categories: + * - \c camelCaseKeys are inputs that specify how to process the file. + * - \c Capitalized_Underscored_Keys are outputs computed from processing the file. + * + * Below are some of the most commonly occuring standardized keys: + * + * Key | Value | Description + * --- | ---- | ----------- + * path | QString | Resolve complete file paths from file names + * forceEnrollment | bool | Enroll exactly one template per file + * separator | QString | Sperate #name into multiple files + * Input_Index | int | Index of a template in a template list + * Label | float | Classification/Regression class + * Confidence | float | Classification/Regression quality + * FTE | bool | Failure to enroll + * FTO | bool | Failure to open + * *_X | float | Position + * *_Y | float | Position + * *_Width | float | Size + * *_Height | float | Size + * *_Radius | float | Size + * Theta | float | Pose + * Roll | float | Pose + * Pitch | float | Pose + * Yaw | float | Pose + * Landmarks | QList | Landmark list + * ROIs | QList | Region Of Interest (ROI) list + * _* | * | Reserved for internal use + */ +struct BR_EXPORT File +{ + QString name; /*!< \brief Path to a file on disk. */ + + File() {} + File(const QString &file) { init(file); } /*!< \brief Construct a file from a string. */ + File(const QString &file, const QVariant &label) { init(file); insert("Label", label); } /*!< \brief Construct a file from a string and assign a label. */ + File(const char *file) { init(file); } /*!< \brief Construct a file from a c-style string. */ + operator QString() const { return name; } /*!< \brief Returns #name. */ + QString flat() const; /*!< \brief A stringified version of the file with metadata. */ + QString hash() const; /*!< \brief A hash of the file. */ + inline void clear() { name.clear(); m_metadata.clear(); } /*!< \brief Clears the file's name and metadata. */ + + inline QList localKeys() const { return m_metadata.keys(); } /*!< \brief Returns the private metadata keys. */ + inline QHash localMetadata() const { return m_metadata; } /*!< \brief Returns the private metadata. */ + inline void insert(const QString &key, const QVariant &value) { set(key, value); } /*!< \brief Equivalent to set(). */ + void append(const QHash &localMetadata); /*!< \brief Add new metadata fields. */ + void append(const File &other); /*!< \brief Append another file using \c separator. */ + QList split() const; /*!< \brief Split the file using \c separator. */ + QList split(const QString &separator) const; /*!< \brief Split the file. */ + + inline void insertParameter(int index, const QVariant &value) { insert("_Arg" + QString::number(index), value); } /*!< \brief Insert a keyless value. */ + inline bool containsParameter(int index) const { return m_metadata.contains("_Arg" + QString::number(index)); } /*!< \brief Check for the existence of a keyless value. */ + inline QVariant parameter(int index) const { return m_metadata.value("_Arg" + QString::number(index)); } /*!< \brief Retrieve a keyless value. */ + + inline bool operator==(const char* other) const { return name == other; } /*!< \brief Compare name to c-style string. */ + inline bool operator==(const File &other) const { return (name == other.name) && (m_metadata == other.m_metadata); } /*!< \brief Compare name and metadata for equality. */ + inline bool operator!=(const File &other) const { return !(*this == other); } /*!< \brief Compare name and metadata for inequality. */ + inline bool operator<(const File &other) const { return name < other.name; } /*!< \brief Compare name. */ + inline bool operator<=(const File &other) const { return name <= other.name; } /*!< \brief Compare name. */ + inline bool operator>(const File &other) const { return name > other.name; } /*!< \brief Compare name. */ + inline bool operator>=(const File &other) const { return name >= other.name; } /*!< \brief Compare name. */ + inline File &operator+=(const QHash &other) { append(other); return *this; } /*!< \brief Add new metadata fields. */ + inline File &operator+=(const File &other) { append(other); return *this; } /*!< \brief Append another file using \c separator. */ + + inline bool isNull() const { return name.isEmpty() && m_metadata.isEmpty(); } /*!< \brief Returns \c true if name and metadata are empty, \c false otherwise. */ + inline bool isTerminal() const { return name == "terminal"; } /*!< \brief Returns \c true if #name is "terminal", \c false otherwise. */ + inline bool exists() const { return QFileInfo(name).exists(); } /*!< \brief Returns \c true if the file exists on disk, \c false otherwise. */ + inline QString fileName() const { return QFileInfo(name).fileName(); } /*!< \brief Returns the file's base name and extension. */ + inline QString baseName() const { const QString baseName = QFileInfo(name).baseName(); + return baseName.isEmpty() ? QDir(name).dirName() : baseName; } /*!< \brief Returns the file's base name. */ + inline QString suffix() const { return QFileInfo(name).suffix(); } /*!< \brief Returns the file's extension. */ + + bool contains(const QString &key) const; /*!< \brief Returns \c true if the key has an associated value, \c false otherwise. */ + QVariant value(const QString &key) const; /*!< \brief Returns the value for the specified key. */ + static QString subject(int label); /*!< \brief Looks up the subject for the provided label. */ + inline QString subject() const { return subject(label()); } /*!< \brief Looks up the subject from the file's label. */ + inline bool failed() const { return getBool("FTE") || getBool("FTO"); } /*!< \brief Returns \c true if the file failed to open or enroll, \c false otherwise. */ + + void set(const QString &key, const QVariant &value); /*!< \brief Insert or overwrite the metadata key with the specified value. */ + QVariant get(const QString &key) const; /*!< \brief Returns a QVariant for the key, throwing an error if the key does not exist. */ + QVariant get(const QString &key, const QVariant &value) const; /*!< \brief Returns a QVariant for the key, returning \em defaultValue if the key does not exist. */ + float label() const; /*!< \brief Convenience function for retrieving the file's \c Label. */ + inline void setLabel(float label) { insert("Label", label); } /*!< \brief Convenience function for setting the file's \c Label. */ + bool getBool(const QString &key) const; /*!< \brief Returns a boolean value for the key. */ + void setBool(const QString &key, bool value = true); /*!< \brief Sets a boolean value for the key. */ + int getInt(const QString &key) const; /*!< \brief Returns an int value for the key, throwing an error if the key does not exist. */ + int getInt(const QString &key, int defaultValue) const; /*!< \brief Returns an int value for the key, returning \em defaultValue if the key does not exist. */ + float getFloat(const QString &key) const; /*!< \brief Returns a float value for the key, throwing an error if the key does not exist. */ + float getFloat(const QString &key, float defaultValue) const; /*!< \brief Returns a float value for the key, returning \em defaultValue if the key does not exist. */ + QString getString(const QString &key) const; /*!< \brief Returns a string value for the key, throwing an error if the key does not exist. */ + QString getString(const QString &key, const QString &defaultValue) const; /*!< \brief Returns a string value for the key, returning \em defaultValue if the key does not exist. */ + + QList landmarks() const; /*!< \brief Returns the file's landmark list. */ + void appendLandmark(const QPointF &landmark); /*!< \brief Adds a landmark to the file's landmark list. */ + void appendLandmarks(const QList &landmarks); /*!< \brief Adds landmarks to the file's landmark list. */ + inline void clearLandmarks() { m_metadata["Landmarks"] = QList(); } /*!< \brief Clears the file's landmark list. */ + void setLandmarks(const QList &landmarks); /*!< \brief Assigns the file's landmark list. */ + + QList ROIs() const; /*!< \brief Returns the file's ROI list. */ + void appendROI(const QRectF &ROI); /*!< \brief Adds a ROI to the file's ROI list. */ + void appendROIs(const QList &ROIs); /*!< \brief Adds ROIs to the file's ROI list. */ + inline void clearROIs() { m_metadata["ROIs"] = QList(); } /*!< \brief Clears the file's landmark list. */ + void setROIs(const QList &ROIs); /*!< \brief Assigns the file's landmark list. */ + +private: + QHash m_metadata; + BR_EXPORT friend QDataStream &operator<<(QDataStream &stream, const File &file); /*!< */ + BR_EXPORT friend QDataStream &operator>>(QDataStream &stream, File &file); /*!< */ + + void init(const QString &file); +}; + +BR_EXPORT QDebug operator<<(QDebug dbg, const File &file); /*!< \brief Prints br::File::flat() to \c stderr. */ +BR_EXPORT QDataStream &operator<<(QDataStream &stream, const File &file); /*!< \brief Serializes the file to a stream. */ +BR_EXPORT QDataStream &operator>>(QDataStream &stream, File &file); /*!< \brief Deserializes the file from a stream. */ + +/*! + * \brief A list of files. + * + * Convenience class for working with a list of files. + */ +struct BR_EXPORT FileList : public QList +{ + FileList() {} + FileList(int n); /*!< \brief Initialize the list with \em n empty files. */ + FileList(const QStringList &files); /*!< \brief Initialize the file list from a string list. */ + FileList(const QList &files) { append(files); } /*!< \brief Initialize the file list from another file list. */ + + QStringList flat() const; /*!< \brief Returns br::File::flat() for each file in the list. */ + QStringList names() const; /*!< \brief Returns #br::File::name for each file in the list. */ + QList labels() const; /*!< \brief Returns br::File::label() for each file in the list. */ + int failures() const; /*!< \brief Returns the number of files with br::File::failed(). */ +}; + +/*! + * \brief A list of matrices associated with a file. + * + * The br::Template is one of the workhorse classes in OpenBR. + * A template represents a biometric at various stages of enrollment and can be modified br::Transform and compared to other templates with br::Comparer. + * + * While there exist many cases (ex. video enrollment, multiple face detects, per-patch subspace learning, ...) where the template will contain more than one matrix, + * in most cases templates have exactly one matrix in their list representing a single image at various stages of enrollment. + * In the cases where exactly one image is expected, the template provides the function m() as an idiom for treating it as a single matrix. + * Casting operators are also provided to pass the template into image processing functions expecting matrices. + * + * Metadata related to the template that is computed during enrollment (ex. bounding boxes, eye locations, quality metrics, ...) should be assigned to the template's #file member. + */ +struct Template : public QList +{ + File file; /*!< \brief The file from which the template is constructed. */ + Template() {} + Template(const File &_file) : file(_file) {} /*!< \brief Initialize #file. */ + Template(const File &_file, const cv::Mat &mat) : file(_file) { QList::append(mat); } /*!< \brief Initialize #file and append a matrix. */ + + inline const cv::Mat &m() const { static const cv::Mat NullMatrix; + return isEmpty() ? qFatal("Template::m() empty template."), NullMatrix : last(); } /*!< \brief Idiom to treat the template as a matrix. */ + inline cv::Mat &m() { return isEmpty() ? QList::append(cv::Mat()), last() : last(); } /*!< \brief Idiom to treat the template as a matrix. */ + inline cv::Mat &operator=(const cv::Mat &other) { return m() = other; } /*!< \brief Idiom to treat the template as a matrix. */ + inline operator const cv::Mat&() const { return m(); } /*!< \brief Idiom to treat the template as a matrix. */ + inline operator cv::Mat&() { return m(); } /*!< \brief Idiom to treat the template as a matrix. */ + inline operator cv::_InputArray() const { return m(); } /*!< \brief Idiom to treat the template as a matrix. */ + inline operator cv::_OutputArray() { return m(); } /*!< \brief Idiom to treat the template as a matrix. */ + inline bool isNull() const { return isEmpty() || !m().data; } /*!< \brief Returns \c true if the template is empty or has no matrix data, \c false otherwise. */ + inline void merge(const Template &other) { QList::append(other); file.append(other.file); } /*!< \brief Append the contents of another template. */ + + /*! + * \brief Returns the total number of bytes in all the matrices. + */ + size_t bytes() const + { + size_t bytes = 0; + foreach (const cv::Mat &m, *this) + bytes += m.total() * m.elemSize(); + return bytes; + } + + /*! + * \brief Copies all the matrices and returns a new template. + */ + Template clone() const + { + Template other(file); + foreach (const cv::Mat &m, *this) other += m.clone(); + return other; + } +}; + +/*! + * \brief A list of templates. + * + * Convenience class for working with a list of templates. + */ +struct TemplateList : public QList