diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index da5fd16da6596671e3441b510943609e8881c50b..512a4d19087bd66c5c895f3f4c5595190122132c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -25,14 +25,14 @@ linux: script: - mkdir build - cd build - - cmake .. + - cmake .. -DWITH_OPTFLOW=TRUE -DBUILD_CALIBRATION=TRUE - make - ctest --output-on-failure windows: stage: all variables: - CMAKE_ARGS: '-DWITH_PCL=FALSE -DCMAKE_GENERATOR_PLATFORM=x64 -DEigen3_DIR="C:/Program Files (x86)/Eigen3/share/eigen3/cmake" -DOpenCV_DIR="D:/opencv-4.0.1/build/install" -DCUDA_TOOLKIT_ROOT_DIR="C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v10.1"' + CMAKE_ARGS: '-DWITH_OPTFLOW=TRUE -DWITH_PCL=FALSE -DCMAKE_GENERATOR_PLATFORM=x64 -DNVPIPE_DIR="D:/Build/NvPipe" -DEigen3_DIR="C:/Program Files (x86)/Eigen3/share/eigen3/cmake" -DOpenCV_DIR="D:/Build/opencv-4.1.1" -DCUDA_TOOLKIT_ROOT_DIR="C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v10.1"' DEPLOY_DIR: 'D:/Shared/AutoDeploy' tags: - win diff --git a/CMakeLists.txt b/CMakeLists.txt index 7082c8a8da8c0f696c1ec532f4e083303846914a..b0aecb25a357a5c978db35a80cb20487adb5d08d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,16 +6,18 @@ include(CheckLanguage) project (ftl.utu.fi) -set(CMAKE_CODELITE_USE_TARGETS ON) include(GNUInstallDirs) include(CTest) enable_testing() -option(WITH_PCL "Use PCL if available" ON) +option(WITH_PCL "Use PCL if available" OFF) +option(WITH_NVPIPE "Use NvPipe for compression if available" ON) +option(WITH_OPTFLOW "Use NVIDIA Optical Flow if available" OFF) option(WITH_FIXSTARS "Use Fixstars libSGM if available" ON) option(BUILD_VISION "Enable the vision component" ON) option(BUILD_RECONSTRUCT "Enable the reconstruction component" ON) option(BUILD_RENDERER "Enable the renderer component" ON) +option(BUILD_CALIBRATION "Enable the calibration component" OFF) set(THREADS_PREFER_PTHREAD_FLAG ON) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/") @@ -28,6 +30,11 @@ find_package( URIParser REQUIRED ) find_package( MsgPack REQUIRED ) find_package( Eigen3 REQUIRED ) +if (WITH_OPTFLOW) + # TODO check that cudaoptflow.hpp exists (OpenCV built with correct contrib modules) + set(HAVE_OPTFLOW true) +endif() + find_package( LibArchive ) if (LibArchive_FOUND) set(HAVE_LIBARCHIVE true) @@ -100,6 +107,28 @@ if (NANOGUI_LIBRARY) message(STATUS "Found NanoGUI: ${NANOGUI_LIBRARY}") endif() +find_library( NVPIPE_LIBRARY NAMES NvPipe libNvPipe PATHS ${NVPIPE_DIR} PATH_SUFFIXES lib) +if (NVPIPE_LIBRARY) + set(HAVE_NVPIPE TRUE) + add_library(nvpipe UNKNOWN IMPORTED) + #set_property(TARGET nanogui PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${NANOGUI_EXTRA_INCS}) + set_property(TARGET nvpipe PROPERTY IMPORTED_LOCATION ${NVPIPE_LIBRARY}) + message(STATUS "Found NvPipe: ${NVPIPE_LIBRARY}") + + if(WIN32) + # Find include + find_path(NVPIPE_INCLUDE_DIRS + NAMES NvPipe.h + PATHS "C:/Program Files/NvPipe" "C:/Program Files (x86)/NvPipe" ${NVPIPE_DIR} + PATH_SUFFIXES include + ) + set_property(TARGET nvpipe PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${NVPIPE_INCLUDE_DIRS}) + endif() +else() + set(NVPIPE_LIBRARY "") + add_library(nvpipe INTERFACE) +endif() + find_program( NODE_NPM NAMES npm ) if (NODE_NPM) message(STATUS "Found NPM: ${NODE_NPM}") @@ -159,7 +188,7 @@ check_include_file_cxx("opencv2/cudastereo.hpp" HAVE_OPENCVCUDA) find_program(CPPCHECK_FOUND cppcheck) if (CPPCHECK_FOUND) message(STATUS "Found cppcheck: will perform source checks") - set(CMAKE_CXX_CPPCHECK "cppcheck" "--enable=warning,performance,portability,style" "--inline-suppr" "--std=c++11" "--suppress=*:*catch.hpp" "--suppress=*:*elas*" "--suppress=*:*nanogui*" "--suppress=*:*json.hpp" "--quiet") + set(CMAKE_CXX_CPPCHECK "cppcheck" "-D__align__(A)" "-DCUDARTAPI" "--enable=warning,performance,style" "--inline-suppr" "--std=c++14" "--suppress=*:*catch.hpp" "--suppress=*:*elas*" "--suppress=*:*nanogui*" "--suppress=*:*json.hpp" "--quiet") endif() # include_directories(${PROJECT_SOURCE_DIR}/common/cpp/include) @@ -185,6 +214,7 @@ SET(CMAKE_USE_RELATIVE_PATHS ON) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) add_subdirectory(components/common/cpp) +add_subdirectory(components/codecs) add_subdirectory(components/net) add_subdirectory(components/rgbd-sources) add_subdirectory(components/control/cpp) @@ -200,11 +230,16 @@ if (BUILD_VISION) add_subdirectory(applications/vision) endif() +if (BUILD_CALIBRATION) + find_package( cvsba REQUIRED ) + add_subdirectory(applications/calibration-multi) +endif() + if (HAVE_PCL) - add_subdirectory(applications/registration) + #add_subdirectory(applications/registration) endif() -if (BUILD_RECONSTRUCT AND HAVE_PCL) +if (BUILD_RECONSTRUCT) add_subdirectory(applications/reconstruct) endif() @@ -232,3 +267,7 @@ if ( TARGET Qt5::Core ) set( CMAKE_CXX_COMPILE_OPTIONS_PIE "-fPIC" ) endif() +if (WIN32) # TODO(nick) Should do based upon compiler (VS) + set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${VS_STARTUP_PROJECT}) + set_property(TARGET ftl-vision PROPERTY VS_DEBUGGER_WORKING_DIRECTORY ${VS_DEBUG_WORKING_DIRECTORY}) +endif() \ No newline at end of file diff --git a/README.md b/README.md index caddf61ebab500e0d844f7ad07a40c70a98db65f..2269fe68c8b670690a14759aa463f343faaf5d0c 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,18 @@ This monorepo contains all elements of the FTL software system. -* Components : Modular components compiled as static libs - * net : A p2p messaging library for C++ and JavaScript - * rgbd-sources : Abstraction and implementations of different RGB-Depth data sources +* [Components](components/) : Modular components compiled as static libs + * [net](components/net/) : A p2p messaging library for C++ and JavaScript + * [rgbd-sources](components/rgbd-sources/) : Abstraction and implementations of different RGB-Depth data sources * renderers : A collection of visualisation tools, including for RGB-D and point clouds * scene-sources : Abstraction and implementations of 3D scene data sources - * common : Utility and configuration tools -* Applications : Executable apps for the different node machines - * vision : Stereo vision node in p2p network, generates an RGB-Depth net stream - * reconstruct : Performs scene reconstruction from synchronised RGB-Depth sources + * [common](components/common/) : Utility and configuration tools +* [Applications](applications/) : Executable apps for the different node machines + * [ftl-vision](applications/vision/) : Stereo vision node in p2p network, generates an RGB-Depth net stream + * [ftl-reconstruct](applications/reconstruct/) : Performs scene reconstruction from synchronised RGB-Depth sources + * calibration-multi : All camera intrinsic and extrinsic calibration in one process + * [ftl-view](applications/groupview/) : A quick camera viewing app that supports frame and video capture + * [ftl-gui](applications/gui/) : Desktop GUI * front-end : Client side FTL code, both web and native * web-service : A web backend service provider acting as a form of proxy * www : FTL Website diff --git a/applications/calibration-multi/CMakeLists.txt b/applications/calibration-multi/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..ae1f1bb1698fbcbd1f8de3dc1b279123025d27b0 --- /dev/null +++ b/applications/calibration-multi/CMakeLists.txt @@ -0,0 +1,12 @@ +set(CALIBMULTI + src/main.cpp + src/visibility.cpp + src/util.cpp + src/multicalibrate.cpp +) + +add_executable(ftl-calibrate-multi ${CALIBMULTI}) + +target_include_directories(ftl-calibrate-multi PRIVATE src) + +target_link_libraries(ftl-calibrate-multi ftlcommon ftlnet ftlrgbd Threads::Threads ${OpenCV_LIBS} ${cvsba_LIBS}) diff --git a/applications/calibration-multi/src/main.cpp b/applications/calibration-multi/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ea770f69b03981164c2b6c97f95aa8841f1d8ae7 --- /dev/null +++ b/applications/calibration-multi/src/main.cpp @@ -0,0 +1,590 @@ +#include <loguru.hpp> + +#include <ftl/configuration.hpp> +#include <ftl/net/universe.hpp> +#include <ftl/rgbd/source.hpp> +#include <ftl/rgbd/group.hpp> + +#include <opencv2/core.hpp> +#include <opencv2/aruco.hpp> +#include <opencv2/core/eigen.hpp> + +#include <algorithm> +#include <numeric> +#include <fstream> + +#include "util.hpp" +#include "multicalibrate.hpp" + +using std::string; +using std::optional; + +using std::list; +using std::vector; +using std::map; +using std::pair; +using std::make_pair; + +using cv::Mat; +using cv::Scalar; +using cv::Size; +using cv::Point2f; +using cv::Point2d; +using cv::Point3f; +using cv::Point3d; +using cv::Vec4f; +using cv::Vec4d; + +using ftl::net::Universe; +using ftl::rgbd::Source; +using ftl::rgbd::Channel; + +Mat getCameraMatrix(const ftl::rgbd::Camera ¶meters) { + Mat m = (cv::Mat_<double>(3,3) << parameters.fx, 0.0, -parameters.cx, 0.0, parameters.fy, -parameters.cy, 0.0, 0.0, 1.0); + return m; +} + +void to_json(nlohmann::json &json, map<string, Eigen::Matrix4d> &transformations) { + for (auto &item : transformations) { + auto val = nlohmann::json::array(); + for(size_t i = 0; i < 16; i++) { val.push_back((float) item.second.data()[i]); } + json[item.first] = val; + } +} + +// FileStorage allows only alphanumeric keys (code below does not work with URIs) + +bool saveRegistration(const string &ofile, const map<string, Mat> &data) { + cv::FileStorage fs(ofile, cv::FileStorage::WRITE); + if (!fs.isOpened()) return false; + for (auto &item : data) { fs << item.first << item.second; } + fs.release(); + return true; +} + +bool saveRegistration(const string &ofile, const map<string, Eigen::Matrix4d> &data) { + map<string, Mat> _data; + for (auto &item : data) { + Mat M; + cv::eigen2cv(item.second, M); + _data[item.first] = M; + } + return saveRegistration(ofile, _data); +} + +bool loadRegistration(const string &ifile, map<string, Mat> &data) { + cv::FileStorage fs(ifile, cv::FileStorage::READ); + if (!fs.isOpened()) return false; + for(cv::FileNodeIterator fit = fs.getFirstTopLevelNode().begin(); + fit != fs.getFirstTopLevelNode().end(); + ++fit) + { + data[(*fit).name()] = (*fit).mat(); + } + fs.release(); + return true; // TODO errors? +} + +bool loadRegistration(const string &ifile, map<string, Eigen::Matrix4d> &data) { + map<string, Mat> _data; + if (!loadRegistration(ifile, _data)) return false; + for (auto &item : _data) { + Eigen::Matrix4d M; + cv::cv2eigen(item.second, M); + data[item.first] = M; + } + return true; +} + +// + +bool saveIntrinsics(const string &ofile, const vector<Mat> &M, const Size &size) { + vector<Mat> D; + { + cv::FileStorage fs(ofile, cv::FileStorage::READ); + fs["D"] >> D; + fs.release(); + } + { + cv::FileStorage fs(ofile, cv::FileStorage::WRITE); + if (fs.isOpened()) { + fs << "resolution" << size; + fs << "K" << M << "D" << D; + fs.release(); + return true; + } + else { + LOG(ERROR) << "Error: can not save the intrinsic parameters to '" << ofile << "'"; + } + return false; + } +} + +bool saveExtrinsics(const string &ofile, Mat &R, Mat &T, Mat &R1, Mat &R2, Mat &P1, Mat &P2, Mat &Q) { + cv::FileStorage fs; + fs.open(ofile, cv::FileStorage::WRITE); + if (fs.isOpened()) { + fs << "R" << R << "T" << T << "R1" << R1 << "R2" << R2 << "P1" + << P1 << "P2" << P2 << "Q" << Q; + fs.release(); + return true; + } else { + LOG(ERROR) << "Error: can not save the extrinsic parameters"; + } + return false; +} + +void stack(const vector<Mat> &img, Mat &out, const int rows, const int cols) { + Size size = img[0].size(); + Size size_out = Size(size.width * cols, size.height * rows); + if (size_out != out.size() || out.type() != CV_8UC3) { + out = Mat(size_out, CV_8UC3, Scalar(0, 0, 0)); + } + + for (size_t i = 0; i < img.size(); i++) { + int row = i % rows; + int col = i / rows; + auto rect = cv::Rect(size.width * col, size.height * row, size.width, size.height); + img[i].copyTo(out(rect)); + } +} + +void stack(const vector<Mat> &img, Mat &out) { + // TODO + int rows = 2; + int cols = (img.size() + 1) / 2; + stack(img, out, rows, cols); +} + +string time_now_string() { + char timestamp[18]; + std::time_t t=std::time(NULL); + std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t)); + return string(timestamp); +} + +void visualizeCalibration( MultiCameraCalibrationNew &calib, Mat &out, + vector<Mat> &rgb, const vector<Mat> &map1, + const vector<Mat> &map2, const vector<cv::Rect> &roi) +{ + vector<Scalar> colors = { + Scalar(64, 64, 255), + Scalar(64, 64, 255), + Scalar(64, 255, 64), + Scalar(64, 255, 64), + }; + + vector<int> markers = {cv::MARKER_SQUARE, cv::MARKER_DIAMOND}; + + for (size_t c = 0; c < rgb.size(); c++) { + cv::remap(rgb[c], rgb[c], map1[c], map2[c], cv::INTER_CUBIC); + cv::rectangle(rgb[c], roi[c], Scalar(24, 224, 24), 2); + + for (int r = 50; r < rgb[c].rows; r = r+50) { + cv::line(rgb[c], cv::Point(0, r), cv::Point(rgb[c].cols-1, r), cv::Scalar(0,0,255), 1); + } + } + + stack(rgb, out); +} + +struct CalibrationParams { + string output_path; + string registration_file; + vector<size_t> idx_cameras; + bool save_extrinsic = true; + bool save_intrinsic = false; + bool optimize_intrinsic = false; + int reference_camera = -1; + double alpha = 0.0; + Size size; +}; + +void calibrate( MultiCameraCalibrationNew &calib, vector<string> &uri_cameras, + const CalibrationParams ¶ms, vector<Mat> &map1, vector<Mat> &map2, vector<cv::Rect> &roi) +{ + int reference_camera = -1; + if (params.reference_camera < 0) { + reference_camera = calib.getOptimalReferenceCamera(); + reference_camera -= (reference_camera & 1); + LOG(INFO) << "optimal camera (automatic): " << reference_camera; + } + LOG(INFO) << "reference camera: " << reference_camera; + + if (params.optimize_intrinsic) calib.setFixIntrinsic(0); + + calib.calibrateAll(reference_camera); + vector<Mat> R, t; + calib.getCalibration(R, t); + + size_t n_cameras = calib.getCamerasCount(); + + vector<Mat> R_rect(n_cameras), t_rect(n_cameras); + vector<Mat> Rt_out(n_cameras); + map1.resize(n_cameras); + map2.resize(n_cameras); + roi.resize(n_cameras); + + for (size_t c = 0; c < n_cameras; c += 2) { + Mat K1 = calib.getCameraMat(c); + Mat K2 = calib.getCameraMat(c + 1); + Mat D1 = calib.getDistCoeffs(c); + Mat D2 = calib.getDistCoeffs(c + 1); + Mat P1, P2, Q; + Mat R1, R2; + Mat R_c1c2, T_c1c2; + + calculateTransform(R[c], t[c], R[c + 1], t[c + 1], R_c1c2, T_c1c2); + cv::stereoRectify(K1, D1, K2, D2, params.size, R_c1c2, T_c1c2, R1, R2, P1, P2, Q, 0, params.alpha); + + Mat _t = Mat(Size(1, 3), CV_64FC1, Scalar(0.0)); + Rt_out[c] = getMat4x4(R[c], t[c]) * getMat4x4(R1, _t).inv(); + Rt_out[c + 1] = getMat4x4(R[c + 1], t[c + 1]) * getMat4x4(R2, _t).inv(); + + { + string node_name; + size_t pos1 = uri_cameras[c/2].find("node"); + size_t pos2 = uri_cameras[c/2].find("#", pos1); + node_name = uri_cameras[c/2].substr(pos1, pos2 - pos1); + + if (params.save_extrinsic) { + // TODO: only R and T required, rectification performed on vision node, + // consider saving global extrinsic calibration? + saveExtrinsics(params.output_path + node_name + "-extrinsic.yml", R_c1c2, T_c1c2, R1, R2, P1, P2, Q); + LOG(INFO) << "Saved: " << params.output_path + node_name + "-extrinsic.yml"; + } + if (params.save_intrinsic) { + saveIntrinsics( + params.output_path + node_name + "-intrinsic.yml", + {calib.getCameraMat(c), + calib.getCameraMat(c + 1)}, + params.size + + ); + LOG(INFO) << "Saved: " << params.output_path + node_name + "-intrinsic.yml"; + } + } + + // for visualization + Size new_size; + cv::stereoRectify(K1, D1, K2, D2, params.size, R_c1c2, T_c1c2, R1, R2, P1, P2, Q, 0, 1.0, new_size, &roi[c], &roi[c + 1]); + cv::initUndistortRectifyMap(K1, D1, R1, P1, params.size, CV_16SC2, map1[c], map2[c]); + cv::initUndistortRectifyMap(K2, D2, R2, P2, params.size, CV_16SC2, map1[c + 1], map2[c + 1]); + } + + { + map<string, Eigen::Matrix4d> out; + for (size_t i = 0; i < n_cameras; i += 2) { + Eigen::Matrix4d M_eigen; + Mat M_cv = Rt_out[i]; + cv::cv2eigen(M_cv, M_eigen); + out[uri_cameras[i/2]] = M_eigen; + } + + nlohmann::json out_json; + to_json(out_json, out); + if (params.save_extrinsic) { + std::ofstream file_out(params.registration_file); + file_out << out_json; + } + else { + LOG(INFO) << "Registration not saved to file"; + LOG(INFO) << out_json; + } + } +} + +void calibrateFromPath( const string &path, + const string &filename, + CalibrationParams ¶ms, + bool visualize=false) +{ + size_t reference_camera = 0; + auto calib = MultiCameraCalibrationNew(0, reference_camera, Size(0, 0), CalibrationTarget(0.250)); + + vector<string> uri_cameras; + cv::FileStorage fs(path + filename, cv::FileStorage::READ); + fs["uri"] >> uri_cameras; + fs["resolution"] >> params.size; + + //params.idx_cameras = {2, 3};//{0, 1, 4, 5, 6, 7, 8, 9, 10, 11}; + params.idx_cameras.resize(uri_cameras.size() * 2); + std::iota(params.idx_cameras.begin(), params.idx_cameras.end(), 0); + + calib.loadInput(path + filename, params.idx_cameras); + + vector<Mat> map1, map2; + vector<cv::Rect> roi; + calibrate(calib, uri_cameras, params, map1, map2, roi); + + if (!visualize) return; + + vector<Scalar> colors = { + Scalar(64, 64, 255), + Scalar(64, 64, 255), + Scalar(64, 255, 64), + Scalar(64, 255, 64), + }; + vector<int> markers = {cv::MARKER_SQUARE, cv::MARKER_DIAMOND}; + + Mat out; + size_t n_cameras = calib.getCamerasCount(); + vector<Mat> rgb(n_cameras); + size_t i = 0; + while(ftl::running) { + for (size_t c = 0; c < n_cameras; c++) { + rgb[c] = cv::imread(path + std::to_string(params.idx_cameras[c]) + "_" + std::to_string(i) + ".jpg"); + for (size_t j = 0; j < rgb.size(); j++) { + vector<Point2d> points; + // TODO: indexing incorrect if all cameras used (see also loadInput) + calib.projectPointsOptimized(c, i, points); // project BA point to image + + for (Point2d &p : points) { + cv::drawMarker(rgb[c], cv::Point2i(p), colors[j % colors.size()], markers[j % markers.size()], 10 + 3 * j, 1); + } + } + } + + visualizeCalibration(calib, out, rgb, map1, map2, roi); + cv::namedWindow("Calibration", cv::WINDOW_KEEPRATIO | cv::WINDOW_NORMAL); + cv::imshow("Calibration", out); + + i = (i + 1) % calib.getViewsCount(); + + if (cv::waitKey(50) != -1) { ftl::running = false; } + } +} + +void runCameraCalibration( ftl::Configurable* root, + int n_views, int min_visible, + string path, string filename, + bool save_input, + CalibrationParams ¶ms) +{ + Universe *net = ftl::create<Universe>(root, "net"); + net->start(); + net->waitConnections(); + + vector<Source*> sources = ftl::createArray<Source>(root, "sources", net); + + const size_t n_sources = sources.size(); + const size_t n_cameras = n_sources * 2; + size_t reference_camera = 0; + + { + auto camera = sources[0]->parameters(); + params.size = Size(camera.width, camera.height); + LOG(INFO) << "Camera resolution: " << params.size; + } + + params.idx_cameras.resize(n_cameras); + std::iota(params.idx_cameras.begin(), params.idx_cameras.end(), 0); + + // TODO: parameter for calibration target type + auto calib = MultiCameraCalibrationNew( n_cameras, reference_camera, + params.size, CalibrationTarget(0.250) + ); + + for (size_t i = 0; i < n_sources; i++) { + auto camera_r = sources[i]->parameters(Channel::Right); + auto camera_l = sources[i]->parameters(Channel::Left); + + CHECK(params.size == Size(camera_r.width, camera_r.height)); + CHECK(params.size == Size(camera_l.width, camera_l.height)); + + Mat K; + K = getCameraMatrix(camera_r); + LOG(INFO) << "K[" << 2 * i + 1 << "] = \n" << K; + calib.setCameraParameters(2 * i + 1, K); + + K = getCameraMatrix(camera_l); + LOG(INFO) << "K[" << 2 * i << "] = \n" << K; + calib.setCameraParameters(2 * i, K); + } + + ftl::rgbd::Group group; + for (Source* src : sources) { + src->setChannel(Channel::Right); + group.addSource(src); + } + + std::mutex mutex; + std::atomic<bool> new_frames = false; + vector<Mat> rgb(n_cameras), rgb_new(n_cameras); + + ftl::timer::start(false); + + group.sync([&mutex, &new_frames, &rgb_new](ftl::rgbd::FrameSet &frames) { + mutex.lock(); + bool good = true; + for (size_t i = 0; i < frames.sources.size(); i ++) { + if (frames.frames[i].get<cv::Mat>(Channel::Left).empty()) good = false; + if (frames.frames[i].get<cv::Mat>(Channel::Right).empty()) good = false; + if (frames.frames[i].get<cv::Mat>(Channel::Left).channels() != 3) good = false; // ASSERT + if (frames.frames[i].get<cv::Mat>(Channel::Right).channels() != 3) good = false; + if (!good) break; + cv::swap(frames.frames[i].get<cv::Mat>(Channel::Left), rgb_new[2 * i]); + cv::swap(frames.frames[i].get<cv::Mat>(Channel::Right), rgb_new[2 * i + 1]); + } + + new_frames = good; + mutex.unlock(); + return true; + }); + + int iter = 0; + Mat show; + + vector<int> visible; + vector<vector<Point2d>> points(n_cameras); + + while(calib.getMinVisibility() < n_views) { + cv::waitKey(10); + while (!new_frames) { + for (auto src : sources) { src->grab(30); } + cv::waitKey(10); + } + + mutex.lock(); + rgb.swap(rgb_new); + new_frames = false; + mutex.unlock(); + + visible.clear(); + int n_found = findCorrespondingPoints(rgb, points, visible); + + if (n_found >= min_visible) { + calib.addPoints(points, visible); + + if (save_input) { + for (size_t i = 0; i < n_cameras; i++) { + cv::imwrite(path + std::to_string(i) + "_" + std::to_string(iter) + ".jpg", rgb[i]); + } + } + iter++; + } + + for (size_t i = 0; i < n_cameras; i++) { + if (visible[i]) { + cv::drawMarker( rgb[i], points[i][0], + Scalar(42, 255, 42), cv::MARKER_TILTED_CROSS, 25, 2); + cv::drawMarker( rgb[i], points[i][1], + Scalar(42, 42, 255), cv::MARKER_TILTED_CROSS, 25, 2); + } + cv::putText(rgb[i], + "Camera " + std::to_string(i), + Point2i(10, 30), + cv::FONT_HERSHEY_COMPLEX_SMALL, 1.0, Scalar(64, 64, 255), 1); + + cv::putText(rgb[i], + std::to_string(std::max(0, (int) (n_views - calib.getViewsCount(i)))), + Point2i(10, rgb[i].rows-10), + cv::FONT_HERSHEY_COMPLEX_SMALL, 1.0, Scalar(64, 64, 255), 1); + + } + + stack(rgb, show); + cv::namedWindow("Cameras", cv::WINDOW_KEEPRATIO | cv::WINDOW_NORMAL); + cv::imshow("Cameras", show); + } + cv::destroyWindow("Cameras"); + + vector<string> uri; + for (size_t i = 0; i < n_sources; i++) { + uri.push_back(sources[i]->getURI()); + } + + if (save_input) { + cv::FileStorage fs(path + filename, cv::FileStorage::WRITE); + fs << "uri" << uri; + calib.saveInput(fs); + fs.release(); + } + + Mat out; + vector<Mat> map1, map2; + vector<cv::Rect> roi; + vector<size_t> idx; + calibrate(calib, uri, params, map1, map2, roi); + + // visualize + while(ftl::running) { + while (!new_frames) { + for (auto src : sources) { src->grab(30); } + if (cv::waitKey(50) != -1) { ftl::running = false; } + } + + mutex.lock(); + rgb.swap(rgb_new); + new_frames = false; + mutex.unlock(); + + visualizeCalibration(calib, out, rgb, map1, map2, roi); + cv::namedWindow("Calibration", cv::WINDOW_KEEPRATIO | cv::WINDOW_NORMAL); + cv::imshow("Calibration", out); + } +} + +int main(int argc, char **argv) { + auto options = ftl::config::read_options(&argv, &argc); + auto root = ftl::configure(argc, argv, "registration_default"); + + // run calibration from saved input? + const bool load_input = root->value<bool>("load_input", false); + // should calibration input be saved + const bool save_input = root->value<bool>("save_input", false); + // should extrinsic calibration be saved (only used with load_input) + const bool save_extrinsic = root->value<bool>("save_extrinsic", true); + // should intrinsic calibration be saved + const bool save_intrinsic = root->value<bool>("save_intrinsic", false); + const bool optimize_intrinsic = root->value<bool>("optimize_intrinsic", false); + // directory where calibration data and images are saved, if save_input enabled + const string calibration_data_dir = root->value<string>("calibration_data_dir", "./"); + // file to save calibration input (2d points and visibility) + const string calibration_data_file = root->value<string>("calibration_data_file", "data.yml"); + // in how many cameras should the pattern be visible + const int min_visible = root->value<int>("min_visible", 3); + // minimum for how many times pattern is seen per camera + const int n_views = root->value<int>("n_views", 500); + // reference camera, -1 for automatic + const int ref_camera = root->value<int>("reference_camera", -1); + // registration file path + const string registration_file = root->value<string>("registration_file", FTL_LOCAL_CONFIG_ROOT "/registration.json"); + // location where extrinsic calibration files saved + const string output_directory = root->value<string>("output_directory", "./"); + + CalibrationParams params; + params.save_extrinsic = save_extrinsic; + params.save_intrinsic = save_intrinsic; + params.optimize_intrinsic = optimize_intrinsic; + params.output_path = output_directory; + params.registration_file = registration_file; + params.reference_camera = ref_camera; + + LOG(INFO) << "\n" + << "\nIMPORTANT: Remeber to set \"use_intrinsics\" to false for nodes!" + << "\n" + << "\n save_input: " << (int) save_input + << "\n load_input: " << (int) load_input + << "\n save_extrinsic: " << (int) save_extrinsic + << "\n save_intrinsic: " << (int) save_intrinsic + << "\n optimize_intrinsic: " << (int) optimize_intrinsic + << "\n calibration_data_dir: " << calibration_data_dir + << "\n calibration_data_file: " << calibration_data_file + << "\n min_visible: " << min_visible + << "\n n_views: " << n_views + << "\n reference_camera: " << ref_camera << (ref_camera != -1 ? "" : " (automatic)") + << "\n registration_file: " << registration_file + << "\n output_directory: " << output_directory + << "\n"; + + if (load_input) { + vector<size_t> idx = {}; + calibrateFromPath(calibration_data_dir, calibration_data_file, params, true); + } + else { + runCameraCalibration(root, n_views, min_visible, calibration_data_dir, calibration_data_file, save_input, params); + } + + return 0; +} \ No newline at end of file diff --git a/applications/calibration-multi/src/multicalibrate.cpp b/applications/calibration-multi/src/multicalibrate.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9d85d1af7ae938981bb445a852fd85a913c6e26e --- /dev/null +++ b/applications/calibration-multi/src/multicalibrate.cpp @@ -0,0 +1,819 @@ +#include "multicalibrate.hpp" + +#include <opencv2/core.hpp> +#include <opencv2/calib3d.hpp> + +#include <cvsba/cvsba.h> +#include <loguru.hpp> + +#include <map> + +using cv::Mat; +using cv::Size; +using cv::Point2d; +using cv::Point3d; +using cv::Vec4d; +using cv::Scalar; + +using std::string; +using std::vector; +using std::map; +using std::pair; +using std::make_pair; + +double CalibrationTarget::estimateScale(vector<Point3d> points) { + + // 1. calculate statistics + // 2. reject possible outliers + // 3. calculate scale factor + + double f = 0.0; + double S = 0.0; + double m = 0.0; + + vector<double> d(points.size() / 2, 0.0); + + for (size_t i = 0; i < points.size(); i += 2) { + const Point3d &p1 = points[i]; + const Point3d &p2 = points[i + 1]; + + Point3d p = p1 - p2; + + double x = sqrt(p.x * p.x + p.y * p.y + p.z * p.z); + double prev_mean = m; + d[i/2] = x; + + f = f + 1.0; + m = m + (x - m) / f; + S = S + (x - m) * (x - prev_mean); + + } + + double stddev = sqrt(S / f); + f = 0.0; + + int outliers = 0; + double scale = 0.0; + + for (double l : d) { + // TODO: * Parameterize how large deviation allowed + // * Validate this actually improves quality + + if (abs(l - m) > 3.0 * stddev) { + outliers++; + } + else { + f += 1.0; + scale += 1.0 / l; + } + DCHECK(scale != INFINITY); + } + + if (outliers != 0) { + LOG(WARNING) << "Outliers (large std. deviation in scale): " << outliers; + } + + LOG(INFO) << "calibration target std. dev. " << stddev << " (" << (int) f << " samples), scale: " << scale * calibration_bar_length_ / f; + + return scale * calibration_bar_length_ / f; + + // TODO: LM-optimization for scale. +} + +MultiCameraCalibrationNew::MultiCameraCalibrationNew( + size_t n_cameras, size_t reference_camera, Size resolution, + CalibrationTarget target, int fix_intrinsics) : + + target_(target), + visibility_graph_(n_cameras), + is_calibrated_(false), + n_cameras_(n_cameras), + reference_camera_(reference_camera), + min_visible_points_(50), + fix_intrinsics_(fix_intrinsics == 1 ? 5 : 0), + + resolution_(resolution), + K_(n_cameras), + dist_coeffs_(n_cameras), + R_(n_cameras), + t_(n_cameras), + + points3d_optimized_(n_cameras), + points3d_(n_cameras), + points2d_(n_cameras), + visible_(n_cameras), + + fm_method_(cv::FM_8POINT), // RANSAC/LMEDS results need validation (does not work) + fm_ransac_threshold_(0.95), + fm_confidence_(0.90) +{ + for (auto &K : K_) { K = Mat::eye(Size(3, 3), CV_64FC1); } + for (auto &d : dist_coeffs_) { d = Mat(Size(5, 1), CV_64FC1, Scalar(0.0)); } +} + +Mat MultiCameraCalibrationNew::getCameraMat(size_t idx) { + DCHECK(idx < n_cameras_); + Mat K; + K_[idx].copyTo(K); + return K; +} + + +Mat MultiCameraCalibrationNew::getCameraMatNormalized(size_t idx, double scale_x, double scale_y) +{ + Mat K = getCameraMat(idx); + CHECK((scale_x != 0.0 && scale_y != 0.0) || ((scale_x == 0.0) && scale_y == 0.0)); + + scale_x = scale_x / (double) resolution_.width; + scale_y = scale_y / (double) resolution_.height; + + Mat scale(Size(3, 3), CV_64F, 0.0); + scale.at<double>(0, 0) = scale_x; + scale.at<double>(1, 1) = scale_y; + scale.at<double>(2, 2) = 1.0; + + return (scale * K); +} + +Mat MultiCameraCalibrationNew::getDistCoeffs(size_t idx) { + DCHECK(idx < n_cameras_); + Mat D; + dist_coeffs_[idx].copyTo(D); + return D; +} + +void MultiCameraCalibrationNew::setCameraParameters(size_t idx, const Mat &K, const Mat &distCoeffs) { + DCHECK(idx < n_cameras_); + DCHECK(K.size() == Size(3, 3)); + DCHECK(distCoeffs.size() == Size(5, 1)); + K.convertTo(K_[idx], CV_64FC1); + distCoeffs.convertTo(dist_coeffs_[idx], CV_64FC1); +} + +void MultiCameraCalibrationNew::setCameraParameters(size_t idx, const Mat &K) { + DCHECK(idx < n_cameras_); + setCameraParameters(idx, K, dist_coeffs_[idx]); +} + +void MultiCameraCalibrationNew::addPoints(vector<vector<Point2d>> points, vector<int> visible) { + DCHECK(points.size() == visible.size()); + DCHECK(visible.size() == n_cameras_); + + for (size_t i = 0; i < n_cameras_; i++) { + visible_[i].insert(visible_[i].end(), points[i].size(), visible[i]); + points2d_[i].insert(points2d_[i].end(), points[i].begin(), points[i].end()); + } + visibility_graph_.update(visible); +} + +void MultiCameraCalibrationNew::reset() { + is_calibrated_ = false; + weights_ = vector(n_cameras_, vector(points2d_[0].size(), 0.0)); + inlier_ = vector(n_cameras_, vector(points2d_[0].size(), 0)); + points3d_ = vector(n_cameras_, vector(points2d_[0].size(), Point3d())); + points3d_optimized_ = vector(points2d_[0].size(), Point3d()); + R_ = vector<Mat>(n_cameras_, Mat::eye(Size(3, 3), CV_64FC1)); + t_ = vector<Mat>(n_cameras_, Mat(Size(1, 3), CV_64FC1, Scalar(0.0))); +} + +void MultiCameraCalibrationNew::saveInput(const string &filename) { + cv::FileStorage fs(filename, cv::FileStorage::WRITE); + saveInput(fs); + fs.release(); +} + +void MultiCameraCalibrationNew::saveInput(cv::FileStorage &fs) { + fs << "resolution" << resolution_; + fs << "K" << K_; + fs << "points2d" << points2d_; + fs << "visible" << visible_; +} + +void MultiCameraCalibrationNew::loadInput(const std::string &filename, const vector<size_t> &cameras_in) { + points2d_.clear(); + points3d_.clear(); + points3d_optimized_.clear(); + visible_.clear(); + inlier_.clear(); + + cv::FileStorage fs(filename, cv::FileStorage::READ); + vector<Mat> K; + vector<vector<Point2d>> points2d; + vector<vector<int>> visible; + fs["K"] >> K; + fs["points2d"] >> points2d; + fs["visible"] >> visible; + fs["resolution"] >> resolution_; + fs.release(); + + vector<size_t> cameras; + if (cameras_in.size() == 0) { + cameras.resize(K.size()); + size_t i = 0; + for (auto &c : cameras) { c = i++; } + } + else { + cameras.reserve(cameras_in.size()); + for (auto &c : cameras_in) { cameras.push_back(c); } + } + + n_cameras_ = cameras.size(); + + points2d_.resize(n_cameras_); + points3d_.resize(n_cameras_); + visible_.resize(n_cameras_); + + for (auto const &c : cameras) { + K_.push_back(K[c]); + } + for (size_t c = 0; c < n_cameras_; c++) { + points2d_[c].reserve(visible[0].size()); + points3d_[c].reserve(visible[0].size()); + visible_[c].reserve(visible[0].size()); + points3d_optimized_.reserve(visible[0].size()); + } + + visibility_graph_ = Visibility(n_cameras_); + dist_coeffs_.resize(n_cameras_); + for (auto &d : dist_coeffs_ ) { d = Mat(Size(5, 1), CV_64FC1, Scalar(0.0)); } + + vector<vector<Point2d>> points2d_add(n_cameras_, vector<Point2d>()); + vector<int> visible_add(n_cameras_); + for (size_t i = 0; i < visible[0].size(); i += target_.n_points) { + int count = 0; + for (size_t c = 0; c < n_cameras_; c++) { + count += visible[c][i]; + points2d_add[c].clear(); + points2d_add[c].insert( + points2d_add[c].begin(), + points2d[cameras[c]].begin() + i, + points2d[cameras[c]].begin() + i + target_.n_points); + visible_add[c] = visible[cameras[c]][i]; + } + if (count >= 2) { + addPoints(points2d_add, visible_add); + } + } + reset(); + + DCHECK(points2d_.size() == n_cameras_); + DCHECK(points2d_.size() == visible_.size()); + size_t len = visible_[0].size(); + for (size_t i = 0; i < n_cameras_; i++) { + DCHECK(visible_[i].size() == len); + DCHECK(points2d_[i].size() == visible_[i].size()); + } +} + +size_t MultiCameraCalibrationNew::getViewsCount() { + return points2d_[0].size() / target_.n_points; +} + +size_t MultiCameraCalibrationNew::getOptimalReferenceCamera() { + return (size_t) visibility_graph_.getOptimalCamera(); +} + +bool MultiCameraCalibrationNew::isVisible(size_t camera, size_t idx) { + return visible_[camera][idx] == 1; +} + +bool MultiCameraCalibrationNew::isValid(size_t camera, size_t idx) { + return inlier_[camera][idx] >= 0; +} + +bool MultiCameraCalibrationNew::isValid(size_t idx) { + for (auto camera : inlier_) { + if (camera[idx] > 0) return true; + } + return false; +} + +vector<Point2d> MultiCameraCalibrationNew::getPoints(size_t camera, size_t idx) { + return vector<Point2d> (points2d_[camera].begin() + idx * (target_.n_points), + points2d_[camera].begin() + idx * (target_.n_points + 1)); +} + + +void MultiCameraCalibrationNew::updatePoints3D(size_t camera, Point3d new_point, + size_t idx, const Mat &R, const Mat &t) { + + int &f = inlier_[camera][idx]; + Point3d &point = points3d_[camera][idx]; + new_point = transformPoint(new_point, R, t); + + if (f == -1) return; + + if (f > 0) { + // TODO: remove parameter (10.0 cm - 1.0m); over 0.25m difference + // would most likely suggest very bad triangulation (sync? wrong match?) + // instead store all triangulations and handle outliers + // (perhaps inverse variance weighted mean?) + + if (euclideanDistance(point, new_point) > 10.0) { + LOG(ERROR) << "bad value (skipping) " << "(" << point << " vs " << new_point << ")"; + f = -1; + } + else { + point = (point * f + new_point) / (double) (f + 1); + f++; + } + } + else { + point = new_point; + f = 1; + } +} + +void MultiCameraCalibrationNew::updatePoints3D(size_t camera, vector<Point3d> points, + vector<size_t> idx, const Mat &R, const Mat &t) { + + for (size_t i = 0; i < idx.size(); i++) { + updatePoints3D(camera, points[i], idx[i], R, t); + } +} + +void MultiCameraCalibrationNew::getVisiblePoints( + vector<size_t> cameras, vector<vector<Point2d>> &points, vector<size_t> &idx) { + + size_t n_points_total = points2d_[0].size(); + DCHECK(cameras.size() <= n_cameras_); + DCHECK(n_points_total % target_.n_points == 0); + + idx.clear(); + idx.reserve(n_points_total); + points.clear(); + points.resize(cameras.size(), {}); + + for (size_t i = 0; i < n_points_total; i += target_.n_points) { + bool visible_all = true; + + for (auto c : cameras) { + for (size_t j = 0; j < target_.n_points; j++) { + visible_all &= isVisible(c, i + j); + } + } + + if (!visible_all) { continue; } + + for (size_t j = 0; j < target_.n_points; j++) { + idx.push_back(i + j); + } + + for (size_t c = 0; c < cameras.size(); c++) { + points[c].insert(points[c].end(), + points2d_[cameras[c]].begin() + i, + points2d_[cameras[c]].begin() + i + target_.n_points + ); + } + } + + for (auto p : points) { DCHECK(idx.size() == p.size()); } +} + +double MultiCameraCalibrationNew::calibratePair(size_t camera_from, size_t camera_to, Mat &rmat, Mat &tvec) { + + vector<size_t> idx; + vector<Point2d> points1, points2; + { + vector<vector<Point2d>> points2d; + getVisiblePoints({camera_from, camera_to}, points2d, idx); + + points1 = points2d[0]; + points2 = points2d[1]; + } + DCHECK(points1.size() % target_.n_points == 0); + DCHECK(points1.size() == points2.size()); + + // cameras possibly lack line of sight? + DCHECK(points1.size() > 8); + + Mat &K1 = K_[camera_from]; + Mat &K2 = K_[camera_to]; + + vector<uchar> inliers; + Mat F, E; + F = cv::findFundamentalMat(points1, points2, fm_method_, fm_ransac_threshold_, fm_confidence_, inliers); + + if (F.empty()) + { + LOG(ERROR) << "Fundamental matrix estimation failed. Possibly degenerate configuration?"; + return INFINITY; + } + + E = K2.t() * F * K1; + + // Only include inliers + if (fm_method_ == cv::FM_LMEDS || fm_method_ == cv::FM_RANSAC) { + vector<Point2d> inliers1, inliers2; + vector<size_t> inliers_idx; + + inliers1.reserve(points1.size()); + inliers2.reserve(points1.size()); + inliers_idx.reserve(points1.size()); + + for (size_t i = 0; i < inliers.size(); i += target_.n_points) { + bool inlier = true; + + for (size_t j = 0; j < target_.n_points; j++) { + inlier &= inliers[i+j]; + } + + if (inlier) { + inliers1.insert(inliers1.end(), points1.begin() + i, points1.begin() + i + target_.n_points); + inliers2.insert(inliers2.end(), points2.begin() + i, points2.begin() + i + target_.n_points); + inliers_idx.insert(inliers_idx.end(), idx.begin() + i, idx.begin() + i + target_.n_points); + } + } + + LOG(INFO) << "Total points: " << points1.size() << ", inliers: " << inliers1.size(); + double ratio_good_points = (double) inliers1.size() / (double) points1.size(); + if (ratio_good_points < 0.66) { + // TODO: ... + LOG(WARNING) << "Over 1/3 of points rejected!"; + if (ratio_good_points < 0.33) { LOG(FATAL) << "Over 2/3 points rejected!"; } + } + + DCHECK(inliers1.size() == inliers_idx.size()); + DCHECK(inliers2.size() == inliers_idx.size()); + + std::swap(inliers1, points1); + std::swap(inliers2, points2); + std::swap(inliers_idx, idx); + } + + // Estimate initial rotation matrix and translation vector and triangulate + // points (in camera 1 coordinate system). + + Mat R1, R2, t1, t2; + R1 = Mat::eye(Size(3, 3), CV_64FC1); + t1 = Mat(Size(1, 3), CV_64FC1, Scalar(0.0)); + + vector<Point3d> points3d; + // Convert homogeneous coordinates + { + Mat points3dh; + recoverPose(E, points1, points2, K1, K2, R2, t2, 1000.0, points3dh); + points3d.reserve(points3dh.cols); + + for (int col = 0; col < points3dh.cols; col++) { + Point3d p = Point3d(points3dh.at<double>(0, col), + points3dh.at<double>(1, col), + points3dh.at<double>(2, col)) + / points3dh.at<double>(3, col); + points3d.push_back(p); + } + } + DCHECK(points3d.size() == points1.size()); + + // Estimate and apply scale factor + { + double scale = target_.estimateScale(points3d); + for (auto &p : points3d) { p = p * scale; } + t1 = t1 * scale; + t2 = t2 * scale; + } + + // Reprojection error before BA + { + // SBA should report squared mean error + const double err1 = reprojectionError(points3d, points1, K1, R1, t1); + const double err2 = reprojectionError(points3d, points2, K2, R2, t2); + + if (abs(err1 - err2) > 2.0) { + LOG(INFO) << "Initial reprojection error (camera " << camera_from << "): " << err1; + LOG(INFO) << "Initial reprojection error (camera " << camera_to << "): " << err2; + } + LOG(INFO) << "Initial reprojection error (" << camera_from << ", " << camera_to << "): " + << sqrt(err1 * err1 + err2 * err2); + + } + + // Bundle Adjustment + // vector<Point3d> points3d_triangulated; + // points3d_triangulated.insert(points3d_triangulated.begin(), points3d.begin(), points3d.end()); + LOG(INFO) << K1; + double err; + cvsba::Sba sba; + { + sba.setParams(cvsba::Sba::Params(cvsba::Sba::TYPE::MOTIONSTRUCTURE, 200, 1.0e-30, 5, 5, false)); + + Mat rvec1, rvec2; + cv::Rodrigues(R1, rvec1); + cv::Rodrigues(R2, rvec2); + + auto points2d = vector<vector<Point2d>> { points1, points2 }; + auto K = vector<Mat> { K1, K2 }; + auto r = vector<Mat> { rvec1, rvec2 }; + auto t = vector<Mat> { t1, t2 }; + auto dcoeffs = vector<Mat> { dist_coeffs_[camera_from], dist_coeffs_[camera_to] }; + + sba.run(points3d, + vector<vector<Point2d>> { points1, points2 }, + vector<vector<int>>(2, vector<int>(points1.size(), 1)), + K, r, t, dcoeffs + ); + + cv::Rodrigues(r[0], R1); + cv::Rodrigues(r[1], R2); + t1 = t[0]; + t2 = t[1]; + + // intrinsic parameters should only be optimized at final BA + //K1 = K[0]; + //K2 = K[1]; + + err = sba.getFinalReprjError(); + LOG(INFO) << "SBA reprojection error before BA " << sba.getInitialReprjError(); + LOG(INFO) << "SBA reprojection error after BA " << err; + } + + calculateTransform(R2, t2, R1, t1, rmat, tvec); + + // Store and average 3D points for both cameras (skip garbage) + if (err < 10.0) { + Mat rmat1, tvec1; + updatePoints3D(camera_from, points3d, idx, R1, t1); + updatePoints3D(camera_to, points3d, idx, R2, t2); + } + else { + LOG(ERROR) << "Large RMS error (" + << reprojectionError(points3d, points2, K2, rmat, tvec) + << "), not updating points!"; + } + + //LOG(INFO) << reprojectionError(points3d, points1, K1, R1, t1); + //LOG(INFO) << reprojectionError(points3d, points2, K2, R2, t2); + + return err; +} + +Point3d MultiCameraCalibrationNew::getPoint3D(size_t camera, size_t idx) { + return points3d_[camera][idx]; +} + +void MultiCameraCalibrationNew::calculateMissingPoints3D() { + points3d_optimized_.clear(); + points3d_optimized_.resize(points3d_[reference_camera_].size()); + + for (size_t i = 0; i < points3d_optimized_.size(); i++) { + if (inlier_[reference_camera_][i] > 0) { + points3d_optimized_[i] = points3d_[reference_camera_][i]; + continue; + } + + if (!isValid(i)) continue; + + double f = 0.0; + Point3d point(0.0, 0.0, 0.0); + for (size_t c = 0; c < n_cameras_; c++) { + if (inlier_[c][i] <= 0) { continue; } + point += transformPoint(getPoint3D(c, i), R_[c], t_[c]); + f += 1.0; + } + + DCHECK(f != 0.0); + + points3d_optimized_[i] = point / f; + } +} + +double MultiCameraCalibrationNew::getReprojectionError(size_t c_from, size_t c_to, const Mat &K, const Mat &R, const Mat &t) { + + vector<Point2d> points2d; + vector<Point3d> points3d; + + for (size_t i = 0; i < points2d_[c_from].size(); i++) { + if (!isValid(i) || !isVisible(c_from, i) || !isVisible(c_to, i)) continue; + points2d.push_back(points2d_[c_from][i]); + points3d.push_back(points3d_[c_to][i]); + } + + return reprojectionError(points3d, points2d, K, R, t); +} + +double MultiCameraCalibrationNew::getReprojectionErrorOptimized(size_t c_from, const Mat &K, const Mat &R, const Mat &t) { + + vector<Point2d> points2d; + vector<Point3d> points3d; + + for (size_t i = 0; i < points2d_[c_from].size(); i++) { + if (!isValid(i) || !isVisible(c_from, i)) continue; + points2d.push_back(points2d_[c_from][i]); + points3d.push_back(points3d_optimized_[i]); + } + + return reprojectionError(points3d, points2d, K, R, t); +} + + +double MultiCameraCalibrationNew::calibrateAll(int reference_camera) { + if (reference_camera != -1) { + DCHECK(reference_camera >= 0 && reference_camera < n_cameras_); + reference_camera_ = reference_camera; + } + + reset(); // remove all old calibration results + map<pair<size_t, size_t>, pair<Mat, Mat>> transformations; + + // All cameras should be calibrated pairwise; otherwise all possible 3D + // points are not necessarily triangulated + + auto paths = visibility_graph_.findShortestPaths(reference_camera_); + + for (size_t c1 = 0; c1 < n_cameras_; c1++) { + for (size_t c2 = c1; c2 < n_cameras_; c2++) { + if (c1 == c2) { + transformations[make_pair(c1, c2)] = + make_pair(Mat::eye(Size(3, 3), CV_64FC1), + Mat(Size(1, 3), CV_64FC1, Scalar(0.0)) + ); + continue; + } + + size_t n_visible = getVisiblePointsCount({c1, c2}); + + if (n_visible < min_visible_points_) { + LOG(INFO) << "Not enough (" << min_visible_points_ << ") points between " + << "cameras " << c1 << " and " << c2 << " (" << n_visible << " points), " + << "skipping"; + continue; + } + LOG(INFO) << "Running pairwise calibration for cameras " + << c1 << " and " << c2 << "(" << n_visible << " points)"; + + if (transformations.find(make_pair(c2, c1)) != transformations.end()) { + continue; + } + Mat R, t, R_i, t_i; + + // TODO: threshold parameter, 16.0 possibly too high + + if (calibratePair(c1, c2, R, t) > 16.0) { + LOG(ERROR) << "Pairwise calibration failed, skipping cameras " + << c1 << " and " << c2; + visibility_graph_.deleteEdge(c1, c2); + continue; + } + + calculateInverse(R, t, R_i, t_i); + + transformations[make_pair(c2, c1)] = make_pair(R, t); + transformations[make_pair(c1, c2)] = make_pair(R_i, t_i); + }} + + for (size_t c = 0; c < paths.size(); c++) { + Mat R_chain = Mat::eye(Size(3, 3), CV_64FC1); + Mat t_chain = Mat(Size(1, 3), CV_64FC1, Scalar(0.0)); + LOG(INFO) << "Chain for camera " << c; + for (auto e: paths[c]) { + CHECK(transformations.find(e) != transformations.end()) << "chain not calculated; pairwise calibration possibly failed earlier?"; + LOG(INFO) << e.first << " -> " << e.second; + Mat R = transformations[e].first; + Mat t = transformations[e].second; + R_chain = R * R_chain; + t_chain = t + R * t_chain; + } + + R_[c] = R_chain; + t_[c] = t_chain; + /*R_[c] = transformations[make_pair(reference_camera_, c)].first; + t_[c] = transformations[make_pair(reference_camera_, c)].second; + DCHECK(R_[c].size() == Size(3, 3)); + DCHECK(t_[c].size() == Size(1, 3));*/ + } + + calculateMissingPoints3D(); + + for (size_t c_from = 0; c_from < n_cameras_; c_from++) { + if (c_from == reference_camera_) continue; + Mat R, t; + calculateInverse(R_[c_from], t_[c_from], R, t); + LOG(INFO) << "Error before BA, cameras " << reference_camera_ << " and " << c_from << ": " + << getReprojectionErrorOptimized(c_from, K_[c_from], R, t); + + } + + double err; + cvsba::Sba sba; + { + sba.setParams(cvsba::Sba::Params(cvsba::Sba::TYPE::MOTIONSTRUCTURE, 200, 1.0e-24, fix_intrinsics_, 5, false)); + + vector<Mat> rvecs(R_.size()); + vector<vector<int>> visible(R_.size()); + vector<Point3d> points3d; + vector<vector<Point2d>> points2d(R_.size()); + vector<size_t> idx; + idx.reserve(points3d_optimized_.size()); + + for (size_t i = 0; i < points3d_optimized_.size(); i++) { + + auto p = points3d_optimized_[i]; + DCHECK(!isnanl(p.x) && !isnanl(p.y) && !isnanl(p.z)); + + int count = 0; + for (size_t c = 0; c < n_cameras_; c++) { + if (isVisible(c, i) && isValid(c, i)) { count++; } + } + + if (count < 2) continue; + + points3d.push_back(p); + idx.push_back(i); + + for (size_t c = 0; c < n_cameras_; c++) { + bool good = isVisible(c, i) && isValid(c, i); + visible[c].push_back(good ? 1 : 0); + points2d[c].push_back(points2d_[c][i]); + } + } + + for (size_t i = 0; i < rvecs.size(); i++) { + calculateInverse(R_[i], t_[i], R_[i], t_[i]); + cv::Rodrigues(R_[i], rvecs[i]); + } + + DCHECK(points2d.size() == n_cameras_); + DCHECK(visible.size() == n_cameras_); + for (size_t c = 0; c < n_cameras_; c++) { + DCHECK(points3d.size() == points2d[c].size()); + DCHECK(points3d.size() == visible[c].size()); + } + + LOG(INFO) << "number of points used: " << points3d.size(); + sba.run(points3d, points2d, visible, + K_, rvecs, t_, dist_coeffs_ + ); + + for (size_t i = 0; i < rvecs.size(); i++) { + cv::Rodrigues(rvecs[i], R_[i]); + calculateInverse(R_[i], t_[i], R_[i], t_[i]); + } + + // save optimized points + { + size_t l = points3d.size(); + points3d_optimized_.clear(); + points3d_optimized_.resize(l, Point3d(NAN, NAN, NAN)); + + for (size_t i = 0; i < points3d.size(); i++) { + points3d_optimized_[idx[i]] = points3d[i]; + } + } + + err = sba.getFinalReprjError(); + LOG(INFO) << "SBA reprojection error before final BA " << sba.getInitialReprjError(); + LOG(INFO) << "SBA reprojection error after final BA " << err; + } + + for (size_t c_from = 0; c_from < n_cameras_; c_from++) { + if (c_from == reference_camera_) continue; + Mat R, t; + calculateInverse(R_[c_from], t_[c_from], R, t); + LOG(INFO) << "Error (RMS) after BA, cameras " << reference_camera_ << " and " << c_from << ": " + << getReprojectionErrorOptimized(c_from, K_[c_from], R, t); + + } + + is_calibrated_ = true; + return err; +} + +void MultiCameraCalibrationNew::projectPointsOriginal(size_t camera_src, size_t camera_dst, size_t idx, vector<Point2d> &points) { + +} + +void MultiCameraCalibrationNew::projectPointsOptimized(size_t camera_dst, size_t idx, vector<Point2d> &points) { + // TODO: indexing does not match input (points may be skipped in loadInput()) + + points.clear(); + size_t i = target_.n_points * idx; + + if (!isValid(i)) return; + + Point3d p1(points3d_optimized_[i]); + Point3d p2(points3d_optimized_[i + 1]); + + if (!std::isfinite(p1.x) || !std::isfinite(p2.x)) { + // DEBUG: should not happen + LOG(ERROR) << "Bad point! (no valid triangulation)"; + return; + } + + Mat R, tvec, rvec; + calculateTransform(R_[reference_camera_], t_[reference_camera_], R_[camera_dst], t_[camera_dst], R, tvec); + + cv::Rodrigues(R, rvec); + cv::projectPoints( vector<Point3d> { p1, p2 }, + rvec, tvec, K_[camera_dst], dist_coeffs_[camera_dst], points); +} + +void MultiCameraCalibrationNew::getCalibration(vector<Mat> &R, vector<Mat> &t) { + DCHECK(is_calibrated_); + R.resize(n_cameras_); + t.resize(n_cameras_); + + for (size_t i = 0; i < n_cameras_; i++) { + R_[i].copyTo(R[i]); + t_[i].copyTo(t[i]); + } +} \ No newline at end of file diff --git a/applications/calibration-multi/src/multicalibrate.hpp b/applications/calibration-multi/src/multicalibrate.hpp new file mode 100644 index 0000000000000000000000000000000000000000..0168e107d69730e72bcd60790e550c19ca16439a --- /dev/null +++ b/applications/calibration-multi/src/multicalibrate.hpp @@ -0,0 +1,155 @@ +#pragma once + +#include <opencv2/core.hpp> + +#include "visibility.hpp" +#include "util.hpp" + +using cv::Mat; +using cv::Size; +using cv::Point2d; +using cv::Point3d; +using cv::Vec4d; +using cv::Scalar; + +using std::vector; +using std::pair; + +class CalibrationTarget { +public: + CalibrationTarget(double length) : + n_points(2), + calibration_bar_length_(length) + {} + /* @brief Estimate scale factor. + * @param 3D points (can pass n views) + */ + double estimateScale(vector<Point3d> points3d); + size_t n_points; + +private: + double calibration_bar_length_; +}; + +class MultiCameraCalibrationNew { +public: + MultiCameraCalibrationNew( size_t n_cameras, size_t reference_camera, + Size resolution, CalibrationTarget target, + int fix_intrinsics=1); + + void setCameraParameters(size_t idx, const Mat &K, const Mat &distCoeffs); + void setCameraParameters(size_t idx, const Mat &K); + + void addPoints(vector<vector<Point2d>> points2d, vector<int> visibility); + + size_t getViewsCount(); + size_t getCamerasCount() { return n_cameras_; } + size_t getOptimalReferenceCamera(); + + size_t getMinVisibility() { return visibility_graph_.getMinVisibility(); } + size_t getViewsCount(size_t camera) { return visibility_graph_.getViewsCount(camera); } + + void setFixIntrinsic(int value) { fix_intrinsics_ = (value == 1 ? 5 : 0); } + + void loadInput(const std::string &filename, const vector<size_t> &cameras = {}); + + void saveInput(cv::FileStorage &fs); + void saveInput(const std::string &filename); + + Mat getCameraMat(size_t idx); + Mat getCameraMatNormalized(size_t idx, double scale_x = 1.0, double scale_y = 1.0); + + Mat getDistCoeffs(size_t idx); + + double calibrateAll(int reference_camera = -1); + double getReprojectionError(); + void getCalibration(vector<Mat> &R, vector<Mat> &t); + + void projectPointsOriginal(size_t camera_src, size_t camera_dst, size_t idx, vector<Point2d> &points); + void projectPointsOptimized(size_t camera_dst, size_t idx, vector<Point2d> &points); + +protected: + bool isVisible(size_t camera, size_t idx); + bool isValid(size_t camera, size_t idx); + bool isValid(size_t idx); + + Point3d getPoint3D(size_t camera, size_t i); + + vector<Point2d> getPoints(size_t camera, size_t idx); + vector<vector<Point2d>> getAllPoints(size_t camera, vector<size_t> idx); + + void getVisiblePoints( vector<size_t> cameras, + vector<vector<Point2d>> &points, + vector<size_t> &idx); + + size_t getVisiblePointsCount(vector<size_t> cameras) { + // TODO: for pairs can use visibility graph adjacency matrix + vector<vector<Point2d>> points2d; + vector<size_t> idx; + getVisiblePoints(cameras, points2d, idx); + return idx.size(); + } + + size_t getTotalPointsCount() { + return points2d_[0].size(); + } + + vector<Point3d> getPoints3D(size_t idx); + + /* @brief Find points which are visible on all cameras. Returns + * corresponding indices in idx vector. + */ + void getVisiblePoints3D(vector<size_t> cameras, + vector<vector<Point3d>> &points, + vector<size_t> &idx); + + /* @brief Update 3D points with new values. If no earlier data, new data + * is used as is, otherwise calculates average. + */ + void updatePoints3D(size_t camera, Point3d new_point, size_t idx, const Mat &R, const Mat &t); + void updatePoints3D(size_t camera, vector<Point3d> new_points, vector<size_t> idx, const Mat &R, const Mat &t); + + /* @brief Calculates 3D points that are not visible in reference camera + * from transformations in visible cameras. + */ + void calculateMissingPoints3D(); + + void getTransformation(size_t camera_from, size_t camera_to, Mat &R, Mat &T); + double calibratePair(size_t camera_from, size_t camera_to, Mat &R, Mat &T); + + /* @brief Calculate reprojection error of visible points (triangulation) */ + double getReprojectionError(size_t c_from, size_t c_to, const Mat &K, const Mat &R, const Mat &T); + + /* @brief Calculate reprojection error of visible points (optimized/averaged points) */ + double getReprojectionErrorOptimized(size_t c_from, const Mat &K, const Mat &R, const Mat &T); + + /* @brief Remove old calibration data calculated by calibrateAll */ + void reset(); + +private: + CalibrationTarget target_; + Visibility visibility_graph_; + + bool is_calibrated_; + size_t n_cameras_; + size_t reference_camera_; + size_t min_visible_points_; + int fix_intrinsics_; + + Size resolution_; + vector<Mat> K_; + vector<Mat> dist_coeffs_; + vector<Mat> R_; + vector<Mat> t_; + + vector<Point3d> points3d_optimized_; + vector<vector<Point3d>> points3d_; + vector<vector<Point2d>> points2d_; + vector<vector<int>> visible_; + vector<vector<int>> inlier_; // "inlier" + vector<vector<double>> weights_; + + int fm_method_; + double fm_ransac_threshold_; + double fm_confidence_; +}; diff --git a/applications/calibration-multi/src/util.cpp b/applications/calibration-multi/src/util.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0019516e4027472d2be733899bde75a50a94dd0f --- /dev/null +++ b/applications/calibration-multi/src/util.cpp @@ -0,0 +1,174 @@ +#include "util.hpp" + +#include <loguru.hpp> + +#include <opencv2/core.hpp> +#include <opencv2/calib3d.hpp> +#include <opencv2/imgproc.hpp> +#include <opencv2/aruco.hpp> + +using std::vector; + +using cv::Mat; +using cv::Point2i; +using cv::Point2d; +using cv::Point3d; +using cv::Size; +using cv::Scalar; + +/* @brief Visualize epipolar lines for given points in the other image. + * @param Points in image + * @param Corresponding image where to draw the lines + * @param Fundamental matrix + * @param Line color + * @param Which image (1 or 2), see OpenCV's computeCorrespondEpilines() + */ +void drawEpipolarLines(vector<Point2d> const &points, Mat &img, Mat const &F, Scalar color, int image) { + Mat lines; + cv::computeCorrespondEpilines(points, image, F, lines); + + for (int i = 0; i < lines.rows; i++) { + cv::Vec3f l = lines.at<cv::Vec3f>(i); + float a = l[0]; + float b = l[1]; + float c = l[2]; + float x0, y0, x1, y1; + x0 = 0; + y0 = (-c -a * x0) / b; + x1 = img.cols; + y1 = (-c -a * x1) / b; + cv::line(img, cv::Point(x0, y0), cv::Point(x1,y1), color, 1); + } +} + +/* @breif Find calibration points. AruCo markers, two per image. + * visible parameter input/ouput + */ +int findCorrespondingPoints(vector<Mat> imgs, vector<vector<Point2d>> &points, + vector<int> &visible) { + using namespace cv; + int count = 0; + + visible.resize(imgs.size(), 1); + + points.clear(); + points.resize(imgs.size(), vector<Point2d>(2, Point2d(0.0, 0.0))); + + auto dictionary = aruco::getPredefinedDictionary(aruco::DICT_5X5_50); + vector<vector<Point2f>> corners; + vector<int> ids; + + for (size_t i = 0; i < imgs.size(); i++) { + if (visible[i] == 0) continue; + + aruco::detectMarkers(imgs[i], dictionary, corners, ids); + if (corners.size() == 2) { + Point2d center0((corners[0][0] + corners[0][1] + corners[0][2] + corners[0][3]) / 4.0); + Point2d center1((corners[1][0] + corners[1][1] + corners[1][2] + corners[1][3]) / 4.0); + if (ids[0] != 0) { std::swap(center0, center1); } + + points[i][0] = center0; points[i][1] = center1; + visible[i] = 1; + + count++; + } + else { + visible[i] = 0; + } + } + + return count; +} + +/* @brief Find AruCo marker centers. + * @param (input) image + * @param (output) found centers + * @param (output) marker IDs + */ +void findMarkerCenters(Mat &img, vector<Point2d> &points, vector<int> &ids, int dict) { + using namespace cv; + + points.clear(); + + auto dictionary = aruco::getPredefinedDictionary(dict); + vector<vector<Point2f>> corners; + + aruco::detectMarkers(img, dictionary, corners, ids); + for (size_t j = 0; j < corners.size(); j++) { + Point2f center((corners[j][0] + corners[j][1] + corners[j][2] + corners[j][3]) / 4.0); + points.push_back(center); + } +} + +/* OpenCV's recoverPose() expects both cameras to have identical intrinsic + * parameters. + */ +int recoverPose(Mat &E, vector<Point2d> &_points1, vector<Point2d> &_points2, + Mat &_cameraMatrix1, Mat &_cameraMatrix2, + Mat &_R, Mat &_t, double distanceThresh, + Mat &triangulatedPoints) { + + Mat points1, points2, cameraMatrix1, cameraMatrix2, cameraMatrix; + + Mat(_points1.size(), 2, CV_64FC1, _points1.data()).convertTo(points1, CV_64F); + Mat(_points2.size(), 2, CV_64FC1, _points2.data()).convertTo(points2, CV_64F); + _cameraMatrix1.convertTo(cameraMatrix1, CV_64F); + _cameraMatrix2.convertTo(cameraMatrix2, CV_64F); + cameraMatrix = Mat::eye(Size(3, 3), CV_64FC1); + + double fx1 = cameraMatrix1.at<double>(0,0); + double fy1 = cameraMatrix1.at<double>(1,1); + double cx1 = cameraMatrix1.at<double>(0,2); + double cy1 = cameraMatrix1.at<double>(1,2); + + double fx2 = cameraMatrix2.at<double>(0,0); + double fy2 = cameraMatrix2.at<double>(1,1); + double cx2 = cameraMatrix2.at<double>(0,2); + double cy2 = cameraMatrix2.at<double>(1,2); + + points1.col(0) = (points1.col(0) - cx1) / fx1; + points1.col(1) = (points1.col(1) - cy1) / fy1; + + points2.col(0) = (points2.col(0) - cx2) / fx2; + points2.col(1) = (points2.col(1) - cy2) / fy2; + + // TODO mask + // cameraMatrix = I (for details, see OpenCV's recoverPose() source code) + // modules/calib3d/src/five-point.cpp (461) + // + // https://github.com/opencv/opencv/blob/371bba8f54560b374fbcd47e7e02f015ac4969ad/modules/calib3d/src/five-point.cpp#L461 + + return cv::recoverPose(E, points1, points2, cameraMatrix, _R, _t, distanceThresh, cv::noArray(), triangulatedPoints); +} + +/* @brief Calculate RMS reprojection error + * @param 3D points + * @param Expected 2D points + * @param Camera matrix + * @param Rotation matrix/vector + * @param Translation vector + */ +double reprojectionError( const vector<Point3d> &points3d, const vector<Point2d> &points2d, + const Mat &K, const Mat &rvec, const Mat &tvec) { + + DCHECK(points3d.size() == points2d.size()); + + Mat _rvec; + if (rvec.size() == Size(3, 3)) { cv::Rodrigues(rvec, _rvec); } + else { _rvec = rvec; } + + DCHECK(_rvec.size() == Size(1, 3) || _rvec.size() == Size(3, 1)); + + vector<Point2d> points_reprojected; + cv::projectPoints(points3d, _rvec, tvec, K, cv::noArray(), points_reprojected); + + int n_points = points2d.size(); + double err = 0.0; + + for (int i = 0; i < n_points; i++) { + Point2d a = points2d[i] - points_reprojected[i]; + err += a.x * a.x + a.y * a.y; + } + + return sqrt(err / n_points); +} \ No newline at end of file diff --git a/applications/calibration-multi/src/util.hpp b/applications/calibration-multi/src/util.hpp new file mode 100644 index 0000000000000000000000000000000000000000..7c2b5702feb1b518fd4662560f349c2aa4de9fed --- /dev/null +++ b/applications/calibration-multi/src/util.hpp @@ -0,0 +1,110 @@ +#pragma once + +#include <loguru.hpp> + +#include <opencv2/core.hpp> +#include <opencv2/aruco.hpp> + +using std::vector; + +using cv::Mat; +using cv::Point2i; +using cv::Point2d; +using cv::Point3d; +using cv::Size; +using cv::Scalar; + +/* @brief Visualize epipolar lines for given points in the other image. + * @param Points in image + * @param Corresponding image where to draw the lines + * @param Fundamental matrix + * @param Line color + * @param Which image (1 or 2), see OpenCV's computeCorrespondEpilines() + */ +void drawEpipolarLines(vector<Point2d> const &points, Mat &img, Mat const &F, Scalar color, int image=1); + + +/* @breif Find calibration points. AruCo markers, two per image. + */ +int findCorrespondingPoints(vector<Mat> imgs, vector<vector<Point2d>> &points, + vector<int> &visible); + +/* @brief Find AruCo marker centers. + * @param (input) image + * @param (output) found centers + * @param (output) marker IDs + */ +void findMarkerCenters(Mat &img, vector<Point2d> &points, vector<int> &ids, int dict=cv::aruco::DICT_4X4_50); + +/* OpenCV's recoverPose() expects both cameras to have identical intrinsic + * parameters. + * + * https://github.com/opencv/opencv/blob/371bba8f54560b374fbcd47e7e02f015ac4969ad/modules/calib3d/src/five-point.cpp#L461 + */ +int recoverPose(Mat &E, vector<Point2d> &_points1, vector<Point2d> &_points2, + Mat &_cameraMatrix1, Mat &_cameraMatrix2, + Mat &_R, Mat &_t, double distanceThresh, + Mat &triangulatedPoints); + +/* @brief Calculate RMS reprojection error + * @param 3D points + * @param Expected 2D points + * @param Camera matrix + * @param Rotation matrix/vector + * @param Translation vector + */ +double reprojectionError( const vector<Point3d> &points3d, const vector<Point2d> &points2d, + const Mat &K, const Mat &rvec, const Mat &tvec); + +inline double euclideanDistance(Point3d a, Point3d b) { + Point3d c = a - b; + return sqrt(c.x*c.x + c.y*c.y + c.z*c.z); +} + +inline Point3d transformPoint(Point3d p, Mat R, Mat t) { + DCHECK(R.size() == Size(3, 3)); + DCHECK(t.size() == Size(1, 3)); + return Point3d(Mat(R * Mat(p) + t)); +} + +inline Point3d inverseTransformPoint(Point3d p, Mat R, Mat t) { + DCHECK(R.size() == Size(3, 3)); + DCHECK(t.size() == Size(1, 3)); + return Point3d(Mat(R.t() * (Mat(p) - t))); +} + +inline Mat getMat4x4(const Mat &R, const Mat &t) { + DCHECK(R.size() == Size(3, 3)); + DCHECK(t.size() == Size(1, 3)); + Mat M = Mat::eye(Size(4, 4), CV_64FC1); + R.copyTo(M(cv::Rect(0, 0, 3, 3))); + t.copyTo(M(cv::Rect(3, 0, 1, 3))); + return M; +} + +inline void getRT(const Mat RT, Mat &R, Mat &t) { + R = RT(cv::Rect(0, 0, 3, 3)); + t = RT(cv::Rect(3, 0, 1, 3)); +} + +// calculate transforms from (R1, t1) to (R2, t2), where parameters +// (R1, t1) and (R2, t2) map to same (target) coordinate system + +inline void calculateTransform(const Mat &R1, const Mat &T1, const Mat &R2, const Mat &T2, Mat &R, Mat &tvec, Mat &M) { + Mat M_src = getMat4x4(R1, T1); + Mat M_dst = getMat4x4(R2, T2); + M = M_dst.inv() * M_src; + R = M(cv::Rect(0, 0, 3, 3)); + tvec = M(cv::Rect(3, 0, 1, 3)); +} + +inline void calculateTransform(const Mat &R1, const Mat &T1, const Mat &R2, const Mat &T2,Mat &R, Mat &tvec) { + Mat M; + calculateTransform(R1, T1, R2, T2, R, tvec, M); +} + +inline void calculateInverse(const Mat &R2, const Mat &T2, Mat &R, Mat &T) { + Mat R1 = Mat::eye(Size(3, 3), CV_64FC1); + Mat T1(Size(1, 3), CV_64FC1, Scalar(0.0)); + calculateTransform(R1, T1, R2, T2, R, T); +} \ No newline at end of file diff --git a/applications/calibration-multi/src/visibility.cpp b/applications/calibration-multi/src/visibility.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a4c16165b47decadfa9ca18ccf641a32b1cb16c1 --- /dev/null +++ b/applications/calibration-multi/src/visibility.cpp @@ -0,0 +1,153 @@ +#include <numeric> +#include <loguru.hpp> +#include <queue> + +#include "visibility.hpp" + +using cv::Mat; +using cv::Scalar; +using cv::Size; +using std::vector; +using std::pair; +using std::make_pair; + +Visibility::Visibility(int n_cameras) : n_cameras_(n_cameras) { + visibility_ = Mat(Size(n_cameras, n_cameras), CV_32SC1, Scalar(0)); + count_ = vector(n_cameras, 0); +} + +void Visibility::update(vector<int> &visible) { + DCHECK(visible.size() == (size_t) n_cameras_); + + for (int i = 0; i < n_cameras_; i++) { + if (visible[i] == 0) continue; + count_[i]++; + + for (int j = 0; j < n_cameras_; j++) { + if (i == j) continue; + if (visible[j] == 1) visibility_.at<int>(i, j)++; + } + } +} + +int Visibility::getOptimalCamera() { + // most visible on average + int best_i; + double best_score = -INFINITY; + for (int i = 0; i < visibility_.rows; i++) { + double score = 0.0; + for (int x = 0; x < visibility_.cols; x++) { + score += visibility_.at<int>(i, x); + } + score = score / (double) visibility_.cols; + if (score > best_score) { + best_i = i; + best_score = score; + } + } + + return best_i; +} + +void Visibility::deleteEdge(int camera1, int camera2) +{ + visibility_.at<int>(camera1, camera2) = 0; + visibility_.at<int>(camera2, camera1) = 0; +} + +int Visibility::getMinVisibility() { + int min_i; + int min_count = INT_MAX; + + for (int i = 0; i < n_cameras_; i++) { + if (count_[i] < min_count) { + min_i = i; + min_count = count_[i]; + } + } + + return min_count; +} + +int Visibility::getViewsCount(int camera) { + return count_[camera]; +} + +vector<vector<pair<int, int>>> Visibility::findShortestPaths(int reference) { + DCHECK(reference < n_cameras_); + + vector<vector<pair<int, int>>> res(n_cameras_); + for (int i = 0; i < n_cameras_; i++) { + res[i] = findShortestPath(i, reference); + } + + return res; +} + +vector<pair<int, int>> Visibility::findShortestPath(int from, int to) { + if (from == to) return vector<pair<int, int>>(); + + vector<bool> visited(n_cameras_, false); + vector<double> distances(n_cameras_, INFINITY); + vector<int> previous(n_cameras_, -1); + + distances[from] = 0.0; + + auto cmp = [](pair<int, double> u, pair<int, double> v) { return u.second > v.second; }; + std::priority_queue<pair<int, double>, vector<pair<int, double>>, decltype(cmp)> pq(cmp); + + pq.push(make_pair(from, distances[from])); + + while(!pq.empty()) { + pair<int, double> current = pq.top(); + pq.pop(); + + int current_id = current.first; + double current_distance = distances[current_id]; + + visited[current_id] = true; + + for (int i = 0; i < n_cameras_; i++) { + int count = visibility_.at<int>(current_id, i); + if (count == 0) continue; // not connected + + double distance = 1.0 / (double) count; + double new_distance = current_distance + distance; + + if (distances[i] > new_distance) { + distances[i] = new_distance; + previous[i] = current_id; + + pq.push(make_pair(i, distances[i])); + } + } + } + + vector<pair<int, int>> res; + int prev = previous[to]; + int current = to; + + do { + res.push_back(make_pair(current, prev)); + current = prev; + prev = previous[prev]; + } + while(prev != -1); + + std::reverse(res.begin(), res.end()); + return res; +} + +vector<int> Visibility::getClosestCameras(int c) { + + // initialize original index locations + vector<int> idx(n_cameras_); + iota(idx.begin(), idx.end(), 0); + int* views = visibility_.ptr<int>(c); + + // sort indexes based on comparing values in v + sort(idx.begin(), idx.end(), + [views](size_t i1, size_t i2) {return views[i1] < views[i2];}); + + return idx; +} \ No newline at end of file diff --git a/applications/calibration-multi/src/visibility.hpp b/applications/calibration-multi/src/visibility.hpp new file mode 100644 index 0000000000000000000000000000000000000000..3521435dd62e2cd6eeea417f926b02f8049eb560 --- /dev/null +++ b/applications/calibration-multi/src/visibility.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include <opencv2/core.hpp> + +using cv::Mat; +using std::vector; +using std::pair; + +class Visibility { +public: + Visibility(int n_cameras); + + /* @breif Update visibility graph. + * @param Which cameras see the feature(s) in this iteration + */ + void update(vector<int> &visible); + + /* @brief For all cameras, find shortest (optimal) paths to reference + * camera + * @param Id of reference camera + * + * Calculates shortest path in weighted graph using Dijstra's + * algorithm. Weights are inverse of views between cameras (nodes) + * + * @todo Add constant weight for each edge (prefer less edges) + */ + vector<vector<pair<int, int>>> findShortestPaths(int reference); + + vector<int> getClosestCameras(int c); + void deleteEdge(int camera1, int camera2); + int getOptimalCamera(); + int getMinVisibility(); + int getViewsCount(int camera); + +protected: + /* @brief Find shortest path between nodes + * @param Source node id + * @param Destination node id + */ + vector<pair<int, int>> findShortestPath(int from, int to); + +private: + int n_cameras_; // @brief number of cameras + Mat visibility_; // @brief adjacency matrix + vector<int> count_; +}; diff --git a/applications/calibration/src/common.cpp b/applications/calibration/src/common.cpp index 49967754dc0dd4f6665db7dae9bd9c5e965e35d3..8a678e4b9ba808dccea146c2ce4c8c1c6942a7f0 100644 --- a/applications/calibration/src/common.cpp +++ b/applications/calibration/src/common.cpp @@ -62,20 +62,24 @@ bool saveExtrinsics(const string &ofile, Mat &R, Mat &T, Mat &R1, Mat &R2, Mat & return false; } -bool saveIntrinsics(const string &ofile, const vector<Mat> &M, const vector<Mat>& D) { +bool saveIntrinsics(const string &ofile, const vector<Mat> &M, const vector<Mat>& D, const Size &size) +{ cv::FileStorage fs(ofile, cv::FileStorage::WRITE); - if (fs.isOpened()) { + if (fs.isOpened()) + { + fs << "resolution" << size; fs << "K" << M << "D" << D; fs.release(); return true; } - else { + else + { LOG(ERROR) << "Error: can not save the intrinsic parameters to '" << ofile << "'"; } return false; } -bool loadIntrinsics(const string &ifile, vector<Mat> &K1, vector<Mat> &D1) { +bool loadIntrinsics(const string &ifile, vector<Mat> &K1, vector<Mat> &D1, Size &size) { using namespace cv; FileStorage fs; @@ -89,9 +93,10 @@ bool loadIntrinsics(const string &ifile, vector<Mat> &K1, vector<Mat> &D1) { LOG(INFO) << "Intrinsics from: " << ifile; - fs["M"] >> K1; + fs["resolution"] >> size; + fs["K"] >> K1; fs["D"] >> D1; - + return true; } @@ -211,6 +216,23 @@ bool CalibrationChessboard::findPoints(Mat &img, vector<Vec2f> &points) { return cv::findChessboardCornersSB(img, pattern_size_, points, chessboard_flags_); } + +void CalibrationChessboard::drawCorners(Mat &img, const vector<Vec2f> &points) { + using cv::Point2i; + vector<Point2i> corners(4); + corners[1] = Point2i(points[0]); + corners[0] = Point2i(points[pattern_size_.width - 1]); + corners[2] = Point2i(points[pattern_size_.width * (pattern_size_.height - 1)]); + corners[3] = Point2i(points.back()); + + cv::Scalar color = cv::Scalar(200, 200, 200); + + for (int i = 0; i <= 4; i++) + { + cv::line(img, corners[i % 4], corners[(i + 1) % 4], color, 2); + } +} + void CalibrationChessboard::drawPoints(Mat &img, const vector<Vec2f> &points) { cv::drawChessboardCorners(img, pattern_size_, points, true); } diff --git a/applications/calibration/src/common.hpp b/applications/calibration/src/common.hpp index 274698b32b51ae9b741b94e25b492ab059637e37..c84f25d249b49eee094e3e898090ffb9ff129f03 100644 --- a/applications/calibration/src/common.hpp +++ b/applications/calibration/src/common.hpp @@ -15,8 +15,8 @@ int getOptionInt(const std::map<std::string, std::string> &options, const std::s double getOptionDouble(const std::map<std::string, std::string> &options, const std::string &opt, double default_value); std::string getOptionString(const std::map<std::string, std::string> &options, const std::string &opt, std::string default_value); -bool loadIntrinsics(const std::string &ifile, std::vector<cv::Mat> &K, std::vector<cv::Mat> &D); -bool saveIntrinsics(const std::string &ofile, const std::vector<cv::Mat> &K, const std::vector<cv::Mat> &D); +bool loadIntrinsics(const std::string &ifile, std::vector<cv::Mat> &K, std::vector<cv::Mat> &D, cv::Size &size); +bool saveIntrinsics(const std::string &ofile, const std::vector<cv::Mat> &K, const std::vector<cv::Mat> &D, const cv::Size &size); // TODO loadExtrinsics() bool saveExtrinsics(const std::string &ofile, cv::Mat &R, cv::Mat &T, cv::Mat &R1, cv::Mat &R2, cv::Mat &P1, cv::Mat &P2, cv::Mat &Q); @@ -90,10 +90,11 @@ public: */ class CalibrationChessboard : Calibration { public: - CalibrationChessboard(const std::map<std::string, std::string> &opt); + explicit CalibrationChessboard(const std::map<std::string, std::string> &opt); void objectPoints(std::vector<cv::Vec3f> &out); bool findPoints(cv::Mat &in, std::vector<cv::Vec2f> &out); void drawPoints(cv::Mat &img, const std::vector<cv::Vec2f> &points); + void drawCorners(cv::Mat &img, const std::vector<cv::Vec2f> &points); private: int chessboard_flags_ = 0; diff --git a/applications/calibration/src/lens.cpp b/applications/calibration/src/lens.cpp index 924787a740ce46e0d4caf31c6630f38d15a02244..b69b26cc6e4cfec9dab04d337d75b21721d2fbae 100644 --- a/applications/calibration/src/lens.cpp +++ b/applications/calibration/src/lens.cpp @@ -14,6 +14,8 @@ #include <opencv2/highgui.hpp> #include <vector> +#include <atomic> +#include <thread> using std::map; using std::string; @@ -30,34 +32,73 @@ void ftl::calibration::intrinsic(map<string, string> &opt) { LOG(INFO) << "Begin intrinsic calibration"; // TODO PARAMETERS TO CONFIG FILE - const Size image_size = Size( getOptionInt(opt, "width", 1280), - getOptionInt(opt, "height", 720)); + const Size image_size = Size( getOptionInt(opt, "width", 1920), + getOptionInt(opt, "height", 1080)); const int n_cameras = getOptionInt(opt, "n_cameras", 2); - const int iter = getOptionInt(opt, "iter", 60); - const int delay = getOptionInt(opt, "delay", 750); + const int iter = getOptionInt(opt, "iter", 40); + const int delay = getOptionInt(opt, "delay", 1000); const double aperture_width = getOptionDouble(opt, "aperture_width", 6.2); const double aperture_height = getOptionDouble(opt, "aperture_height", 4.6); const string filename_intrinsics = getOptionString(opt, "profile", FTL_LOCAL_CONFIG_ROOT "/intrinsics.yml"); CalibrationChessboard calib(opt); + bool use_guess = getOptionInt(opt, "use_guess", 1); + //bool use_guess_distortion = getOptionInt(opt, "use_guess_distortion", 0); LOG(INFO) << "Intrinsic calibration parameters"; - LOG(INFO) << " profile: " << filename_intrinsics; - LOG(INFO) << " n_cameras: " << n_cameras; - LOG(INFO) << " width: " << image_size.width; - LOG(INFO) << " height: " << image_size.height; - LOG(INFO) << " iter: " << iter; - LOG(INFO) << " delay: " << delay; - LOG(INFO) << " aperture_width: " << aperture_width; - LOG(INFO) << " aperture_height: " << aperture_height; + LOG(INFO) << " profile: " << filename_intrinsics; + LOG(INFO) << " n_cameras: " << n_cameras; + LOG(INFO) << " width: " << image_size.width; + LOG(INFO) << " height: " << image_size.height; + LOG(INFO) << " iter: " << iter; + LOG(INFO) << " delay: " << delay; + LOG(INFO) << " aperture_width: " << aperture_width; + LOG(INFO) << " aperture_height: " << aperture_height; + LOG(INFO) << " use_guess: " << use_guess; + //LOG(INFO) << " use_guess_distortion: " << use_guess_distortion; + LOG(INFO) << "-----------------------------------"; - int calibrate_flags = cv::CALIB_ZERO_TANGENT_DIST | cv::CALIB_FIX_ASPECT_RATIO; + int calibrate_flags = cv::CALIB_ZERO_TANGENT_DIST | cv::CALIB_FIX_ASPECT_RATIO; + if (use_guess) { calibrate_flags |= cv::CALIB_USE_INTRINSIC_GUESS; } + // cv::CALIB_FIX_PRINCIPAL_POINT; // PARAMETERS + + vector<Mat> camera_matrix(n_cameras), dist_coeffs(n_cameras); + + for (Mat &d : dist_coeffs) + { + d = Mat(Size(5, 1), CV_64FC1, cv::Scalar(0.0)); + } + + if (use_guess) + { + camera_matrix.clear(); + vector<Mat> tmp; + Size tmp_size; + + loadIntrinsics(filename_intrinsics, camera_matrix, tmp, tmp_size); + CHECK(camera_matrix.size() == n_cameras); // (camera_matrix.size() == dist_coeffs.size()) + if ((tmp_size != image_size) && (!tmp_size.empty())) + { + Mat scale = Mat::eye(Size(3, 3), CV_64FC1); + scale.at<double>(0, 0) = ((double) image_size.width) / ((double) tmp_size.width); + scale.at<double>(1, 1) = ((double) image_size.height) / ((double) tmp_size.height); + for (Mat &K : camera_matrix) { K = scale * K; } + } + + if (tmp_size.empty()) + { + use_guess = false; + LOG(FATAL) << "No valid calibration found."; + } + } + vector<cv::VideoCapture> cameras; cameras.reserve(n_cameras); for (int c = 0; c < n_cameras; c++) { cameras.emplace_back(c); } - for (auto &camera : cameras) { + for (auto &camera : cameras) + { if (!camera.isOpened()) { LOG(ERROR) << "Could not open camera device"; return; @@ -68,43 +109,102 @@ void ftl::calibration::intrinsic(map<string, string> &opt) { vector<vector<vector<Vec2f>>> image_points(n_cameras); vector<vector<vector<Vec3f>>> object_points(n_cameras); + vector<Mat> img(n_cameras); + vector<Mat> img_display(n_cameras); vector<int> count(n_cameras, 0); + Mat display(Size(image_size.width * n_cameras, image_size.height), CV_8UC3); - while (iter > *std::min_element(count.begin(), count.end())) { + for (int c = 0; c < n_cameras; c++) + { + img_display[c] = Mat(display, cv::Rect(c * image_size.width, 0, image_size.width, image_size.height)); + } - for (auto &camera : cameras) { camera.grab(); } + std::mutex m; + std::atomic<bool> ready = false; + auto capture = std::thread([n_cameras, delay, &m, &ready, &count, &calib, &img, &image_points, &object_points]() + { + vector<Mat> tmp(n_cameras); + while(true) + { + if (!ready) + { + std::this_thread::sleep_for(std::chrono::milliseconds(delay)); + continue; + } - for (int c = 0; c < n_cameras; c++) { - vector<Vec2f> points; - cameras[c].retrieve(img[c]); - - if (calib.findPoints(img[c], points)) { - calib.drawPoints(img[c], points); - count[c]++; + m.lock(); + ready = false; + for (int c = 0; c < n_cameras; c++) + { + img[c].copyTo(tmp[c]); + } + m.unlock(); + + for (int c = 0; c < n_cameras; c++) + { + vector<Vec2f> points; + if (calib.findPoints(tmp[c], points)) + { + count[c]++; + } + else { continue; } + + vector<Vec3f> points_ref; + calib.objectPoints(points_ref); + Mat camera_matrix, dist_coeffs; + image_points[c].push_back(points); + object_points[c].push_back(points_ref); } - else { continue; } - - vector<Vec3f> points_ref; - calib.objectPoints(points_ref); - Mat camera_matrix, dist_coeffs; - vector<Mat> rvecs, tvecs; - - image_points[c].push_back(points); - object_points[c].push_back(points_ref); + std::this_thread::sleep_for(std::chrono::milliseconds(delay)); + } + }); + + while (iter > *std::min_element(count.begin(), count.end())) + { + if (m.try_lock()) + { + for (auto &camera : cameras) { camera.grab(); } + + for (int c = 0; c < n_cameras; c++) + { + cameras[c].retrieve(img[c]); + } + + ready = true; + m.unlock(); } - for (int c = 0; c < n_cameras; c++) { - cv::imshow("Camera " + std::to_string(c), img[c]); + for (int c = 0; c < n_cameras; c++) + { + img[c].copyTo(img_display[c]); + m.lock(); + + if (image_points[c].size() > 0) + { + + for (auto &points : image_points[c]) + { + calib.drawCorners(img_display[c], points); + } + + calib.drawPoints(img_display[c], image_points[c].back()); + } + + m.unlock(); } - cv::waitKey(delay); + cv::namedWindow("Cameras", cv::WINDOW_KEEPRATIO | cv::WINDOW_NORMAL); + cv::imshow("Cameras", display); + + cv::waitKey(10); } - - vector<Mat> camera_matrix(n_cameras), dist_coeffs(n_cameras); - for (int c = 0; c < n_cameras; c++) { + cv::destroyAllWindows(); + + for (int c = 0; c < n_cameras; c++) + { LOG(INFO) << "Calculating intrinsic paramters for camera " << std::to_string(c); vector<Mat> rvecs, tvecs; @@ -135,7 +235,7 @@ void ftl::calibration::intrinsic(map<string, string> &opt) { LOG(INFO) << ""; } - saveIntrinsics(filename_intrinsics, camera_matrix, dist_coeffs); + saveIntrinsics(filename_intrinsics, camera_matrix, dist_coeffs, image_size); LOG(INFO) << "intrinsic paramaters saved to: " << filename_intrinsics; vector<Mat> map1(n_cameras), map2(n_cameras); diff --git a/applications/calibration/src/stereo.cpp b/applications/calibration/src/stereo.cpp index c009d6dd0311c27bc952e7daba18afc76e30288f..964cf815300971cf31130e78fae94e1c21a172ad 100644 --- a/applications/calibration/src/stereo.cpp +++ b/applications/calibration/src/stereo.cpp @@ -68,7 +68,7 @@ void ftl::calibration::stereo(map<string, string> &opt) { int stereocalibrate_flags = cv::CALIB_FIX_INTRINSIC | cv::CALIB_FIX_PRINCIPAL_POINT | cv::CALIB_FIX_ASPECT_RATIO | - cv::CALIB_ZERO_TANGENT_DIST | cv::CALIB_SAME_FOCAL_LENGTH | cv::CALIB_RATIONAL_MODEL | + cv::CALIB_ZERO_TANGENT_DIST | cv::CALIB_SAME_FOCAL_LENGTH | cv::CALIB_FIX_K3 | cv::CALIB_FIX_K4 | cv::CALIB_FIX_K5; vector<cv::VideoCapture> cameras { cv::VideoCapture(0), cv::VideoCapture(1) }; @@ -93,11 +93,17 @@ void ftl::calibration::stereo(map<string, string> &opt) { vector<Mat> dist_coeffs(2); vector<Mat> camera_matrices(2); - - if (!loadIntrinsics(filename_intrinsics, camera_matrices, dist_coeffs)) { + Size intrinsic_resolution; + if (!loadIntrinsics(filename_intrinsics, camera_matrices, dist_coeffs, intrinsic_resolution)) + { LOG(FATAL) << "Failed to load intrinsic camera parameters from file."; } + if (intrinsic_resolution != image_size) + { + LOG(FATAL) << "Intrinsic resolution is not same as input resolution (TODO)"; + } + Mat R, T, E, F, per_view_errors; // capture calibration patterns @@ -152,11 +158,11 @@ void ftl::calibration::stereo(map<string, string> &opt) { vector<Vec3f> points_ref; calib.objectPoints(points_ref); + /* doesn't seem to be very helpful (error almost always low enough) // calculate reprojection error with single pair of images // reject it if RMS reprojection error too high int flags = stereocalibrate_flags; - - // TODO move to "findPoints"-thread + double rms_iter = stereoCalibrate( vector<vector<Vec3f>> { points_ref }, vector<vector<Vec2f>> { new_points[0] }, @@ -170,7 +176,7 @@ void ftl::calibration::stereo(map<string, string> &opt) { if (rms_iter > max_error) { LOG(WARNING) << "RMS reprojection error too high, maximum allowed error: " << max_error; continue; - } + }*/ if (use_grid) { // store results in result grid @@ -225,14 +231,11 @@ void ftl::calibration::stereo(map<string, string> &opt) { Mat R1, R2, P1, P2, Q; cv::Rect validRoi[2]; - // calculate extrinsic parameters - // NOTE: Other code assumes CALIB_ZERO_DISPARITY is used (for Cy == Cx). - // Depth map map calculation disparityToDepth() could be incorrect otherwise. stereoRectify( camera_matrices[0], dist_coeffs[0], camera_matrices[1], dist_coeffs[1], image_size, R, T, R1, R2, P1, P2, Q, - cv::CALIB_ZERO_DISPARITY, alpha, image_size, + 0, alpha, image_size, &validRoi[0], &validRoi[1] ); @@ -257,21 +260,33 @@ void ftl::calibration::stereo(map<string, string> &opt) { camera.grab(); camera.retrieve(in[i]); + auto p = cv::Point2i(camera_matrices[i].at<double>(0, 2), camera_matrices[i].at<double>(1, 2)); + cv::drawMarker(in[i], p, cv::Scalar(51, 204, 51), cv::MARKER_CROSS, 40, 1); + cv::drawMarker(in[i], p, cv::Scalar(51, 204, 51), cv::MARKER_SQUARE, 25); + cv::remap(in[i], out[i], map1[i], map2[i], cv::INTER_CUBIC); - // cv::cvtColor(out[i], out_gray[i], cv::COLOR_BGR2GRAY); // draw lines for (int r = 50; r < image_size.height; r = r+50) { cv::line(out[i], cv::Point(0, r), cv::Point(image_size.width-1, r), cv::Scalar(0,0,255), 1); } + if (i == 0) { // left camera + auto p_r = cv::Point2i(-Q.at<double>(0, 3), -Q.at<double>(1, 3)); + cv::drawMarker(out[i], p_r, cv::Scalar(0, 0, 204), cv::MARKER_CROSS, 30); + cv::drawMarker(out[i], p_r, cv::Scalar(0, 0, 204), cv::MARKER_SQUARE); + } + + cv::imshow("Camera " + std::to_string(i) + " (unrectified)", in[i]); cv::imshow("Camera " + std::to_string(i) + " (rectified)", out[i]); } - + /* not useful cv::absdiff(out_gray[0], out_gray[1], diff); cv::applyColorMap(diff, diff_color, cv::COLORMAP_JET); cv::imshow("Difference", diff_color); */ } + + cv::destroyAllWindows(); } diff --git a/applications/groupview/src/main.cpp b/applications/groupview/src/main.cpp index f57337183e2a0528a5f85cbfc42b49ff32b2ebba..6b32221ef1a13ad05f9e5bb915c1186eca0aa52c 100644 --- a/applications/groupview/src/main.cpp +++ b/applications/groupview/src/main.cpp @@ -13,7 +13,12 @@ using Eigen::Matrix4d; using std::map; using std::string; +using std::vector; +using cv::Size; +using cv::Mat; +using ftl::rgbd::Channel; +// TODO: remove code duplication (function from reconstruction) static void from_json(nlohmann::json &json, map<string, Matrix4d> &transformations) { for (auto it = json.begin(); it != json.end(); ++it) { Eigen::Matrix4d m; @@ -23,6 +28,7 @@ static void from_json(nlohmann::json &json, map<string, Matrix4d> &transformatio } } +// TODO: remove code duplication (function from reconstruction) static bool loadTransformations(const string &path, map<string, Matrix4d> &data) { std::ifstream file(path); if (!file.is_open()) { @@ -36,8 +42,108 @@ static bool loadTransformations(const string &path, map<string, Matrix4d> &data) return true; } -int main(int argc, char **argv) { - auto root = ftl::configure(argc, argv, "viewer_default"); +// TODO: remove code duplication (function from calibrate-multi) +void stack(const vector<Mat> &img, Mat &out, const int rows, const int cols) { + Size size = img[0].size(); + Size size_out = Size(size.width * cols, size.height * rows); + if (size_out != out.size() || out.type() != CV_8UC3) { + out = Mat(size_out, CV_8UC3, cv::Scalar(0, 0, 0)); + } + + for (size_t i = 0; i < img.size(); i++) { + int row = i % rows; + int col = i / rows; + auto rect = cv::Rect(size.width * col, size.height * row, size.width, size.height); + img[i].copyTo(out(rect)); + } +} + +// TODO: remove code duplication (function from calibrate-multi) +void stack(const vector<Mat> &img, Mat &out) { + // TODO + int rows = 2; + int cols = (img.size() + 1) / 2; + stack(img, out, rows, cols); +} + +void modeLeftRight(ftl::Configurable *root) { + ftl::net::Universe *net = ftl::create<ftl::net::Universe>(root, "net"); + + net->start(); + net->waitConnections(); + + auto sources = ftl::createArray<ftl::rgbd::Source>(root, "sources", net); + const string path = root->value<string>("save_to", "./"); + const string file_type = root->value<string>("file_type", "jpg"); + + const size_t n_cameras = sources.size() * 2; + ftl::rgbd::Group group; + + for (auto* src : sources) { + src->setChannel(Channel::Right); + group.addSource(src); + } + + std::mutex mutex; + std::atomic<bool> new_frames = false; + vector<Mat> rgb(n_cameras), rgb_new(n_cameras); + + group.sync([&mutex, &new_frames, &rgb_new](ftl::rgbd::FrameSet &frames) { + mutex.lock(); + bool good = true; + for (size_t i = 0; i < frames.frames.size(); i ++) { + auto &chan1 = frames.frames[i].get<cv::Mat>(Channel::Colour); + auto &chan2 = frames.frames[i].get<cv::Mat>(frames.sources[i]->getChannel()); + if (chan1.empty()) good = false; + if (chan2.empty()) good = false; + if (chan1.channels() != 3) good = false; // ASSERT + if (chan2.channels() != 3) good = false; + if (!good) break; + + chan1.copyTo(rgb_new[2 * i]); + chan2.copyTo(rgb_new[2 * i + 1]); + } + + new_frames = good; + mutex.unlock(); + return true; + }); + + int idx = 0; + + Mat show; + + while (ftl::running) { + int key; + + while (!new_frames) { + for (auto src : sources) { src->grab(30); } + key = cv::waitKey(10); + } + + mutex.lock(); + rgb.swap(rgb_new); + new_frames = false; + mutex.unlock(); + + stack(rgb, show); + cv::namedWindow("Cameras", cv::WINDOW_KEEPRATIO | cv::WINDOW_NORMAL); + cv::imshow("Cameras", show); + + key = cv::waitKey(100); + // TODO: fix + if (key == 's') { + for (size_t c = 0; c < n_cameras; c++ ) { + cv::imwrite(path + "camera" + std::to_string(c) + "_" + std::to_string(idx) + "." + file_type, rgb[c]); + } + LOG(INFO) << "Saved (" << idx << ")"; + idx++; + } + if (key == 27) break; + } +} + +void modeFrame(ftl::Configurable *root, int frames=1) { ftl::net::Universe *net = ftl::create<ftl::net::Universe>(root, "net"); net->start(); @@ -62,47 +168,161 @@ int main(int argc, char **argv) { } else { s->setPose(T->second); } - s->setChannel(ftl::rgbd::kChanDepth); + s->setChannel(Channel::Depth); group.addSource(s); } - bool grab = false; + std::atomic<bool> grab = false; + std::atomic<bool> video = false; - group.sync([&grab](const ftl::rgbd::FrameSet &fs) { - LOG(INFO) << "Complete set: " << fs.timestamp; - if (grab) { - grab = false; + vector<cv::Mat> rgb(sources.size()); + vector<cv::Mat> depth(sources.size()); + MUTEX mtx; + + group.sync([&grab,&rgb,&depth,&mtx](ftl::rgbd::FrameSet &fs) { + UNIQUE_LOCK(mtx, lk); + //LOG(INFO) << "Complete set: " << fs.timestamp; + if (!ftl::running) { return false; } + + std::vector<cv::Mat> frames; + + for (size_t i=0; i<fs.sources.size(); ++i) { + auto &chan1 = fs.frames[i].get<cv::Mat>(Channel::Colour); + auto &chan2 = fs.frames[i].get<cv::Mat>(fs.sources[i]->getChannel()); + if (chan1.empty() || chan2.empty()) return true; + + frames.push_back(chan1); + } + + cv::Mat show; + + stack(frames, show); + + cv::resize(show, show, cv::Size(1280,720)); + cv::namedWindow("Cameras", cv::WINDOW_KEEPRATIO | cv::WINDOW_NORMAL); + cv::imshow("Cameras", show); + + auto key = cv::waitKey(1); + if (key == 27) ftl::running = false; + if (key == 'g') grab = true; #ifdef HAVE_LIBARCHIVE + if (grab) { + grab = false; char timestamp[18]; std::time_t t=std::time(NULL); std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t)); auto writer = ftl::rgbd::SnapshotWriter(std::string(timestamp) + ".tar.gz"); for (size_t i=0; i<fs.sources.size(); ++i) { - writer.addCameraParams(std::string("camera")+std::to_string(i), fs.sources[i]->getPose(), fs.sources[i]->parameters()); - LOG(INFO) << "SAVE: " << fs.channel1[i].cols << ", " << fs.channel2[i].type(); - writer.addCameraRGBD(std::string("camera")+std::to_string(i), fs.channel1[i], fs.channel2[i]); + auto &chan1 = fs.frames[i].get<cv::Mat>(Channel::Colour); + auto &chan2 = fs.frames[i].get<cv::Mat>(fs.sources[i]->getChannel()); + + writer.addSource(fs.sources[i]->getURI(), fs.sources[i]->parameters(), fs.sources[i]->getPose()); + //LOG(INFO) << "SAVE: " << fs.channel1[i].cols << ", " << fs.channel2[i].type(); + writer.addRGBD(i, chan1, chan2); } -#endif // HAVE_LIBARCHIVE } +#endif // HAVE_LIBARCHIVE return true; }); - int current = 0; + /*cv::Mat show; while (ftl::running) { - //std::this_thread::sleep_for(std::chrono::milliseconds(20)); for (auto s : sources) s->grab(30); - cv::Mat rgb,depth; - sources[current%sources.size()]->getFrames(rgb, depth); - if (!rgb.empty()) cv::imshow("View", rgb); - auto key = cv::waitKey(20); + for (size_t i = 0; i < sources.size(); i++) { + //do { sources[i]->getFrames(rgb[i], depth[i]); } + while(rgb[i].empty()); + } + + { + UNIQUE_LOCK(mtx, lk); + stack(rgb, show); + } + cv::resize(show, show, cv::Size(1280,720)); + cv::namedWindow("Cameras", cv::WINDOW_KEEPRATIO | cv::WINDOW_NORMAL); + cv::imshow("Cameras", show); + auto key = cv::waitKey(20); if (key == 27) break; - if (key == 'n') current++; if (key == 'g') grab = true; + }*/ +} + +void modeVideo(ftl::Configurable *root) { + + ftl::net::Universe *net = ftl::create<ftl::net::Universe>(root, "net"); + + net->start(); + net->waitConnections(); + + auto sources = ftl::createArray<ftl::rgbd::Source>(root, "sources", net); + const string path = root->value<string>("save_to", "./"); + + for (auto* src : sources) { src->setChannel(Channel::Depth); } + + cv::Mat show; + vector<cv::Mat> rgb(sources.size()); + vector<cv::Mat> depth(sources.size()); + +#ifdef HAVE_LIBARCHIVE + char timestamp[18]; + std::time_t t=std::time(NULL); + std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t)); + ftl::rgbd::SnapshotWriter writer = ftl::rgbd::SnapshotWriter(std::string(timestamp) + ".tar.gz"); + + for (size_t i = 0; i < sources.size(); i++) { + writer.addSource(sources[i]->getURI(), sources[i]->parameters(), sources[i]->getPose()); + } +#endif // HAVE_LIBARCHIVE + + bool save = false; + + while (ftl::running) { + for (auto s : sources) s->grab(30); + for (size_t i = 0; i < sources.size(); i++) { + do { sources[i]->getFrames(rgb[i], depth[i]); } + while(rgb[i].empty() || depth[i].empty()); + } + +#ifdef HAVE_LIBARCHIVE + if (save) { + for (size_t i = 0; i < sources.size(); i++) { + writer.addRGBD(i, rgb[i], depth[i]); + } + } +#endif // HAVE_LIBARCHIVE + + stack(rgb, show); + cv::namedWindow("Cameras", cv::WINDOW_KEEPRATIO | cv::WINDOW_NORMAL); + cv::imshow("Cameras", show); + + auto key = cv::waitKey(20); + if (key == 'r') { + save = true; + } + if (key == 's') { + save = false; + } + if (key == 27) break; + } +} + +int main(int argc, char **argv) { + auto root = ftl::configure(argc, argv, "viewer_default"); + + if (root->value("stereo", false)) { + LOG(INFO) << "Stereo images mode"; + modeLeftRight(root); + } else if (root->value("video", false)) { + LOG(INFO) << "Video mode"; + modeVideo(root); + } else { + //modeVideo(root); + modeFrame(root); } + ftl::running = false; return 0; } diff --git a/applications/gui/CMakeLists.txt b/applications/gui/CMakeLists.txt index b8970d7b91c47aa780c4ee9692b92779b1bc2fd6..fbed0680dde997c0f0a76519ac272fb3e5c1c64f 100644 --- a/applications/gui/CMakeLists.txt +++ b/applications/gui/CMakeLists.txt @@ -27,6 +27,6 @@ target_include_directories(ftl-gui PUBLIC #endif() #target_include_directories(cv-node PUBLIC ${PROJECT_SOURCE_DIR}/include) -target_link_libraries(ftl-gui ftlcommon ftlctrl ftlrgbd Threads::Threads ${OpenCV_LIBS} ${OPENVR_LIBRARIES} glog::glog ftlnet ftlrender nanogui GL) +target_link_libraries(ftl-gui ftlcommon ftlctrl ftlrgbd Threads::Threads ${OpenCV_LIBS} ${OPENVR_LIBRARIES} glog::glog ftlnet nanogui GL) diff --git a/applications/gui/src/camera.cpp b/applications/gui/src/camera.cpp index 0f2d8c1fa962dd7b9021ed34ebbf8e9e92ee84e8..32d78a4a0ff8bbccfed60b13293b1277ab599792 100644 --- a/applications/gui/src/camera.cpp +++ b/applications/gui/src/camera.cpp @@ -7,6 +7,8 @@ using ftl::rgbd::isValidDepth; using ftl::gui::GLTexture; using ftl::gui::PoseWindow; +using ftl::rgbd::Channel; +using ftl::rgbd::Channels; // TODO(Nick) MOVE class StatisticsImage { @@ -16,7 +18,7 @@ private: float n_; // total number of samples public: - StatisticsImage(cv::Size size); + explicit StatisticsImage(cv::Size size); StatisticsImage(cv::Size size, float max_f); /* @brief reset all statistics to 0 @@ -129,19 +131,29 @@ ftl::gui::Camera::Camera(ftl::gui::Screen *screen, ftl::rgbd::Source *src) : scr rotmat_.setIdentity(); //up_ = Eigen::Vector3f(0,1.0f,0); lerpSpeed_ = 0.999f; - depth_ = false; + sdepth_ = false; ftime_ = (float)glfwGetTime(); pause_ = false; - channel_ = ftl::rgbd::kChanLeft; + channel_ = Channel::Left; - channels_.push_back(ftl::rgbd::kChanLeft); - channels_.push_back(ftl::rgbd::kChanDepth); + channels_ += Channel::Left; + channels_ += Channel::Depth; // Create pose window... posewin_ = new PoseWindow(screen, src_->getURI()); posewin_->setTheme(screen->windowtheme); posewin_->setVisible(false); + + src->setCallback([this](int64_t ts, cv::Mat &rgb, cv::Mat &depth) { + UNIQUE_LOCK(mutex_, lk); + rgb_.create(rgb.size(), rgb.type()); + depth_.create(depth.size(), depth.type()); + cv::swap(rgb_,rgb); + cv::swap(depth_, depth); + cv::flip(rgb_,rgb_,0); + cv::flip(depth_,depth_,0); + }); } ftl::gui::Camera::~Camera() { @@ -219,26 +231,27 @@ void ftl::gui::Camera::showSettings() { } -void ftl::gui::Camera::setChannel(ftl::rgbd::channel_t c) { +void ftl::gui::Camera::setChannel(Channel c) { channel_ = c; switch (c) { - case ftl::rgbd::kChanFlow: - case ftl::rgbd::kChanConfidence: - case ftl::rgbd::kChanNormals: - case ftl::rgbd::kChanRight: + case Channel::Energy: + case Channel::Flow: + case Channel::Confidence: + case Channel::Normals: + case Channel::Right: src_->setChannel(c); break; - case ftl::rgbd::kChanDeviation: + case Channel::Deviation: if (stats_) { stats_->reset(); } - src_->setChannel(ftl::rgbd::kChanDepth); + src_->setChannel(Channel::Depth); break; - case ftl::rgbd::kChanDepth: + case Channel::Depth: src_->setChannel(c); break; - default: src_->setChannel(ftl::rgbd::kChanNone); + default: src_->setChannel(Channel::None); } } @@ -253,17 +266,63 @@ static Eigen::Matrix4d ConvertSteamVRMatrixToMatrix4( const vr::HmdMatrix34_t &m return matrixObj; } +static void visualizeDepthMap( const cv::Mat &depth, cv::Mat &out, + const float max_depth) +{ + DCHECK(max_depth > 0.0); + + depth.convertTo(out, CV_8U, 255.0f / max_depth); + out = 255 - out; + cv::Mat mask = (depth >= 39.0f); // TODO (mask for invalid pixels) + + applyColorMap(out, out, cv::COLORMAP_JET); + out.setTo(cv::Scalar(255, 255, 255), mask); +} + +static void visualizeEnergy( const cv::Mat &depth, cv::Mat &out, + const float max_depth) +{ + DCHECK(max_depth > 0.0); + + depth.convertTo(out, CV_8U, 255.0f / max_depth); + //out = 255 - out; + cv::Mat mask = (depth >= 39.0f); // TODO (mask for invalid pixels) + + applyColorMap(out, out, cv::COLORMAP_JET); + out.setTo(cv::Scalar(255, 255, 255), mask); +} + +static void drawEdges( const cv::Mat &in, cv::Mat &out, + const int ksize = 3, double weight = -1.0, const int threshold = 32, + const int threshold_type = cv::THRESH_TOZERO) +{ + cv::Mat edges; + cv::Laplacian(in, edges, 8, ksize); + cv::threshold(edges, edges, threshold, 255, threshold_type); + + cv::Mat edges_color(in.size(), CV_8UC3); + cv::addWeighted(edges, weight, out, 1.0, 0.0, out, CV_8UC3); +} + +bool ftl::gui::Camera::thumbnail(cv::Mat &thumb) { + UNIQUE_LOCK(mutex_, lk); + src_->grab(1,9); + if (rgb_.empty()) return false; + cv::resize(rgb_, thumb, cv::Size(320,180)); + return true; +} + const GLTexture &ftl::gui::Camera::captureFrame() { float now = (float)glfwGetTime(); delta_ = now - ftime_; ftime_ = now; if (src_ && src_->isReady()) { - cv::Mat rgb, depth; + UNIQUE_LOCK(mutex_, lk); if (screen_->hasVR()) { #ifdef HAVE_OPENVR - src_->setChannel(ftl::rgbd::kChanRight); + src_->setChannel(Channel::Right); vr::VRCompositor()->WaitGetPoses(rTrackedDevicePose_, vr::k_unMaxTrackedDeviceCount, NULL, 0 ); @@ -300,37 +359,37 @@ const GLTexture &ftl::gui::Camera::captureFrame() { } src_->grab(); - src_->getFrames(rgb, depth); - - cv::flip(rgb,rgb,0); - cv::flip(depth,depth,0); + //src_->getFrames(rgb, depth); // When switching from right to depth, client may still receive // right images from previous batch (depth.channels() == 1 check) - if (channel_ == ftl::rgbd::kChanDeviation && - depth.rows > 0 && depth.channels() == 1) + if (channel_ == Channel::Deviation && + depth_.rows > 0 && depth_.channels() == 1) { if (!stats_) { - stats_ = new StatisticsImage(depth.size()); + stats_ = new StatisticsImage(depth_.size()); } - stats_->update(depth); + stats_->update(depth_); } cv::Mat tmp; switch(channel_) { - case ftl::rgbd::kChanDepth: - if (depth.rows == 0) { break; } - //imageSize = Vector2f(depth.cols,depth.rows); - depth.convertTo(tmp, CV_8U, 255.0f / 5.0f); - tmp = 255 - tmp; - applyColorMap(tmp, tmp, cv::COLORMAP_JET); + case Channel::Energy: + if (depth_.rows == 0) { break; } + visualizeEnergy(depth_, tmp, 10.0); + texture_.update(tmp); + break; + case Channel::Depth: + if (depth_.rows == 0) { break; } + visualizeDepthMap(depth_, tmp, 7.0); + if (screen_->root()->value("showEdgesInDepth", false)) drawEdges(rgb_, tmp); texture_.update(tmp); break; - case ftl::rgbd::kChanDeviation: - if (depth.rows == 0) { break; } + case Channel::Deviation: + if (depth_.rows == 0) { break; } //imageSize = Vector2f(depth.cols, depth.rows); stats_->getStdDev(tmp); tmp.convertTo(tmp, CV_8U, 1000.0); @@ -338,23 +397,23 @@ const GLTexture &ftl::gui::Camera::captureFrame() { texture_.update(tmp); break; - case ftl::rgbd::kChanFlow: - case ftl::rgbd::kChanConfidence: - case ftl::rgbd::kChanNormals: - case ftl::rgbd::kChanRight: - if (depth.rows == 0 || depth.type() != CV_8UC3) { break; } - texture_.update(depth); + case Channel::Flow: + case Channel::Confidence: + case Channel::Normals: + case Channel::Right: + if (depth_.rows == 0 || depth_.type() != CV_8UC3) { break; } + texture_.update(depth_); break; default: - if (rgb.rows == 0) { break; } + if (rgb_.rows == 0) { break; } //imageSize = Vector2f(rgb.cols,rgb.rows); - texture_.update(rgb); + texture_.update(rgb_); #ifdef HAVE_OPENVR - if (screen_->hasVR() && depth.channels() >= 3) { + if (screen_->hasVR() && depth_.channels() >= 3) { LOG(INFO) << "DRAW RIGHT"; - textureRight_.update(depth); + textureRight_.update(depth_); } #endif } diff --git a/applications/gui/src/camera.hpp b/applications/gui/src/camera.hpp index cdd243ab3cb35e1cad7fddf9dfa9b3eae339c774..55042fe0d7b3baf5f24a26e390b1348084bcf320 100644 --- a/applications/gui/src/camera.hpp +++ b/applications/gui/src/camera.hpp @@ -23,6 +23,8 @@ class Camera { Camera(ftl::gui::Screen *screen, ftl::rgbd::Source *src); ~Camera(); + Camera(const Camera &)=delete; + ftl::rgbd::Source *source(); int width() { return (src_) ? src_->parameters().width : 0; } @@ -36,16 +38,18 @@ class Camera { void showPoseWindow(); void showSettings(); - void setChannel(ftl::rgbd::channel_t c); + void setChannel(ftl::rgbd::Channel c); void togglePause(); void isPaused(); - const std::vector<ftl::rgbd::channel_t> &availableChannels(); + const ftl::rgbd::Channels &availableChannels(); const GLTexture &captureFrame(); const GLTexture &getLeft() const { return texture_; } const GLTexture &getRight() const { return textureRight_; } + bool thumbnail(cv::Mat &thumb); + nlohmann::json getMetaData(); StatisticsImage *stats_ = nullptr; @@ -65,10 +69,13 @@ class Camera { float ftime_; float delta_; float lerpSpeed_; - bool depth_; + bool sdepth_; bool pause_; - ftl::rgbd::channel_t channel_; - std::vector<ftl::rgbd::channel_t> channels_; + ftl::rgbd::Channel channel_; + ftl::rgbd::Channels channels_; + cv::Mat rgb_; + cv::Mat depth_; + MUTEX mutex_; #ifdef HAVE_OPENVR vr::TrackedDevicePose_t rTrackedDevicePose_[ vr::k_unMaxTrackedDeviceCount ]; diff --git a/applications/gui/src/media_panel.cpp b/applications/gui/src/media_panel.cpp index 6fb6d5085cc02efae38fcdfce0819ee0d2ae7110..cb44400bb1c850669332376e0134f138b9c460f3 100644 --- a/applications/gui/src/media_panel.cpp +++ b/applications/gui/src/media_panel.cpp @@ -12,91 +12,89 @@ #endif using ftl::gui::MediaPanel; +using ftl::rgbd::Channel; MediaPanel::MediaPanel(ftl::gui::Screen *screen) : nanogui::Window(screen, ""), screen_(screen) { - using namespace nanogui; + using namespace nanogui; - paused_ = false; - writer_ = nullptr; + paused_ = false; + writer_ = nullptr; - setLayout(new BoxLayout(Orientation::Horizontal, + setLayout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 5, 10)); - auto size = Vector2i(400, 60); - //setFixedSize(size); - setPosition(Vector2i(screen->width() / 2 - size[0]/2, screen->height() - 30 - size[1])); + auto size = Vector2i(400, 60); + //setFixedSize(size); + setPosition(Vector2i(screen->width() / 2 - size[0]/2, screen->height() - 30 - size[1])); - auto button = new Button(this, "", ENTYPO_ICON_EDIT); + auto button = new Button(this, "", ENTYPO_ICON_EDIT); button->setTooltip("Edit camera properties"); - button->setCallback([this]() { - auto *cam = screen_->activeCamera(); - if (cam) cam->showPoseWindow(); - }); + button->setCallback([this]() { + auto *cam = screen_->activeCamera(); + if (cam) cam->showPoseWindow(); + }); - button = new Button(this, "", ENTYPO_ICON_CONTROLLER_RECORD); - button->setFlags(Button::ToggleButton); - button->setChangeCallback([this,button](bool state) { - if (state){ - auto *cam = screen_->activeCamera(); + button = new Button(this, "", ENTYPO_ICON_CONTROLLER_RECORD); + button->setFlags(Button::ToggleButton); + button->setChangeCallback([this,button](bool state) { + if (state){ + auto *cam = screen_->activeCamera(); - button->setTextColor(nanogui::Color(1.0f,0.1f,0.1f,1.0f)); - char timestamp[18]; + button->setTextColor(nanogui::Color(1.0f,0.1f,0.1f,1.0f)); + char timestamp[18]; std::time_t t=std::time(NULL); std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t)); writer_ = new ftl::rgbd::SnapshotStreamWriter(std::string(timestamp) + ".tar.gz", 1000 / 25); - writer_->addSource(cam->source()); - writer_->start(); - } else { - button->setTextColor(nanogui::Color(1.0f,1.0f,1.0f,1.0f)); - if (writer_) { - writer_->stop(); - delete writer_; - writer_ = nullptr; - } - } - //if (state) ... start - //else ... stop - }); + writer_->addSource(cam->source()); + writer_->start(); + } else { + button->setTextColor(nanogui::Color(1.0f,1.0f,1.0f,1.0f)); + if (writer_) { + writer_->stop(); + delete writer_; + writer_ = nullptr; + } + } + //if (state) ... start + //else ... stop + }); button = new Button(this, "", ENTYPO_ICON_CONTROLLER_STOP); - button->setCallback([this]() { - screen_->setActiveCamera(nullptr); - }); + button->setCallback([this]() { + screen_->setActiveCamera(nullptr); + }); - button = new Button(this, "", ENTYPO_ICON_CONTROLLER_PAUS); - button->setCallback([this,button]() { - paused_ = !paused_; - screen_->control()->pause(); - if (paused_) { - button->setIcon(ENTYPO_ICON_CONTROLLER_PLAY); - } else { - button->setIcon(ENTYPO_ICON_CONTROLLER_PAUS); - } - }); + button = new Button(this, "", ENTYPO_ICON_CONTROLLER_PAUS); + button->setCallback([this,button]() { + paused_ = !paused_; + screen_->control()->pause(); + if (paused_) { + button->setIcon(ENTYPO_ICON_CONTROLLER_PLAY); + } else { + button->setIcon(ENTYPO_ICON_CONTROLLER_PAUS); + } + }); - //button = new Button(this, "", ENTYPO_ICON_CONTROLLER_RECORD); + //button = new Button(this, "", ENTYPO_ICON_CONTROLLER_RECORD); #ifdef HAVE_LIBARCHIVE auto button_snapshot = new Button(this, "", ENTYPO_ICON_IMAGES); - button_snapshot->setTooltip("Screen capture"); + button_snapshot->setTooltip("Screen capture"); button_snapshot->setCallback([this] { - ftl::gui::Camera *cam = screen_->activeCamera(); - if (!cam) return; - - try { - char timestamp[18]; + ftl::gui::Camera *cam = screen_->activeCamera(); + if (!cam) return; + + try { + char timestamp[18]; std::time_t t=std::time(NULL); std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t)); auto writer = ftl::rgbd::SnapshotWriter(std::string(timestamp) + ".tar.gz"); cv::Mat rgb, depth; cam->source()->getFrames(rgb, depth); - if (!writer.addCameraParams("0", cam->source()->getPose(), cam->source()->parameters()) || !writer.addCameraRGBD( - "0", // TODO - rgb, - depth - )) { - LOG(ERROR) << "Snapshot failed"; - } + writer.addSource( cam->source()->getURI(), + cam->source()->parameters(), + cam->source()->getPose()); + writer.addRGBD(0, rgb, depth); } catch(std::runtime_error) { LOG(ERROR) << "Snapshot failed (file error)"; @@ -118,7 +116,7 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen) : nanogui::Window(screen, ""), button->setCallback([this]() { ftl::gui::Camera *cam = screen_->activeCamera(); if (cam) { - cam->setChannel(ftl::rgbd::kChanLeft); + cam->setChannel(Channel::Left); } }); @@ -127,7 +125,7 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen) : nanogui::Window(screen, ""), right_button_->setCallback([this]() { ftl::gui::Camera *cam = screen_->activeCamera(); if (cam) { - cam->setChannel(ftl::rgbd::kChanRight); + cam->setChannel(Channel::Right); } }); @@ -136,7 +134,7 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen) : nanogui::Window(screen, ""), depth_button_->setCallback([this]() { ftl::gui::Camera *cam = screen_->activeCamera(); if (cam) { - cam->setChannel(ftl::rgbd::kChanDepth); + cam->setChannel(Channel::Depth); } }); @@ -153,7 +151,7 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen) : nanogui::Window(screen, ""), button->setCallback([this]() { ftl::gui::Camera *cam = screen_->activeCamera(); if (cam) { - cam->setChannel(ftl::rgbd::kChanDeviation); + cam->setChannel(Channel::Deviation); } }); @@ -162,7 +160,7 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen) : nanogui::Window(screen, ""), button->setCallback([this]() { ftl::gui::Camera *cam = screen_->activeCamera(); if (cam) { - cam->setChannel(ftl::rgbd::kChanNormals); + cam->setChannel(Channel::Normals); } }); @@ -171,7 +169,7 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen) : nanogui::Window(screen, ""), button->setCallback([this]() { ftl::gui::Camera *cam = screen_->activeCamera(); if (cam) { - cam->setChannel(ftl::rgbd::kChanFlow); + cam->setChannel(Channel::Flow); } }); @@ -180,7 +178,16 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen) : nanogui::Window(screen, ""), button->setCallback([this]() { ftl::gui::Camera *cam = screen_->activeCamera(); if (cam) { - cam->setChannel(ftl::rgbd::kChanConfidence); + cam->setChannel(Channel::Confidence); + } + }); + + button = new Button(popup, "Energy"); + button->setFlags(Button::RadioButton); + button->setCallback([this]() { + ftl::gui::Camera *cam = screen_->activeCamera(); + if (cam) { + cam->setChannel(Channel::Energy); } }); diff --git a/applications/gui/src/media_panel.hpp b/applications/gui/src/media_panel.hpp index d7f9aa9938ee51418629ee42781e738b535c38d0..9e9154d860483a6bf6c88b8da856a4a89862de2f 100644 --- a/applications/gui/src/media_panel.hpp +++ b/applications/gui/src/media_panel.hpp @@ -15,7 +15,7 @@ class Screen; class MediaPanel : public nanogui::Window { public: - MediaPanel(ftl::gui::Screen *); + explicit MediaPanel(ftl::gui::Screen *); ~MediaPanel(); void cameraChanged(); diff --git a/applications/gui/src/screen.cpp b/applications/gui/src/screen.cpp index 6fcd4c12489f8b97a4d20c9be772520cce151cfa..03d1847112fa86468d9661d5a9966b71a3d46b09 100644 --- a/applications/gui/src/screen.cpp +++ b/applications/gui/src/screen.cpp @@ -51,15 +51,15 @@ namespace { })"; } -ftl::gui::Screen::Screen(ftl::Configurable *proot, ftl::net::Universe *pnet, ftl::ctrl::Master *controller) : nanogui::Screen(Eigen::Vector2i(1024, 768), "FT-Lab Remote Presence") { +ftl::gui::Screen::Screen(ftl::Configurable *proot, ftl::net::Universe *pnet, ftl::ctrl::Master *controller) : + nanogui::Screen(Eigen::Vector2i(1024, 768), "FT-Lab Remote Presence"), + status_("FT-Lab Remote Presence System") { using namespace nanogui; net_ = pnet; ctrl_ = controller; root_ = proot; camera_ = nullptr; - status_ = "FT-Lab Remote Presence System"; - setSize(Vector2i(1280,720)); toolbuttheme = new Theme(*theme()); diff --git a/applications/gui/src/src_window.cpp b/applications/gui/src/src_window.cpp index e98ec081b1d23f34cfe541756cbd8925e4290dbc..e7ffc4b1e0e44caccd67cce5d60d34b481ec58d7 100644 --- a/applications/gui/src/src_window.cpp +++ b/applications/gui/src/src_window.cpp @@ -116,7 +116,7 @@ void SourceWindow::draw(NVGcontext *ctx) { cv::Mat t; auto *cam = cameras_[available_[i]]; if (cam) { - if (cam->source()->thumbnail(t)) { + if (cam->thumbnail(t)) { thumbs_[i].update(t); } else { refresh_thumbs_ = true; diff --git a/applications/gui/src/src_window.hpp b/applications/gui/src/src_window.hpp index 7f28279c7fa3cd83bdb62a074e55d4d549799f73..b2fe8a9e0957f3345a719a0c37bac46bf473089c 100644 --- a/applications/gui/src/src_window.hpp +++ b/applications/gui/src/src_window.hpp @@ -22,7 +22,7 @@ class Camera; class SourceWindow : public nanogui::Window { public: - SourceWindow(ftl::gui::Screen *screen); + explicit SourceWindow(ftl::gui::Screen *screen); ~SourceWindow(); const std::vector<ftl::gui::Camera*> &getCameras(); diff --git a/applications/reconstruct/CMakeLists.txt b/applications/reconstruct/CMakeLists.txt index ae06aab6d142ba8a2ad3a1e0970905e3a70468e9..dcee7afa0147378ffaabd91263e200f8fe284c98 100644 --- a/applications/reconstruct/CMakeLists.txt +++ b/applications/reconstruct/CMakeLists.txt @@ -4,23 +4,19 @@ set(REPSRC src/main.cpp - src/voxel_scene.cpp - src/scene_rep_hash_sdf.cu - src/compactors.cu - src/garbage.cu - src/integrators.cu + #src/voxel_scene.cpp #src/ray_cast_sdf.cu - src/splat_render.cu src/camera_util.cu - src/voxel_hash.cu - src/voxel_hash.cpp #src/ray_cast_sdf.cpp src/registration.cpp #src/virtual_source.cpp - src/splat_render.cpp - src/dibr.cu - src/depth_camera.cu - src/depth_camera.cpp + #src/splat_render.cpp + #src/dibr.cu + #src/mls.cu + #src/depth_camera.cu + #src/depth_camera.cpp + src/ilw.cpp + src/ilw.cu ) add_executable(ftl-reconstruct ${REPSRC}) diff --git a/applications/reconstruct/include/ftl/depth_camera.hpp b/applications/reconstruct/include/ftl/depth_camera.hpp index cff8ff71f8872a965b2f05071e96f26a4b0a604a..39e037abd90f64e6730577578e09a1f69998b43c 100644 --- a/applications/reconstruct/include/ftl/depth_camera.hpp +++ b/applications/reconstruct/include/ftl/depth_camera.hpp @@ -4,8 +4,8 @@ //#include <cutil_inline.h> //#include <cutil_math.h> -#include <vector_types.h> -#include <cuda_runtime.h> +//#include <vector_types.h> +//#include <cuda_runtime.h> #include <ftl/cuda_matrix_util.hpp> #include <ftl/cuda_common.hpp> diff --git a/applications/reconstruct/include/ftl/depth_camera_params.hpp b/applications/reconstruct/include/ftl/depth_camera_params.hpp index 4864fccbdc9fa52647687e885f955a12132b0c04..c1c3b8e615577e2d4006ad15cfa975a27c5797fe 100644 --- a/applications/reconstruct/include/ftl/depth_camera_params.hpp +++ b/applications/reconstruct/include/ftl/depth_camera_params.hpp @@ -4,12 +4,13 @@ //#include <cutil_inline.h> //#include <cutil_math.h> -#include <vector_types.h> -#include <cuda_runtime.h> +//#include <cuda_runtime.h> #include <ftl/cuda_matrix_util.hpp> #include <ftl/rgbd/camera.hpp> +//#include <vector_types.h> + struct __align__(16) DepthCameraParams { float fx; float fy; diff --git a/applications/reconstruct/include/ftl/registration.hpp b/applications/reconstruct/include/ftl/registration.hpp index 20f8eac6ab9ad751bc4021d6f5469d6480affb2d..1af4de8a5e3cbc8b64b291e4e115165e197cf28a 100644 --- a/applications/reconstruct/include/ftl/registration.hpp +++ b/applications/reconstruct/include/ftl/registration.hpp @@ -6,10 +6,6 @@ #include <ftl/rgbd.hpp> #include <opencv2/opencv.hpp> -#ifdef HAVE_PCL - -#include <pcl/common/common_headers.h> -#include <pcl/point_cloud.h> namespace ftl { namespace registration { @@ -20,187 +16,7 @@ void from_json(nlohmann::json &json, std::map<std::string, Eigen::Matrix4d> &tra bool loadTransformations(const std::string &path, std::map<std::string, Eigen::Matrix4d> &data); bool saveTransformations(const std::string &path, std::map<std::string, Eigen::Matrix4d> &data); -/** @brief Find transformation matrix for transforming clouds_source to clouds_target. - * Assumes that corresponding points in clouds_source[i] and clouds_target[i] have same indices. - */ -Eigen::Matrix4f findTransformation( std::vector<pcl::PointCloud<pcl::PointXYZ>::Ptr> clouds_source, - std::vector<pcl::PointCloud<pcl::PointXYZ>::Ptr> clouds_target); - - -/** @brief Convert chessboard corners found with OpenCV's findChessboardCorners to PCL point cloud. */ -pcl::PointCloud<pcl::PointXYZ>::Ptr cornersToPointCloud(const std::vector<cv::Point2f> &corners, const cv::Mat &disp, const ftl::rgbd::Camera &p); - -/** @brief Find chessboard corners from image and store them in PCL PointCloud. - * @note Corners will be drawn in rgb. - */ -bool findChessboardCorners(cv::Mat &rgb, const cv::Mat &depth, const ftl::rgbd::Camera &p, const cv::Size pattern_size, pcl::PointCloud<pcl::PointXYZ>::Ptr &out, float error_threshold); - -/** - * @brief Abstract class for registration - * - * Registration procedure - * - * @todo Support for multiple features (possibly necessary for other algorithms). - */ -class Registration : public ftl::Configurable { -public: - explicit Registration(nlohmann::json &config); - void addSource(ftl::rgbd::Source* source); - size_t getSourcesCount() { return sources_.size(); } - - /** - * @brief Run registration loop. - * - * Loop terminates when processData() returns false. - */ - virtual void run(); - - /** - * @brief Find registration transformations. run() must be called before - * findTransformations(). Transformations are in same order as - * sources were added with addSource(). - * - * Transformations are calculated to targetsource if configured. If - * targetsource is not configured or is not found in inputs, which target - * coordinate system is used depends on sub-classes' implementation. - * - * @param Output parameter for transformations. - * @return True if transformations found, otherwise false. - */ - virtual bool findTransformations(std::vector<Eigen::Matrix4f> &data)=0; - - /** - * @brief Overload of findTransformations(). Map keys are source URIs. - */ - virtual bool findTransformations(std::map<std::string, Eigen::Matrix4f> &data); - -protected: - ftl::rgbd::Source* getSource(size_t idx); - - bool isTargetSourceSet(); - bool isTargetSourceFound(); - bool isTargetSource(ftl::rgbd::Source* source); - bool isTargetSource(size_t idx); - - /** - * @brief Get index for target source. If target source not defined - * returns 0. If target source is not found, returns 0. - */ - size_t getTargetSourceIdx(); - - /** - * @brief Resets visibility matrix. - */ - void resetVisibility(); - - /** - * @brief Check if there is enough data to cover all the cameras; - * that is visibility is a connected graph. Implemented with BFS. - * Implementations of processData() in sub-classes may use this - * method (but it is not required). - * - * @todo Add support for checking visibility for each different features - * found in all images. Also see findFeatures(). - */ - bool connectedVisibility(); - - /** - * @brief Check if there are enough data for all the sources to calculate - * transformations. Method may also implement additional processing - * for the data. Called once after iteration round. run() stops - * when processData() returns false. - */ - virtual bool processData()=0; - - /** - * @brief Find features in source, return - * - * Called iteratively n times for every input (n * inputs.size()). Exact - * details depend on implementation of processData(). Implementations may - * choose not to set/use visibility information. - * - * @param Input source - * @param Unique index for source provided by run(). Same source will - * always have same idx. Implementations may choose to ignore it. - * @return True/false if feature was found. Used to build adjacency matrix. - */ - virtual bool findFeatures(ftl::rgbd::Source* source, size_t idx)=0; - - std::vector<std::vector<bool>> visibility_; /*< Adjacency matrix for sources (feature visibility). */ - -private: - std::optional<std::string> target_source_; /*< Reference coordinate system for transformations. */ - std::vector<ftl::rgbd::Source*> sources_; -}; - -/** - * @brief Registration using chessboard calibration pattern - * - * Parameters from configuration: - * - * patternsize: required - * Chessboard pattern size, inner corners. - * - * maxerror: default +inf - * Maximum allowed error value for pattern detection. MSE error between - * estimated plane and points captured from input. - * - * delay: default 500 - * Milliseconds between captured images. - * - * chain: default false - * Enabling allows camera chaining. In chain mode, pattern is not - * required to be visible in every source. In default (chain: false) - * mode, pattern visibility is required for every source. - * - * iter: default 10 - * Number of iterations for capturing calibration samples. In - * non-chaining mode, each iteration consists of images where patterns - * were detected on every input. In chaining mode each iteration only - * requires camera visibility to be connected. - */ -class ChessboardRegistration : public Registration { -public: - explicit ChessboardRegistration(nlohmann::json &config); - /** - * @brief Creates new ChessboardRegistration or ChessboardRegistrationChain - * object depending on chain option in config. User of the method - * needs to free the memory. - */ - static ChessboardRegistration* create(nlohmann::json &config); - - void run() override; - bool findTransformations(std::vector<Eigen::Matrix4f> &data) override; - -protected: - bool findFeatures(ftl::rgbd::Source* source, size_t idx) override; - bool processData() override; - cv::Size pattern_size_; - std::vector<std::vector<std::optional<pcl::PointCloud<pcl::PointXYZ>::Ptr>>> data_; - std::vector<Eigen::Matrix4f> T_; - float error_threshold_; - uint delay_; - uint iter_; - uint iter_remaining_; -}; - -/** - * @brief Chain registration. Finds visibility and then runs registration. - */ -class ChessboardRegistrationChain : public ChessboardRegistration { -public: - explicit ChessboardRegistrationChain(nlohmann::json &config); - - bool findTransformations(std::vector<Eigen::Matrix4f> &data) override; - -protected: - bool processData() override; - std::vector<Eigen::Matrix4f> T_; - std::vector<std::vector<std::pair<size_t, size_t>>> edges_; -}; - } } -#endif // HAVE_PCL #endif // _FTL_RECONSTRUCT_REGISTRATION_HPP_ \ No newline at end of file diff --git a/applications/reconstruct/include/ftl/voxel_hash.hpp b/applications/reconstruct/include/ftl/voxel_hash.hpp deleted file mode 100644 index 98c2eca90530c309b5d2e46848493634aaaa053c..0000000000000000000000000000000000000000 --- a/applications/reconstruct/include/ftl/voxel_hash.hpp +++ /dev/null @@ -1,428 +0,0 @@ -// From: https://github.com/niessner/VoxelHashing/blob/master/DepthSensingCUDA/Source/VoxelUtilHashSDF.h - -#pragma once - -#ifndef sint -typedef signed int sint; -#endif - -#ifndef uint -typedef unsigned int uint; -#endif - -#ifndef slong -typedef signed long slong; -#endif - -#ifndef ulong -typedef unsigned long ulong; -#endif - -#ifndef uchar -typedef unsigned char uchar; -#endif - -#ifndef schar -typedef signed char schar; -#endif - - - - -#include <ftl/cuda_util.hpp> - -#include <ftl/cuda_matrix_util.hpp> -#include <ftl/voxel_hash_params.hpp> - -#include <ftl/depth_camera.hpp> - -#define SDF_BLOCK_SIZE 8 -#define SDF_BLOCK_SIZE_OLAP 8 - -#ifndef MINF -#define MINF __int_as_float(0xff800000) -#endif - -#ifndef PINF -#define PINF __int_as_float(0x7f800000) -#endif - -extern __constant__ ftl::voxhash::HashParams c_hashParams; -extern "C" void updateConstantHashParams(const ftl::voxhash::HashParams& hashParams); - -namespace ftl { -namespace voxhash { - -//status flags for hash entries -static const int LOCK_ENTRY = -1; -static const int FREE_ENTRY = -2147483648; -static const int NO_OFFSET = 0; - -static const uint kFlagSurface = 0x00000001; - -struct __align__(16) HashEntryHead { - union { - short4 posXYZ; // hash position (lower left corner of SDFBlock)) - uint64_t pos; - }; - int offset; // offset for collisions - uint flags; -}; - -struct __align__(16) HashEntry -{ - HashEntryHead head; - uint voxels[16]; // 512 bits, 1 bit per voxel - //uint validity[16]; // Is the voxel valid, 512 bit - - /*__device__ void operator=(const struct HashEntry& e) { - ((long long*)this)[0] = ((const long long*)&e)[0]; - ((long long*)this)[1] = ((const long long*)&e)[1]; - //((int*)this)[4] = ((const int*)&e)[4]; - ((long long*)this)[2] = ((const long long*)&e)[2]; - ((long long*)this)[2] = ((const long long*)&e)[3]; - ((long long*)this)[2] = ((const long long*)&e)[4]; - ((long long*)this)[2] = ((const long long*)&e)[5]; - ((long long*)this)[2] = ((const long long*)&e)[6]; - ((long long*)this)[2] = ((const long long*)&e)[7]; - ((long long*)this)[2] = ((const long long*)&e)[8]; - ((long long*)this)[2] = ((const long long*)&e)[9]; - ((long long*)this)[2] = ((const long long*)&e)[10]; - }*/ -}; - -struct __align__(8) Voxel { - float sdf; //signed distance function - uchar3 color; //color - uchar weight; //accumulated sdf weight - - __device__ void operator=(const struct Voxel& v) { - ((long long*)this)[0] = ((const long long*)&v)[0]; - } - -}; - -/** - * Voxel Hash Table structure and operations. Works on both CPU and GPU with - * host <-> device transfer included. - */ -struct HashData { - - /////////////// - // Host part // - /////////////// - - __device__ __host__ - HashData() { - //d_heap = NULL; - //d_heapCounter = NULL; - d_hash = NULL; - d_hashDecision = NULL; - d_hashDecisionPrefix = NULL; - d_hashCompactified = NULL; - d_hashCompactifiedCounter = NULL; - //d_SDFBlocks = NULL; - d_hashBucketMutex = NULL; - m_bIsOnGPU = false; - } - - /** - * Create all the data structures, either on GPU or system memory. - */ - __host__ void allocate(const HashParams& params, bool dataOnGPU = true); - - __host__ void updateParams(const HashParams& params); - - __host__ void free(); - - /** - * Download entire hash table from GPU to CPU memory. - */ - __host__ HashData download() const; - - /** - * Upload entire hash table from CPU to GPU memory. - */ - __host__ HashData upload() const; - - __host__ size_t getAllocatedBlocks() const; - - __host__ size_t getFreeBlocks() const; - - __host__ size_t getCollisionCount() const; - - - - ///////////////// - // Device part // - ///////////////// -//#define __CUDACC__ -#ifdef __CUDACC__ - - __device__ - const HashParams& params() const { - return c_hashParams; - } - - //! see teschner et al. (but with correct prime values) - __device__ - uint computeHashPos(const int3& virtualVoxelPos) const { - const int p0 = 73856093; - const int p1 = 19349669; - const int p2 = 83492791; - - int res = ((virtualVoxelPos.x * p0) ^ (virtualVoxelPos.y * p1) ^ (virtualVoxelPos.z * p2)) % params().m_hashNumBuckets; - if (res < 0) res += params().m_hashNumBuckets; - return (uint)res; - } - - //merges two voxels (v0 the currently stored voxel, v1 is the input voxel) - __device__ - void combineVoxel(const Voxel &v0, const Voxel& v1, Voxel &out) const { - - //v.color = (10*v0.weight * v0.color + v1.weight * v1.color)/(10*v0.weight + v1.weight); //give the currently observed color more weight - //v.color = (v0.weight * v0.color + v1.weight * v1.color)/(v0.weight + v1.weight); - //out.color = 0.5f * (v0.color + v1.color); //exponential running average - - - float3 c0 = make_float3(v0.color.x, v0.color.y, v0.color.z); - float3 c1 = make_float3(v1.color.x, v1.color.y, v1.color.z); - - //float3 res = (c0.x+c0.y+c0.z == 0) ? c1 : 0.5f*c0 + 0.5f*c1; - //float3 res = (c0+c1)/2; - float3 res = (c0 * (float)v0.weight + c1 * (float)v1.weight) / ((float)v0.weight + (float)v1.weight); - //float3 res = c1; - - out.color.x = (uchar)(res.x+0.5f); out.color.y = (uchar)(res.y+0.5f); out.color.z = (uchar)(res.z+0.5f); - - // Nick: reduces colour flicker but not ideal.. - //out.color = v1.color; - - // Option 3 (Nick): Use colour with minimum SDF since it should be closest to surface. - // Results in stable but pixelated output - //out.color = (v0.weight > 0 && (fabs(v0.sdf) < fabs(v1.sdf))) ? v0.color : v1.color; - - // Option 4 (Nick): Merge colours based upon relative closeness - /*float3 c0 = make_float3(v0.color.x, v0.color.y, v0.color.z); - float3 c1 = make_float3(v1.color.x, v1.color.y, v1.color.z); - float factor = fabs(v0.sdf - v1.sdf) / 0.05f / 2.0f; - if (factor > 0.5f) factor = 0.5f; - float factor0 = (fabs(v0.sdf) < fabs(v1.sdf)) ? 1.0f - factor : factor; - float factor1 = 1.0f - factor0; - out.color.x = (v0.weight > 0) ? (uchar)(c0.x * factor0 + c1.x * factor1) : c1.x; - out.color.y = (v0.weight > 0) ? (uchar)(c0.y * factor0 + c1.y * factor1) : c1.y; - out.color.z = (v0.weight > 0) ? (uchar)(c0.z * factor0 + c1.z * factor1) : c1.z;*/ - - out.sdf = (v0.sdf * (float)v0.weight + v1.sdf * (float)v1.weight) / ((float)v0.weight + (float)v1.weight); - out.weight = min(params().m_integrationWeightMax, (unsigned int)v0.weight + (unsigned int)v1.weight); - } - - - //! returns the truncation of the SDF for a given distance value - __device__ - float getTruncation(float z) const { - return params().m_truncation + params().m_truncScale * z; - } - - - __device__ - float3 worldToVirtualVoxelPosFloat(const float3& pos) const { - return pos / params().m_virtualVoxelSize; - } - - __device__ - int3 worldToVirtualVoxelPos(const float3& pos) const { - //const float3 p = pos*g_VirtualVoxelResolutionScalar; - const float3 p = pos / params().m_virtualVoxelSize; - return make_int3(p+make_float3(sign(p))*0.5f); - } - - __device__ - int3 virtualVoxelPosToSDFBlock(int3 virtualVoxelPos) const { - if (virtualVoxelPos.x < 0) virtualVoxelPos.x -= SDF_BLOCK_SIZE_OLAP-1; - if (virtualVoxelPos.y < 0) virtualVoxelPos.y -= SDF_BLOCK_SIZE_OLAP-1; - if (virtualVoxelPos.z < 0) virtualVoxelPos.z -= SDF_BLOCK_SIZE_OLAP-1; - - return make_int3( - virtualVoxelPos.x/SDF_BLOCK_SIZE_OLAP, - virtualVoxelPos.y/SDF_BLOCK_SIZE_OLAP, - virtualVoxelPos.z/SDF_BLOCK_SIZE_OLAP); - } - - // Computes virtual voxel position of corner sample position - __device__ - int3 SDFBlockToVirtualVoxelPos(const int3& sdfBlock) const { - return sdfBlock*SDF_BLOCK_SIZE_OLAP; - } - - __device__ - float3 virtualVoxelPosToWorld(const int3& pos) const { - return make_float3(pos)*params().m_virtualVoxelSize; - } - - __device__ - float3 SDFBlockToWorld(const int3& sdfBlock) const { - return virtualVoxelPosToWorld(SDFBlockToVirtualVoxelPos(sdfBlock)); - } - - __device__ - int3 worldToSDFBlock(const float3& worldPos) const { - return virtualVoxelPosToSDFBlock(worldToVirtualVoxelPos(worldPos)); - } - - __device__ - bool isInBoundingBox(const HashParams &hashParams, const int3& sdfBlock) { - // NOTE (Nick): Changed, just assume all voxels are potentially in frustrum - //float3 posWorld = virtualVoxelPosToWorld(SDFBlockToVirtualVoxelPos(sdfBlock)) + hashParams.m_virtualVoxelSize * 0.5f * (SDF_BLOCK_SIZE - 1.0f); - //return camera.isInCameraFrustumApprox(hashParams.m_rigidTransformInverse, posWorld); - return !(hashParams.m_flags & ftl::voxhash::kFlagClipping) || sdfBlock.x > hashParams.m_minBounds.x && sdfBlock.x < hashParams.m_maxBounds.x && - sdfBlock.y > hashParams.m_minBounds.y && sdfBlock.y < hashParams.m_maxBounds.y && - sdfBlock.z > hashParams.m_minBounds.z && sdfBlock.z < hashParams.m_maxBounds.z; - } - - //! computes the (local) virtual voxel pos of an index; idx in [0;511] - __device__ - uint3 delinearizeVoxelIndex(uint idx) const { - uint x = idx % SDF_BLOCK_SIZE; - uint y = (idx % (SDF_BLOCK_SIZE * SDF_BLOCK_SIZE)) / SDF_BLOCK_SIZE; - uint z = idx / (SDF_BLOCK_SIZE * SDF_BLOCK_SIZE); - return make_uint3(x,y,z); - } - - //! computes the linearized index of a local virtual voxel pos; pos in [0;7]^3 - __device__ - uint linearizeVoxelPos(const int3& virtualVoxelPos) const { - return - virtualVoxelPos.z * SDF_BLOCK_SIZE * SDF_BLOCK_SIZE + - virtualVoxelPos.y * SDF_BLOCK_SIZE + - virtualVoxelPos.x; - } - - __device__ - int virtualVoxelPosToLocalSDFBlockIndex(const int3& virtualVoxelPos) const { - int3 localVoxelPos = make_int3( - virtualVoxelPos.x % SDF_BLOCK_SIZE, - virtualVoxelPos.y % SDF_BLOCK_SIZE, - virtualVoxelPos.z % SDF_BLOCK_SIZE); - - if (localVoxelPos.x < 0) localVoxelPos.x += SDF_BLOCK_SIZE; - if (localVoxelPos.y < 0) localVoxelPos.y += SDF_BLOCK_SIZE; - if (localVoxelPos.z < 0) localVoxelPos.z += SDF_BLOCK_SIZE; - - return linearizeVoxelPos(localVoxelPos); - } - - __device__ - int worldToLocalSDFBlockIndex(const float3& world) const { - int3 virtualVoxelPos = worldToVirtualVoxelPos(world); - return virtualVoxelPosToLocalSDFBlockIndex(virtualVoxelPos); - } - - - //! returns the hash entry for a given worldPos; if there was no hash entry the returned entry will have a ptr with FREE_ENTRY set - __device__ - int getHashEntry(const float3& worldPos) const { - //int3 blockID = worldToSDFVirtualVoxelPos(worldPos)/SDF_BLOCK_SIZE; //position of sdf block - int3 blockID = worldToSDFBlock(worldPos); - return getHashEntryForSDFBlockPos(blockID); - } - - - __device__ - void deleteHashEntry(uint id) { - deleteHashEntry(d_hash[id]); - } - - __device__ - void deleteHashEntry(HashEntry& hashEntry) { - hashEntry.head.pos = 0; - hashEntry.head.offset = FREE_ENTRY; - for (int i=0; i<16; ++i) hashEntry.voxels[i] = 0; - } - - __device__ - bool voxelExists(const float3& worldPos) const { - int hashEntry = getHashEntry(worldPos); - return (hashEntry != -1); - } - - __device__ - void deleteVoxel(Voxel& v) const { - v.color = make_uchar3(0,0,0); - v.weight = 0; - v.sdf = 0.0f; - } - - - __device__ - bool getVoxel(const float3& worldPos) const { - int hashEntry = getHashEntry(worldPos); - if (hashEntry == -1) { - return false; - } else { - int3 virtualVoxelPos = worldToVirtualVoxelPos(worldPos); - int ix = virtualVoxelPosToLocalSDFBlockIndex(virtualVoxelPos); - return d_hash[hashEntry].voxels[ix/32] & (0x1 << (ix % 32)); - } - } - - __device__ - bool getVoxel(const int3& virtualVoxelPos) const { - int hashEntry = getHashEntryForSDFBlockPos(virtualVoxelPosToSDFBlock(virtualVoxelPos)); - if (hashEntry == -1) { - return false; - } else { - int ix = virtualVoxelPosToLocalSDFBlockIndex(virtualVoxelPos); - return d_hash[hashEntry].voxels[ix >> 5] & (0x1 << (ix & 0x1F)); - } - } - - /*__device__ - void setVoxel(const int3& virtualVoxelPos, bool voxelInput) const { - int hashEntry = getHashEntryForSDFBlockPos(virtualVoxelPosToSDFBlock(virtualVoxelPos)); - if (hashEntry == -1) { - d_SDFBlocks[hashEntry.ptr + virtualVoxelPosToLocalSDFBlockIndex(virtualVoxelPos)] = voxelInput; - int ix = virtualVoxelPosToLocalSDFBlockIndex(virtualVoxelPos); - d_hash[hashEntry].voxels[ix >> 5] |= (0x1 << (ix & 0x1F)); - } - }*/ - - //! returns the hash entry for a given sdf block id; if there was no hash entry the returned entry will have a ptr with FREE_ENTRY set - __device__ - int getHashEntryForSDFBlockPos(const int3& sdfBlock) const; - - //for histogram (no collision traversal) - __device__ - unsigned int getNumHashEntriesPerBucket(unsigned int bucketID); - - //for histogram (collisions traversal only) - __device__ - unsigned int getNumHashLinkedList(unsigned int bucketID); - - - //pos in SDF block coordinates - __device__ - void allocBlock(const int3& pos); - - //!inserts a hash entry without allocating any memory: used by streaming: TODO MATTHIAS check the atomics in this function - __device__ - bool insertHashEntry(HashEntry entry); - - //! deletes a hash entry position for a given sdfBlock index (returns true uppon successful deletion; otherwise returns false) - __device__ - bool deleteHashEntryElement(const int3& sdfBlock); - -#endif //CUDACC - - int* d_hashDecision; // - int* d_hashDecisionPrefix; // - HashEntry* d_hash; //hash that stores pointers to sdf blocks - HashEntry** d_hashCompactified; //same as before except that only valid pointers are there - int* d_hashCompactifiedCounter; //atomic counter to add compactified entries atomically - int* d_hashBucketMutex; //binary flag per hash bucket; used for allocation to atomically lock a bucket - - bool m_bIsOnGPU; //the class be be used on both cpu and gpu -}; - -} // namespace voxhash -} // namespace ftl diff --git a/applications/reconstruct/include/ftl/voxel_hash_params.hpp b/applications/reconstruct/include/ftl/voxel_hash_params.hpp index 480e16d478a7a3c82d046f6de464d7bb20c04f64..ac5fcaa726febaff0b56426b6d481d48bae3e421 100644 --- a/applications/reconstruct/include/ftl/voxel_hash_params.hpp +++ b/applications/reconstruct/include/ftl/voxel_hash_params.hpp @@ -4,8 +4,8 @@ //#include <cutil_inline.h> //#include <cutil_math.h> -#include <vector_types.h> -#include <cuda_runtime.h> +//#include <vector_types.h> +//#include <cuda_runtime.h> #include <ftl/cuda_matrix_util.hpp> @@ -28,8 +28,8 @@ struct __align__(16) HashParams { unsigned int m_integrationWeightSample; unsigned int m_integrationWeightMax; - int3 m_minBounds; - int3 m_maxBounds; + float3 m_minBounds; + float3 m_maxBounds; float m_spatialSmoothing; float m_colourSmoothing; float m_confidenceThresh; diff --git a/applications/reconstruct/include/ftl/voxel_scene.hpp b/applications/reconstruct/include/ftl/voxel_scene.hpp index d594d479f1f35ffc9cece087b867be2f4b781ee0..4ea2d5eb3d98b0a3baac018f2c9ace9b5ad7a6f0 100644 --- a/applications/reconstruct/include/ftl/voxel_scene.hpp +++ b/applications/reconstruct/include/ftl/voxel_scene.hpp @@ -2,7 +2,7 @@ #pragma once -#include <cuda_runtime.h> +//#include <cuda_runtime.h> #include <ftl/cuda_common.hpp> #include <ftl/rgbd/source.hpp> @@ -10,6 +10,7 @@ #include <ftl/matrix_conversion.hpp> #include <ftl/voxel_hash.hpp> #include <ftl/depth_camera.hpp> +#include <ftl/rgbd/group.hpp> #include <unordered_set> namespace ftl { @@ -34,6 +35,8 @@ class SceneRep : public ftl::Configurable { */ int upload(); + int upload(ftl::rgbd::FrameSet &); + /** * Merge all camera frames into the voxel hash datastructure. */ diff --git a/applications/reconstruct/src/compactors.cu b/applications/reconstruct/src/compactors.cu deleted file mode 100644 index b7cdd5028f0f5ec78de47d8bf6f9099c5448a494..0000000000000000000000000000000000000000 --- a/applications/reconstruct/src/compactors.cu +++ /dev/null @@ -1,236 +0,0 @@ -#include "compactors.hpp" - -using ftl::voxhash::HashData; -using ftl::voxhash::HashParams; -using ftl::voxhash::Voxel; -using ftl::voxhash::HashEntry; -using ftl::voxhash::FREE_ENTRY; - -#define COMPACTIFY_HASH_THREADS_PER_BLOCK 256 -//#define COMPACTIFY_HASH_SIMPLE - - -/*__global__ void fillDecisionArrayKernel(HashData hashData, DepthCameraData depthCameraData) -{ - const HashParams& hashParams = c_hashParams; - const unsigned int idx = blockIdx.x*blockDim.x + threadIdx.x; - - if (idx < hashParams.m_hashNumBuckets * HASH_BUCKET_SIZE) { - hashData.d_hashDecision[idx] = 0; - if (hashData.d_hash[idx].ptr != FREE_ENTRY) { - if (hashData.isSDFBlockInCameraFrustumApprox(hashData.d_hash[idx].pos)) { - hashData.d_hashDecision[idx] = 1; //yes - } - } - } -}*/ - -/*extern "C" void fillDecisionArrayCUDA(HashData& hashData, const HashParams& hashParams, const DepthCameraData& depthCameraData) -{ - const dim3 gridSize((HASH_BUCKET_SIZE * hashParams.m_hashNumBuckets + (T_PER_BLOCK*T_PER_BLOCK) - 1)/(T_PER_BLOCK*T_PER_BLOCK), 1); - const dim3 blockSize((T_PER_BLOCK*T_PER_BLOCK), 1); - - fillDecisionArrayKernel<<<gridSize, blockSize>>>(hashData, depthCameraData); - -#ifdef _DEBUG - cudaSafeCall(cudaDeviceSynchronize()); - //cutilCheckMsg(__FUNCTION__); -#endif - -}*/ - -/*__global__ void compactifyHashKernel(HashData hashData) -{ - const HashParams& hashParams = c_hashParams; - const unsigned int idx = blockIdx.x*blockDim.x + threadIdx.x; - if (idx < hashParams.m_hashNumBuckets * HASH_BUCKET_SIZE) { - if (hashData.d_hashDecision[idx] == 1) { - hashData.d_hashCompactified[hashData.d_hashDecisionPrefix[idx]-1] = hashData.d_hash[idx]; - } - } -}*/ - -/*extern "C" void compactifyHashCUDA(HashData& hashData, const HashParams& hashParams) -{ - const dim3 gridSize((HASH_BUCKET_SIZE * hashParams.m_hashNumBuckets + (T_PER_BLOCK*T_PER_BLOCK) - 1)/(T_PER_BLOCK*T_PER_BLOCK), 1); - const dim3 blockSize((T_PER_BLOCK*T_PER_BLOCK), 1); - - compactifyHashKernel<<<gridSize, blockSize>>>(hashData); - -#ifdef _DEBUG - cudaSafeCall(cudaDeviceSynchronize()); - //cutilCheckMsg(__FUNCTION__); -#endif -}*/ - -/*__global__ void compactifyVisibleKernel(HashData hashData, HashParams hashParams, DepthCameraParams camera) -{ - //const HashParams& hashParams = c_hashParams; - const unsigned int idx = blockIdx.x*blockDim.x + threadIdx.x; -#ifdef COMPACTIFY_HASH_SIMPLE - if (idx < hashParams.m_hashNumBuckets) { - if (hashData.d_hash[idx].ptr != FREE_ENTRY) { - if (hashData.isSDFBlockInCameraFrustumApprox(hashParams, camera, hashData.d_hash[idx].pos)) - { - int addr = atomicAdd(hashData.d_hashCompactifiedCounter, 1); - hashData.d_hashCompactified[addr] = hashData.d_hash[idx]; - } - } - } -#else - __shared__ int localCounter; - if (threadIdx.x == 0) localCounter = 0; - __syncthreads(); - - int addrLocal = -1; - if (idx < hashParams.m_hashNumBuckets) { - if (hashData.d_hash[idx].ptr != FREE_ENTRY) { - if (hashData.isSDFBlockInCameraFrustumApprox(hashParams, camera, hashData.d_hash[idx].pos)) - { - addrLocal = atomicAdd(&localCounter, 1); - } - } - } - - __syncthreads(); - - __shared__ int addrGlobal; - if (threadIdx.x == 0 && localCounter > 0) { - addrGlobal = atomicAdd(hashData.d_hashCompactifiedCounter, localCounter); - } - __syncthreads(); - - if (addrLocal != -1) { - const unsigned int addr = addrGlobal + addrLocal; - hashData.d_hashCompactified[addr] = hashData.d_hash[idx]; - } -#endif -} - -void ftl::cuda::compactifyVisible(HashData& hashData, const HashParams& hashParams, const DepthCameraParams &camera, cudaStream_t stream) { - const unsigned int threadsPerBlock = COMPACTIFY_HASH_THREADS_PER_BLOCK; - const dim3 gridSize((hashParams.m_hashNumBuckets + threadsPerBlock - 1) / threadsPerBlock, 1); - const dim3 blockSize(threadsPerBlock, 1); - - cudaSafeCall(cudaMemsetAsync(hashData.d_hashCompactifiedCounter, 0, sizeof(int),stream)); - compactifyVisibleKernel << <gridSize, blockSize, 0, stream >> >(hashData, hashParams, camera); - //unsigned int res = 0; - //cudaSafeCall(cudaMemcpyAsync(&res, hashData.d_hashCompactifiedCounter, sizeof(unsigned int), cudaMemcpyDeviceToHost, stream)); - -#ifdef _DEBUG - cudaSafeCall(cudaDeviceSynchronize()); - //cutilCheckMsg(__FUNCTION__); -#endif - //return res; -}*/ - -__global__ void compactifyAllocatedKernel(HashData hashData) -{ - const HashParams& hashParams = c_hashParams; - const unsigned int idx = blockIdx.x*blockDim.x + threadIdx.x; -#ifdef COMPACTIFY_HASH_SIMPLE - if (idx < hashParams.m_hashNumBuckets) { - if (hashData.d_hash[idx].head.offset != FREE_ENTRY) { - int addr = atomicAdd(hashData.d_hashCompactifiedCounter, 1); - hashData.d_hashCompactified[addr] = &hashData.d_hash[idx]; - } - } -#else - __shared__ int localCounter; - if (threadIdx.x == 0) localCounter = 0; - __syncthreads(); - - int addrLocal = -1; - if (idx < hashParams.m_hashNumBuckets) { - if (hashData.d_hash[idx].head.offset != FREE_ENTRY) { - addrLocal = atomicAdd(&localCounter, 1); - } - } - - __syncthreads(); - - __shared__ int addrGlobal; - if (threadIdx.x == 0 && localCounter > 0) { - addrGlobal = atomicAdd(hashData.d_hashCompactifiedCounter, localCounter); - } - __syncthreads(); - - if (addrLocal != -1) { - const unsigned int addr = addrGlobal + addrLocal; - hashData.d_hashCompactified[addr] = &hashData.d_hash[idx]; - } -#endif -} - -void ftl::cuda::compactifyAllocated(HashData& hashData, const HashParams& hashParams, cudaStream_t stream) { - const unsigned int threadsPerBlock = COMPACTIFY_HASH_THREADS_PER_BLOCK; - const dim3 gridSize((hashParams.m_hashNumBuckets + threadsPerBlock - 1) / threadsPerBlock, 1); - const dim3 blockSize(threadsPerBlock, 1); - - cudaSafeCall(cudaMemsetAsync(hashData.d_hashCompactifiedCounter, 0, sizeof(int), stream)); - compactifyAllocatedKernel << <gridSize, blockSize, 0, stream >> >(hashData); - //unsigned int res = 0; - //cudaSafeCall(cudaMemcpyAsync(&res, hashData.d_hashCompactifiedCounter, sizeof(unsigned int), cudaMemcpyDeviceToHost, stream)); - -#ifdef _DEBUG - cudaSafeCall(cudaDeviceSynchronize()); - //cutilCheckMsg(__FUNCTION__); -#endif - //return res; -} - - -__global__ void compactifyOccupiedKernel(HashData hashData) -{ - const HashParams& hashParams = c_hashParams; - const unsigned int idx = blockIdx.x*blockDim.x + threadIdx.x; -#ifdef COMPACTIFY_HASH_SIMPLE - if (idx < hashParams.m_hashNumBuckets) { - if (hashData.d_hash[idx].head.offset != FREE_ENTRY && hashData.d_hash[idx].head.flags & ftl::voxhash::kFlagSurface) { - int addr = atomicAdd(hashData.d_hashCompactifiedCounter, 1); - hashData.d_hashCompactified[addr] = &hashData.d_hash[idx]; - } - } -#else - __shared__ int localCounter; - if (threadIdx.x == 0) localCounter = 0; - __syncthreads(); - - int addrLocal = -1; - if (idx < hashParams.m_hashNumBuckets) { - if (hashData.d_hash[idx].head.offset != FREE_ENTRY && (hashData.d_hash[idx].head.flags & ftl::voxhash::kFlagSurface)) { // TODO:(Nick) Check voxels for all 0 or all 1 - addrLocal = atomicAdd(&localCounter, 1); - } - } - - __syncthreads(); - - __shared__ int addrGlobal; - if (threadIdx.x == 0 && localCounter > 0) { - addrGlobal = atomicAdd(hashData.d_hashCompactifiedCounter, localCounter); - } - __syncthreads(); - - if (addrLocal != -1) { - const unsigned int addr = addrGlobal + addrLocal; - hashData.d_hashCompactified[addr] = &hashData.d_hash[idx]; - } -#endif -} - -void ftl::cuda::compactifyOccupied(HashData& hashData, const HashParams& hashParams, cudaStream_t stream) { - const unsigned int threadsPerBlock = COMPACTIFY_HASH_THREADS_PER_BLOCK; - const dim3 gridSize((hashParams.m_hashNumBuckets + threadsPerBlock - 1) / threadsPerBlock, 1); - const dim3 blockSize(threadsPerBlock, 1); - - cudaSafeCall(cudaMemsetAsync(hashData.d_hashCompactifiedCounter, 0, sizeof(int), stream)); - compactifyAllocatedKernel << <gridSize, blockSize, 0, stream >> >(hashData); - //unsigned int res = 0; - //cudaSafeCall(cudaMemcpyAsync(&res, hashData.d_hashCompactifiedCounter, sizeof(unsigned int), cudaMemcpyDeviceToHost, stream)); - -#ifdef _DEBUG - cudaSafeCall(cudaDeviceSynchronize()); - //cutilCheckMsg(__FUNCTION__); -#endif - //return res; -} diff --git a/applications/reconstruct/src/compactors.hpp b/applications/reconstruct/src/compactors.hpp deleted file mode 100644 index 6c61985eea8448a078b8abe3e821992519c9425f..0000000000000000000000000000000000000000 --- a/applications/reconstruct/src/compactors.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef _FTL_RECONSTRUCT_COMPACTORS_HPP_ -#define _FTL_RECONSTRUCT_COMPACTORS_HPP_ - -#include <ftl/voxel_hash.hpp> - -namespace ftl { -namespace cuda { - -// Compact visible -//void compactifyVisible(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams, const DepthCameraParams &camera, cudaStream_t); - -// Compact allocated -void compactifyAllocated(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams, cudaStream_t); - -// Compact visible surfaces -void compactifyOccupied(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams, cudaStream_t stream); - -} -} - -#endif // _FTL_RECONSTRUCT_COMPACTORS_HPP_ diff --git a/applications/reconstruct/src/depth_camera.cu b/applications/reconstruct/src/depth_camera.cu index 9a36ea452225c1a174d0ba6ced0baf98471688eb..7a322eaf733230772e72c50722e849335bf7e891 100644 --- a/applications/reconstruct/src/depth_camera.cu +++ b/applications/reconstruct/src/depth_camera.cu @@ -47,6 +47,21 @@ void ftl::cuda::clear_depth(const ftl::cuda::TextureObject<int> &depth, cudaStre clear_depth_kernel<<<clear_gridSize, clear_blockSize, 0, stream>>>(depth); } +__global__ void clear_to_zero_kernel(ftl::cuda::TextureObject<float> depth) { + const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x; + const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y; + + if (x < depth.width() && y < depth.height()) { + depth(x,y) = 0.0f; //PINF; + } +} + +void ftl::cuda::clear_to_zero(const ftl::cuda::TextureObject<float> &depth, cudaStream_t stream) { + const dim3 clear_gridSize((depth.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (depth.height() + T_PER_BLOCK - 1)/T_PER_BLOCK); + const dim3 clear_blockSize(T_PER_BLOCK, T_PER_BLOCK); + clear_to_zero_kernel<<<clear_gridSize, clear_blockSize, 0, stream>>>(depth); +} + __global__ void clear_points_kernel(ftl::cuda::TextureObject<float4> depth) { const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x; const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y; @@ -79,6 +94,21 @@ void ftl::cuda::clear_colour(const ftl::cuda::TextureObject<uchar4> &depth, cuda clear_colour_kernel<<<clear_gridSize, clear_blockSize, 0, stream>>>(depth); } +__global__ void clear_colour_kernel(ftl::cuda::TextureObject<float4> depth) { + const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x; + const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y; + + if (x < depth.width() && y < depth.height()) { + depth(x,y) = make_float4(0.0f); + } +} + +void ftl::cuda::clear_colour(const ftl::cuda::TextureObject<float4> &depth, cudaStream_t stream) { + const dim3 clear_gridSize((depth.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (depth.height() + T_PER_BLOCK - 1)/T_PER_BLOCK); + const dim3 clear_blockSize(T_PER_BLOCK, T_PER_BLOCK); + clear_colour_kernel<<<clear_gridSize, clear_blockSize, 0, stream>>>(depth); +} + // ===== Type convert ===== template <typename A, typename B> @@ -103,331 +133,6 @@ void ftl::cuda::int_to_float(const ftl::cuda::TextureObject<int> &in, ftl::cuda: convert_kernel<int,float><<<gridSize, blockSize, 0, stream>>>(in, out, scale); } -/// ===== MLS Smooth - -// TODO:(Nick) Put this in a common location (used in integrators.cu) -extern __device__ float spatialWeighting(float r); -extern __device__ float spatialWeighting(float r, float h); - -/* - * Kim, K., Chalidabhongse, T. H., Harwood, D., & Davis, L. (2005). - * Real-time foreground-background segmentation using codebook model. - * Real-Time Imaging. https://doi.org/10.1016/j.rti.2004.12.004 - */ - __device__ float colordiffFloat(const uchar4 &pa, const uchar4 &pb) { - const float x_2 = pb.x * pb.x + pb.y * pb.y + pb.z * pb.z; - const float v_2 = pa.x * pa.x + pa.y * pa.y + pa.z * pa.z; - const float xv_2 = pow(pb.x * pa.x + pb.y * pa.y + pb.z * pa.z, 2); - const float p_2 = xv_2 / v_2; - return sqrt(x_2 - p_2); -} - -__device__ float colordiffFloat2(const uchar4 &pa, const uchar4 &pb) { - float3 delta = make_float3((float)pa.x - (float)pb.x, (float)pa.y - (float)pb.y, (float)pa.z - (float)pb.z); - return length(delta); -} - -/* - * Colour weighting as suggested in: - * C. Kuster et al. Spatio-Temporal Geometry Fusion for Multiple Hybrid Cameras using Moving Least Squares Surfaces. 2014. - * c = colour distance - */ - __device__ float colourWeighting(float c) { - const float h = c_hashParams.m_colourSmoothing; - if (c >= h) return 0.0f; - float ch = c / h; - ch = 1.0f - ch*ch; - return ch*ch*ch*ch; -} - -#define WINDOW_RADIUS 5 - -__device__ float mlsCamera(int cam, const float3 &mPos, uchar4 c1, float3 &wpos) { - const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[cam]; - - const float3 pf = camera.poseInverse * mPos; - float3 pos = make_float3(0.0f, 0.0f, 0.0f); - const uint2 screenPos = make_uint2(camera.params.cameraToKinectScreenInt(pf)); - float weights = 0.0f; - - //#pragma unroll - for (int v=-WINDOW_RADIUS; v<=WINDOW_RADIUS; ++v) { - for (int u=-WINDOW_RADIUS; u<=WINDOW_RADIUS; ++u) { - //if (screenPos.x+u < width && screenPos.y+v < height) { //on screen - float depth = tex2D<float>(camera.depth, screenPos.x+u, screenPos.y+v); - const float3 camPos = camera.params.kinectDepthToSkeleton(screenPos.x+u, screenPos.y+v, depth); - float weight = spatialWeighting(length(pf - camPos)); - - if (weight > 0.0f) { - uchar4 c2 = tex2D<uchar4>(camera.colour, screenPos.x+u, screenPos.y+v); - weight *= colourWeighting(colordiffFloat2(c1,c2)); - - if (weight > 0.0f) { - wpos += weight* (camera.pose * camPos); - weights += weight; - } - } - //} - } - } - - //wpos += (camera.pose * pos); - - return weights; -} - -__device__ float mlsCameraNoColour(int cam, const float3 &mPos, uchar4 c1, const float4 &norm, float3 &wpos, float h) { - const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[cam]; - - const float3 pf = camera.poseInverse * mPos; - float3 pos = make_float3(0.0f, 0.0f, 0.0f); - const uint2 screenPos = make_uint2(camera.params.cameraToKinectScreenInt(pf)); - float weights = 0.0f; - - //#pragma unroll - for (int v=-WINDOW_RADIUS; v<=WINDOW_RADIUS; ++v) { - for (int u=-WINDOW_RADIUS; u<=WINDOW_RADIUS; ++u) { - //if (screenPos.x+u < width && screenPos.y+v < height) { //on creen - float depth = tex2D<float>(camera.depth, screenPos.x+u, screenPos.y+v); - const float3 camPos = camera.params.kinectDepthToSkeleton(screenPos.x+u, screenPos.y+v, depth); - - // TODO:(Nick) dot product of normals < 0 means the point - // should be ignored with a weight of 0 since it is facing the wrong direction - // May be good to simply weight using the dot product to give - // a stronger weight to those whose normals are closer - - float weight = spatialWeighting(length(pf - camPos), h); - - if (weight > 0.0f) { - float4 n2 = tex2D<float4>(camera.normal, screenPos.x+u, screenPos.y+v); - if (dot(make_float3(norm), make_float3(n2)) > 0.0f) { - - uchar4 c2 = tex2D<uchar4>(camera.colour, screenPos.x+u, screenPos.y+v); - - if (colourWeighting(colordiffFloat2(c1,c2)) > 0.0f) { - pos += weight*camPos; // (camera.pose * camPos); - weights += weight; - } - } - } - //} - } - } - - if (weights > 0.0f) wpos += (camera.pose * (pos / weights)) * weights; - - return weights; -} - -__device__ float mlsCameraBest(int cam, const float3 &mPos, uchar4 c1, float3 &wpos) { - const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[cam]; - - const float3 pf = camera.poseInverse * mPos; - float3 pos = make_float3(0.0f, 0.0f, 0.0f); - const uint2 screenPos = make_uint2(camera.params.cameraToKinectScreenInt(pf)); - float weights = 0.0f; - - //#pragma unroll - for (int v=-WINDOW_RADIUS; v<=WINDOW_RADIUS; ++v) { - for (int u=-WINDOW_RADIUS; u<=WINDOW_RADIUS; ++u) { - //if (screenPos.x+u < width && screenPos.y+v < height) { //on screen - float depth = tex2D<float>(camera.depth, screenPos.x+u, screenPos.y+v); - const float3 camPos = camera.params.kinectDepthToSkeleton(screenPos.x+u, screenPos.y+v, depth); - float weight = spatialWeighting(length(pf - camPos)); - - if (weight > 0.0f) { - uchar4 c2 = tex2D<uchar4>(camera.colour, screenPos.x+u, screenPos.y+v); - weight *= colourWeighting(colordiffFloat2(c1,c2)); - - if (weight > weights) { - pos = weight* (camera.pose * camPos); - weights = weight; - } - } - //} - } - } - - wpos += pos; - //wpos += (camera.pose * pos); - - return weights; -} - -__device__ float mlsCameraPoint(int cam, const float3 &mPos, uchar4 c1, float3 &wpos) { - const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[cam]; - - const float3 pf = camera.poseInverse * mPos; - float3 pos = make_float3(0.0f, 0.0f, 0.0f); - const uint2 screenPos = make_uint2(camera.params.cameraToKinectScreenInt(pf)); - float weights = 0.0f; - - - //float depth = tex2D<float>(camera.depth, screenPos.x, screenPos.y); - const float3 worldPos = make_float3(tex2D<float4>(camera.points, screenPos.x, screenPos.y)); - if (worldPos.z == MINF) return 0.0f; - - float weight = spatialWeighting(length(mPos - worldPos)); - - if (weight > 0.0f) { - wpos += weight* (worldPos); - weights += weight; - } - - return weights; -} - -__global__ void mls_smooth_kernel(ftl::cuda::TextureObject<float4> output, HashParams hashParams, int numcams, int cam) { - const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x; - const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y; - - const int width = output.width(); - const int height = output.height(); - - const DepthCameraCUDA &mainCamera = c_cameras[cam]; - - if (x < width && y < height) { - - const float depth = tex2D<float>(mainCamera.depth, x, y); - const uchar4 c1 = tex2D<uchar4>(mainCamera.colour, x, y); - const float4 norm = tex2D<float4>(mainCamera.normal, x, y); - //if (x == 400 && y == 200) printf("NORMX: %f\n", norm.x); - - float3 wpos = make_float3(0.0f); - float3 wnorm = make_float3(0.0f); - float weights = 0.0f; - - if (depth >= mainCamera.params.m_sensorDepthWorldMin && depth <= mainCamera.params.m_sensorDepthWorldMax) { - float3 mPos = mainCamera.pose * mainCamera.params.kinectDepthToSkeleton(x, y, depth); - - if ((!(hashParams.m_flags & ftl::voxhash::kFlagClipping)) || (mPos.x > hashParams.m_minBounds.x && mPos.x < hashParams.m_maxBounds.x && - mPos.y > hashParams.m_minBounds.y && mPos.y < hashParams.m_maxBounds.y && - mPos.z > hashParams.m_minBounds.z && mPos.z < hashParams.m_maxBounds.z)) { - - if (hashParams.m_flags & ftl::voxhash::kFlagMLS) { - for (uint cam2=0; cam2<numcams; ++cam2) { - //if (cam2 == cam) weights += mlsCameraNoColour(cam2, mPos, c1, wpos, c_hashParams.m_spatialSmoothing*0.1f); //weights += 0.5*mlsCamera(cam2, mPos, c1, wpos); - weights += mlsCameraNoColour(cam2, mPos, c1, norm, wpos, c_hashParams.m_spatialSmoothing); //*((cam == cam2)? 0.1f : 5.0f)); - - // Previous approach - //if (cam2 == cam) continue; - //weights += mlsCameraBest(cam2, mPos, c1, wpos); - } - wpos /= weights; - } else { - weights = 1000.0f; - wpos = mPos; - } - - //output(x,y) = (weights >= hashParams.m_confidenceThresh) ? make_float4(wpos, 0.0f) : make_float4(MINF,MINF,MINF,MINF); - - if (weights >= hashParams.m_confidenceThresh) output(x,y) = make_float4(wpos, 0.0f); - - //const uint2 screenPos = make_uint2(mainCamera.params.cameraToKinectScreenInt(mainCamera.poseInverse * wpos)); - //if (screenPos.x < output.width() && screenPos.y < output.height()) { - // output(screenPos.x,screenPos.y) = (weights >= hashParams.m_confidenceThresh) ? make_float4(wpos, 0.0f) : make_float4(MINF,MINF,MINF,MINF); - //} - } - } - } -} - -void ftl::cuda::mls_smooth(TextureObject<float4> &output, const HashParams &hashParams, int numcams, int cam, cudaStream_t stream) { - const dim3 gridSize((output.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (output.height() + T_PER_BLOCK - 1)/T_PER_BLOCK); - const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK); - - mls_smooth_kernel<<<gridSize, blockSize, 0, stream>>>(output, hashParams, numcams, cam); - -#ifdef _DEBUG - cudaSafeCall(cudaDeviceSynchronize()); -#endif -} - -#define RESAMPLE_RADIUS 7 - -__global__ void mls_resample_kernel(ftl::cuda::TextureObject<int> depthin, ftl::cuda::TextureObject<uchar4> colourin, ftl::cuda::TextureObject<float> depthout, HashParams hashParams, int numcams, SplatParams params) { - const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x; - const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y; - - const int width = depthin.width(); - const int height = depthin.height(); - - if (x < width && y < height) { - - //const int depth = depthin.tex2D((int)x, (int)y); - //if (depth != 0x7FFFFFFF) { - // depthout(x,y) = (float)depth / 1000.0f; - // return; - //} - - struct map_t { - int d; - int quad; - }; - - map_t mappings[5]; - int mapidx = 0; - - for (int v=-RESAMPLE_RADIUS; v<=RESAMPLE_RADIUS; ++v) { - for (int u=-RESAMPLE_RADIUS; u<=RESAMPLE_RADIUS; ++u) { - - const int depth = depthin.tex2D((int)x+u, (int)y+v); - const uchar4 c1 = colourin.tex2D((int)x+u, (int)y+v); - - if (depth != 0x7FFFFFFF) { - int i=0; - for (i=0; i<mapidx; ++i) { - if (abs(mappings[i].d - depth) < 100) { - if (u < 0 && v < 0) mappings[i].quad |= 0x1; - if (u > 0 && v < 0) mappings[i].quad |= 0x2; - if (u > 0 && v > 0) mappings[i].quad |= 0x4; - if (u < 0 && v > 0) mappings[i].quad |= 0x8; - break; - } - } - if (i == mapidx && i < 5) { - mappings[mapidx].d = depth; - mappings[mapidx].quad = 0; - if (u < 0 && v < 0) mappings[mapidx].quad |= 0x1; - if (u > 0 && v < 0) mappings[mapidx].quad |= 0x2; - if (u > 0 && v > 0) mappings[mapidx].quad |= 0x4; - if (u < 0 && v > 0) mappings[mapidx].quad |= 0x8; - ++mapidx; - } else { - //printf("EXCEEDED\n"); - } - } - } - } - - int bestdepth = 1000000; - //int count = 0; - for (int i=0; i<mapidx; ++i) { - if (__popc(mappings[i].quad) >= 3 && mappings[i].d < bestdepth) bestdepth = mappings[i].d; - //if (mappings[i].quad == 15 && mappings[i].d < bestdepth) bestdepth = mappings[i].d; - //if (mappings[i].quad == 15) count ++; - } - - //depthout(x,y) = (mapidx == 5) ? 3.0f : 0.0f; - - if (bestdepth < 1000000) { - depthout(x,y) = (float)bestdepth / 1000.0f; - } - } -} - -void ftl::cuda::mls_resample(const TextureObject<int> &depthin, const TextureObject<uchar4> &colourin, TextureObject<float> &depthout, const HashParams &hashParams, int numcams, const SplatParams ¶ms, cudaStream_t stream) { - const dim3 gridSize((depthin.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (depthin.height() + T_PER_BLOCK - 1)/T_PER_BLOCK); - const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK); - - mls_resample_kernel<<<gridSize, blockSize, 0, stream>>>(depthin, colourin, depthout, hashParams, numcams, params); - -#ifdef _DEBUG - cudaSafeCall(cudaDeviceSynchronize()); -#endif -} - - /// ===== Median Filter ====== #define WINDOW_SIZE 3 diff --git a/applications/reconstruct/src/depth_camera_cuda.hpp b/applications/reconstruct/src/depth_camera_cuda.hpp index 552c2e59d44206c089fee68124ca814d3e752ad6..26bfcad73f5ae72f20cfe2dd29d0e91c62fca62b 100644 --- a/applications/reconstruct/src/depth_camera_cuda.hpp +++ b/applications/reconstruct/src/depth_camera_cuda.hpp @@ -10,8 +10,10 @@ namespace cuda { void clear_depth(const TextureObject<float> &depth, cudaStream_t stream); void clear_depth(const TextureObject<int> &depth, cudaStream_t stream); +void clear_to_zero(const ftl::cuda::TextureObject<float> &depth, cudaStream_t stream); void clear_points(const ftl::cuda::TextureObject<float4> &depth, cudaStream_t stream); void clear_colour(const ftl::cuda::TextureObject<uchar4> &depth, cudaStream_t stream); +void clear_colour(const ftl::cuda::TextureObject<float4> &depth, cudaStream_t stream); void median_filter(const ftl::cuda::TextureObject<int> &in, ftl::cuda::TextureObject<float> &out, cudaStream_t stream); @@ -21,7 +23,7 @@ void float_to_int(const ftl::cuda::TextureObject<float> &in, ftl::cuda::TextureO void mls_smooth(TextureObject<float4> &output, const ftl::voxhash::HashParams &hashParams, int numcams, int cam, cudaStream_t stream); -void mls_resample(const TextureObject<int> &depthin, const TextureObject<uchar4> &colourin, TextureObject<float> &depthout, const ftl::voxhash::HashParams &hashParams, int numcams, const ftl::render::SplatParams ¶ms, cudaStream_t stream); +void mls_render_depth(const TextureObject<int> &input, TextureObject<int> &output, const ftl::render::SplatParams ¶ms, int numcams, cudaStream_t stream); void hole_fill(const TextureObject<int> &depth_in, const TextureObject<float> &depth_out, const DepthCameraParams ¶ms, cudaStream_t stream); diff --git a/applications/reconstruct/src/dibr.cu b/applications/reconstruct/src/dibr.cu deleted file mode 100644 index 9558a0e0d2ac4b7a3978ccd48a6f6791f99a337f..0000000000000000000000000000000000000000 --- a/applications/reconstruct/src/dibr.cu +++ /dev/null @@ -1,221 +0,0 @@ -#include "splat_render_cuda.hpp" -#include <cuda_runtime.h> - -#include <ftl/cuda_matrix_util.hpp> - -#include "splat_params.hpp" -#include <ftl/depth_camera.hpp> - -#define T_PER_BLOCK 8 - -using ftl::cuda::TextureObject; -using ftl::render::SplatParams; - -extern __constant__ ftl::voxhash::DepthCameraCUDA c_cameras[MAX_CAMERAS]; - -__global__ void clearColourKernel(TextureObject<uchar4> colour) { - const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x; - const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y; - - if (x < colour.width() && y < colour.height()) { - //depth(x,y) = 0x7f800000; //PINF; - colour(x,y) = make_uchar4(76,76,82,0); - } -} - - -__global__ void dibr_depthmap_kernel( - TextureObject<int> depth, int numcams, SplatParams params) { - - const int i = threadIdx.y*blockDim.y + threadIdx.x; - const int bx = blockIdx.x*blockDim.x; - const int by = blockIdx.y*blockDim.y; - const int x = bx + threadIdx.x; - const int y = by + threadIdx.y; - - for (int j=0; j<numcams; ++j) { - const ftl::voxhash::DepthCameraCUDA camera = c_cameras[j]; - - float4 d = tex2D<float4>(camera.points, x, y); - if (d.z < 0.0f) continue; - //if (d >= params.camera.m_sensorDepthWorldMax) continue; - - //const float3 worldPos = camera.pose * camera.params.kinectDepthToSkeleton(x, y, d); - - const float3 worldPos = make_float3(d); - const float3 camPos = params.m_viewMatrix * worldPos; - const float2 screenPosf = params.camera.cameraToKinectScreenFloat(camPos); - const uint2 screenPos = make_uint2(make_int2(screenPosf)); - - if (camPos.z < params.camera.m_sensorDepthWorldMin) continue; - - const unsigned int cx = screenPos.x; - const unsigned int cy = screenPos.y; - - - if (cx < depth.width() && cy < depth.height()) { - //float camd = depth_in.tex2D((int)cx,(int)cy); - //atomicMin(&depth(x,y), idepth); - //float camdiff = fabs(camPos.z-camd); - //if (camdiff < 0.1f) { - //colour_out(cx,cy) = tex2D<uchar4>(camera.colour,x,y); - //} else { - //colour_out(cx,cy) = make_uchar4(camdiff * 100, 0, 0, 255); - //} - - atomicMin(&depth(cx,cy), camPos.z * 1000.0f); - } - } -} - - -__global__ void dibr_kernel( - TextureObject<int> depth_in, - TextureObject<uchar4> colour_out, int numcams, SplatParams params) { - - const int i = threadIdx.y*blockDim.y + threadIdx.x; - const int bx = blockIdx.x*blockDim.x; - const int by = blockIdx.y*blockDim.y; - const int x = bx + threadIdx.x; - const int y = by + threadIdx.y; - - for (int j=0; j<numcams; ++j) { - const ftl::voxhash::DepthCameraCUDA camera = c_cameras[j]; - - float4 d = tex2D<float4>(camera.points, x, y); - if (d.z < 0.0f) continue; - //if (d >= params.camera.m_sensorDepthWorldMax) continue; - - //const float3 worldPos = camera.pose * camera.params.kinectDepthToSkeleton(x, y, d); - - const float3 worldPos = make_float3(d); - const float3 camPos = params.m_viewMatrix * worldPos; - const float2 screenPosf = params.camera.cameraToKinectScreenFloat(camPos); - const uint2 screenPos = make_uint2(make_int2(screenPosf)); - - if (camPos.z < params.camera.m_sensorDepthWorldMin) continue; - - const unsigned int cx = screenPos.x; - const unsigned int cy = screenPos.y; - - - if (cx < colour_out.width() && cy < colour_out.height()) { - //float camd = depth_in.tex2D((int)cx,(int)cy); - //atomicMin(&depth(x,y), idepth); - //float camdiff = fabs(camPos.z-camd); - //if (camdiff < 0.1f) { - - if (depth_in(cx,cy) == (int)(camPos.z * 1000.0f)) { - colour_out(cx,cy) = tex2D<uchar4>(camera.colour,x,y); - //colour_out(cx,cy) = (j==0) ? make_uchar4(20,255,0,255) : make_uchar4(20,0,255,255); - } - - - //} else { - //colour_out(cx,cy) = make_uchar4(camdiff * 100, 0, 0, 255); - //} - } - } -} - -__device__ inline float4 make_float4(const uchar4 &c) { - return make_float4(c.x,c.y,c.z,c.w); -} - -__global__ void dibr_kernel_rev( - TextureObject<float> depth_in, - TextureObject<uchar4> colour_out, int numcams, SplatParams params) { - - const int i = threadIdx.y*blockDim.y + threadIdx.x; - const int bx = blockIdx.x*blockDim.x; - const int by = blockIdx.y*blockDim.y; - const int x = bx + threadIdx.x; - const int y = by + threadIdx.y; - - float camd = depth_in.tex2D((int)x,(int)y); - if (camd < 0.01f) return; - if (camd >= params.camera.m_sensorDepthWorldMax) return; - - const float3 worldPos = params.m_viewMatrixInverse * params.camera.kinectDepthToSkeleton(x, y, camd); - float mindiff = 1000.0f; - float4 col = make_float4(0.0f,0.0f,0.0f,0.0f); - int count = 0; - - for (int j=0; j<numcams; ++j) { - const ftl::voxhash::DepthCameraCUDA camera = c_cameras[j]; - - const float3 camPos = camera.poseInverse * worldPos; - const float2 screenPosf = camera.params.cameraToKinectScreenFloat(camPos); - const uint2 screenPos = make_uint2(make_int2(screenPosf)); - - if (camPos.z < params.camera.m_sensorDepthWorldMin) continue; - - const unsigned int cx = screenPos.x; - const unsigned int cy = screenPos.y; - - if (cx < camera.params.m_imageWidth && cy < camera.params.m_imageHeight) { - float d = tex2D<float>(camera.depth, (int)cx, (int)cy); - float camdiff = fabs(camPos.z-d); - - if (camdiff < mindiff) { - mindiff = camdiff; - col += make_float4(tex2D<uchar4>(camera.colour,cx,cy)); - ++count; - } - - //if (camdiff < 0.1f) { - // colour_out(x,y) = tex2D<uchar4>(camera.colour,cx,cy); - //} else { - //colour_out(x,y) = make_uchar4(camdiff * 100, 0, 0, 255); - //} - } - } - - if (count > 0) { - col = col / (float)count; - colour_out(x,y) = make_uchar4(col.x,col.y,col.z,255); - } else { - colour_out(x,y) = make_uchar4(76,76,76,255); - } -} - -void ftl::cuda::dibr(const TextureObject<int> &depth_out, - const TextureObject<uchar4> &colour_out, int numcams, const SplatParams ¶ms, cudaStream_t stream) { - - const dim3 gridSize((depth_out.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (depth_out.height() + T_PER_BLOCK - 1)/T_PER_BLOCK); - const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK); - - clearColourKernel<<<gridSize, blockSize, 0, stream>>>(colour_out); - -#ifdef _DEBUG - cudaSafeCall(cudaDeviceSynchronize()); -#endif - - dibr_depthmap_kernel<<<gridSize, blockSize, 0, stream>>>(depth_out, numcams, params); - dibr_kernel<<<gridSize, blockSize, 0, stream>>>(depth_out, colour_out, numcams, params); - cudaSafeCall( cudaGetLastError() ); - -#ifdef _DEBUG - cudaSafeCall(cudaDeviceSynchronize()); -#endif -} - -void ftl::cuda::dibr(const TextureObject<float> &depth_out, - const TextureObject<uchar4> &colour_out, int numcams, const SplatParams ¶ms, cudaStream_t stream) { - - const dim3 gridSize((depth_out.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (depth_out.height() + T_PER_BLOCK - 1)/T_PER_BLOCK); - const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK); - - clearColourKernel<<<gridSize, blockSize, 0, stream>>>(colour_out); - -#ifdef _DEBUG - cudaSafeCall(cudaDeviceSynchronize()); -#endif - - dibr_kernel_rev<<<gridSize, blockSize, 0, stream>>>(depth_out, colour_out, numcams, params); - cudaSafeCall( cudaGetLastError() ); - -#ifdef _DEBUG - cudaSafeCall(cudaDeviceSynchronize()); -#endif -} diff --git a/applications/reconstruct/src/garbage.cu b/applications/reconstruct/src/garbage.cu deleted file mode 100644 index b685e9e6b7d94434ff425eff268699a715261522..0000000000000000000000000000000000000000 --- a/applications/reconstruct/src/garbage.cu +++ /dev/null @@ -1,135 +0,0 @@ -#include <ftl/voxel_hash.hpp> -#include "garbage.hpp" - -using namespace ftl::voxhash; - -#define T_PER_BLOCK 8 -#define NUM_CUDA_BLOCKS 10000 - -/*__global__ void starveVoxelsKernel(HashData hashData) { - int ptr; - - // Stride over all allocated blocks - for (int bi=blockIdx.x; bi<*hashData.d_hashCompactifiedCounter; bi+=NUM_CUDA_BLOCKS) { - - ptr = hashData.d_hashCompactified[bi].ptr; - int weight = hashData.d_SDFBlocks[ptr + threadIdx.x].weight; - weight = max(0, weight-2); - hashData.d_SDFBlocks[ptr + threadIdx.x].weight = weight; //CHECK Remove to totally clear previous frame (Nick) - - } -} - -void ftl::cuda::starveVoxels(HashData& hashData, const HashParams& hashParams, cudaStream_t stream) { - const unsigned int threadsPerBlock = SDF_BLOCK_SIZE*SDF_BLOCK_SIZE*SDF_BLOCK_SIZE; - const dim3 gridSize(NUM_CUDA_BLOCKS, 1); - const dim3 blockSize(threadsPerBlock, 1); - - //if (hashParams.m_numOccupiedBlocks > 0) { - starveVoxelsKernel << <gridSize, blockSize, 0, stream >> >(hashData); - //} -#ifdef _DEBUG - cudaSafeCall(cudaDeviceSynchronize()); - //cutilCheckMsg(__FUNCTION__); -#endif -}*/ - -#define ENTRIES_PER_BLOCK 4 - -__global__ void clearVoxelsKernel(HashData hashData) { - const int lane = threadIdx.x % 16; - const int halfWarp = threadIdx.x / 16; - - // Stride over all allocated blocks - for (int bi=blockIdx.x+halfWarp; bi<*hashData.d_hashCompactifiedCounter; bi+=NUM_CUDA_BLOCKS*ENTRIES_PER_BLOCK) { - - HashEntry *entry = hashData.d_hashCompactified[bi]; - //hashData.d_SDFBlocks[entry.ptr + threadIdx.x].weight = 0; - entry->voxels[lane] = 0; - - } -} - -void ftl::cuda::clearVoxels(HashData& hashData, const HashParams& hashParams) { - const unsigned int threadsPerBlock = 16 * ENTRIES_PER_BLOCK; - const dim3 gridSize(NUM_CUDA_BLOCKS, 1); - const dim3 blockSize(threadsPerBlock, 1); - - clearVoxelsKernel << <gridSize, blockSize >> >(hashData); -} - - -__global__ void garbageCollectIdentifyKernel(HashData hashData) { - const int lane = threadIdx.x % 16; - const int halfWarp = threadIdx.x / 16; - - // Stride over all allocated blocks - for (int bi=blockIdx.x+halfWarp; bi<*hashData.d_hashCompactifiedCounter; bi+=NUM_CUDA_BLOCKS * ENTRIES_PER_BLOCK) { - - const HashEntry *entry = hashData.d_hashCompactified[bi]; - - const uint v = entry->voxels[lane]; - const uint mask = (halfWarp & 0x1) ? 0xFFFF0000 : 0x0000FFFF; - uint ballot_result = __ballot_sync(mask, v == 0 || v == 0xFFFFFFFF); - - if (lane == 0) hashData.d_hashDecision[bi] = (ballot_result == mask) ? 1 : 0; - - } -} - -void ftl::cuda::garbageCollectIdentify(HashData& hashData, const HashParams& hashParams, cudaStream_t stream) { - - const unsigned int threadsPerBlock = SDF_BLOCK_SIZE * SDF_BLOCK_SIZE * SDF_BLOCK_SIZE / 2; - const dim3 gridSize(NUM_CUDA_BLOCKS, 1); - const dim3 blockSize(threadsPerBlock, 1); - - //if (hashParams.m_numOccupiedBlocks > 0) { - garbageCollectIdentifyKernel << <gridSize, blockSize, 0, stream >> >(hashData); - //} -#ifdef _DEBUG - cudaSafeCall(cudaDeviceSynchronize()); - //cutilCheckMsg(__FUNCTION__); -#endif -} - - -__global__ void garbageCollectFreeKernel(HashData hashData) { - - // Stride over all allocated blocks - for (int bi=blockIdx.x*blockDim.x + threadIdx.x; bi<*hashData.d_hashCompactifiedCounter; bi+=NUM_CUDA_BLOCKS*blockDim.x) { - - HashEntry *entry = hashData.d_hashCompactified[bi]; - - if ((entry->head.flags & ftl::voxhash::kFlagSurface) == 0) { //decision to delete the hash entry - - - //if (entry->head.offset == FREE_ENTRY) return; //should never happen since we did compactify before - - int3 posI3 = make_int3(entry->head.posXYZ.x, entry->head.posXYZ.y, entry->head.posXYZ.z); - - if (hashData.deleteHashEntryElement(posI3)) { //delete hash entry from hash (and performs heap append) - //#pragma unroll - //for (uint i = 0; i < 16; i++) { //clear sdf block: CHECK TODO another kernel? - // entry->voxels[i] = 0; - //} - } - } - - } -} - - -void ftl::cuda::garbageCollectFree(HashData& hashData, const HashParams& hashParams, cudaStream_t stream) { - - const unsigned int threadsPerBlock = T_PER_BLOCK*T_PER_BLOCK; - const dim3 gridSize(NUM_CUDA_BLOCKS, 1); // (hashParams.m_numOccupiedBlocks + threadsPerBlock - 1) / threadsPerBlock - const dim3 blockSize(threadsPerBlock, 1); - - //if (hashParams.m_numOccupiedBlocks > 0) { - garbageCollectFreeKernel << <gridSize, blockSize, 0, stream >> >(hashData); - //} -#ifdef _DEBUG - cudaSafeCall(cudaDeviceSynchronize()); - //cutilCheckMsg(__FUNCTION__); -#endif -} diff --git a/applications/reconstruct/src/garbage.hpp b/applications/reconstruct/src/garbage.hpp deleted file mode 100644 index 5d1d7574d252b40da18008da39f1bf89a7d667fb..0000000000000000000000000000000000000000 --- a/applications/reconstruct/src/garbage.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef _FTL_RECONSTRUCTION_GARBAGE_HPP_ -#define _FTL_RECONSTRUCTION_GARBAGE_HPP_ - -namespace ftl { -namespace cuda { - -void clearVoxels(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams); -void starveVoxels(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams, cudaStream_t stream); -void garbageCollectIdentify(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams, cudaStream_t stream); -void garbageCollectFree(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams, cudaStream_t stream); - -} -} - -#endif // _FTL_RECONSTRUCTION_GARBAGE_HPP_ diff --git a/applications/reconstruct/src/ilw.cpp b/applications/reconstruct/src/ilw.cpp new file mode 100644 index 0000000000000000000000000000000000000000..86a4cca5e4f82047ed6591a137b694788da879eb --- /dev/null +++ b/applications/reconstruct/src/ilw.cpp @@ -0,0 +1,120 @@ +#include "ilw.hpp" +#include <ftl/utility/matrix_conversion.hpp> +#include <ftl/rgbd/source.hpp> +#include <ftl/cuda/points.hpp> +#include <loguru.hpp> + +#include "ilw_cuda.hpp" + +using ftl::ILW; +using ftl::detail::ILWData; +using ftl::rgbd::Channel; +using ftl::rgbd::Channels; +using ftl::rgbd::Format; +using cv::cuda::GpuMat; + +ILW::ILW(nlohmann::json &config) : ftl::Configurable(config) { + +} + +ILW::~ILW() { + +} + +bool ILW::process(ftl::rgbd::FrameSet &fs, cudaStream_t stream) { + _phase0(fs, stream); + + //for (int i=0; i<2; ++i) { + _phase1(fs, stream); + //for (int j=0; j<3; ++j) { + // _phase2(fs); + //} + + // TODO: Break if no time left + //} + + return true; +} + +bool ILW::_phase0(ftl::rgbd::FrameSet &fs, cudaStream_t stream) { + // Make points channel... + for (size_t i=0; i<fs.frames.size(); ++i) { + auto &f = fs.frames[i]; + auto *s = fs.sources[i]; + + if (f.empty(Channel::Depth + Channel::Colour)) { + LOG(ERROR) << "Missing required channel"; + continue; + } + + auto &t = f.createTexture<float4>(Channel::Points, Format<float4>(f.get<GpuMat>(Channel::Colour).size())); + auto pose = MatrixConversion::toCUDA(s->getPose().cast<float>()); //.inverse()); + ftl::cuda::point_cloud(t, f.createTexture<float>(Channel::Depth), s->parameters(), pose, stream); + + // TODO: Create energy vector texture and clear it + // Create energy and clear it + + // Convert colour from BGR to BGRA if needed + if (f.get<GpuMat>(Channel::Colour).type() == CV_8UC3) { + // Convert to 4 channel colour + auto &col = f.get<GpuMat>(Channel::Colour); + GpuMat tmp(col.size(), CV_8UC4); + cv::cuda::swap(col, tmp); + cv::cuda::cvtColor(tmp,col, cv::COLOR_BGR2BGRA); + } + + f.createTexture<float4>(Channel::EnergyVector, Format<float4>(f.get<GpuMat>(Channel::Colour).size())); + f.createTexture<float>(Channel::Energy, Format<float>(f.get<GpuMat>(Channel::Colour).size())); + f.createTexture<uchar4>(Channel::Colour); + } + + return true; +} + +bool ILW::_phase1(ftl::rgbd::FrameSet &fs, cudaStream_t stream) { + // Run correspondence kernel to create an energy vector + + // For each camera combination + for (size_t i=0; i<fs.frames.size(); ++i) { + for (size_t j=0; j<fs.frames.size(); ++j) { + if (i == j) continue; + + LOG(INFO) << "Running phase1"; + + auto &f1 = fs.frames[i]; + auto &f2 = fs.frames[j]; + //auto s1 = fs.frames[i]; + auto s2 = fs.sources[j]; + + auto pose = MatrixConversion::toCUDA(s2->getPose().cast<float>().inverse()); + + try { + //Calculate energy vector to best correspondence + ftl::cuda::correspondence_energy_vector( + f1.getTexture<float4>(Channel::Points), + f2.getTexture<float4>(Channel::Points), + f1.getTexture<uchar4>(Channel::Colour), + f2.getTexture<uchar4>(Channel::Colour), + // TODO: Add normals and other things... + f1.getTexture<float4>(Channel::EnergyVector), + f1.getTexture<float>(Channel::Energy), + pose, + s2->parameters(), + stream + ); + } catch (ftl::exception &e) { + LOG(ERROR) << "Exception in correspondence: " << e.what(); + } + + LOG(INFO) << "Correspondences done... " << i; + } + } + + return true; +} + +bool ILW::_phase2(ftl::rgbd::FrameSet &fs) { + // Run energies and motion kernel + + return true; +} diff --git a/applications/reconstruct/src/ilw.cu b/applications/reconstruct/src/ilw.cu new file mode 100644 index 0000000000000000000000000000000000000000..90133a3a57800ee87a91fd50902deea5f701258a --- /dev/null +++ b/applications/reconstruct/src/ilw.cu @@ -0,0 +1,86 @@ +#include "ilw_cuda.hpp" + +using ftl::cuda::TextureObject; +using ftl::rgbd::Camera; + +#define WARP_SIZE 32 +#define T_PER_BLOCK 8 +#define FULL_MASK 0xffffffff + +__device__ inline float warpMax(float e) { + for (int i = WARP_SIZE/2; i > 0; i /= 2) { + const float other = __shfl_xor_sync(FULL_MASK, e, i, WARP_SIZE); + e = max(e, other); + } + return e; +} + +__global__ void correspondence_energy_vector_kernel( + TextureObject<float4> p1, + TextureObject<float4> p2, + TextureObject<uchar4> c1, + TextureObject<uchar4> c2, + TextureObject<float4> vout, + TextureObject<float> eout, + float4x4 pose2, // Inverse + Camera cam2) { + + // Each warp picks point in p1 + const int tid = (threadIdx.x + threadIdx.y * blockDim.x); + const int x = (blockIdx.x*blockDim.x + threadIdx.x) / WARP_SIZE; + const int y = blockIdx.y*blockDim.y + threadIdx.y; + + const float3 world1 = make_float3(p1.tex2D(x, y)); + const float3 camPos2 = pose2 * world1; + const uint2 screen2 = cam2.camToScreen<uint2>(camPos2); + + const int upsample = 8; + + // Project to p2 using cam2 + // Each thread takes a possible correspondence and calculates a weighting + const int lane = tid % WARP_SIZE; + for (int i=lane; i<upsample*upsample; i+=WARP_SIZE) { + const float u = (i % upsample) - (upsample / 2); + const float v = (i / upsample) - (upsample / 2); + + const float3 world2 = make_float3(p2.tex2D(screen2.x+u, screen2.y+v)); + + // Determine degree of correspondence + const float confidence = 1.0f / length(world1 - world2); + + printf("conf %f\n", confidence); + const float maxconf = warpMax(confidence); + + // This thread has best confidence value + if (maxconf == confidence) { + vout(x,y) = vout.tex2D(x, y) + make_float4( + (world1.x - world2.x) * maxconf, + (world1.y - world2.y) * maxconf, + (world1.z - world2.z) * maxconf, + maxconf); + eout(x,y) = eout.tex2D(x,y) + length(world1 - world2)*maxconf; + } + } +} + +void ftl::cuda::correspondence_energy_vector( + TextureObject<float4> &p1, + TextureObject<float4> &p2, + TextureObject<uchar4> &c1, + TextureObject<uchar4> &c2, + TextureObject<float4> &vout, + TextureObject<float> &eout, + float4x4 &pose2, + const Camera &cam2, + cudaStream_t stream) { + + const dim3 gridSize((p1.width() + 2 - 1)/2, (p1.height() + T_PER_BLOCK - 1)/T_PER_BLOCK); + const dim3 blockSize(2*WARP_SIZE, T_PER_BLOCK); + + printf("COR SIZE %d,%d\n", p1.width(), p1.height()); + + correspondence_energy_vector_kernel<<<gridSize, blockSize, 0, stream>>>( + p1, p2, c1, c2, vout, eout, pose2, cam2 + ); + cudaSafeCall( cudaGetLastError() ); +} diff --git a/applications/reconstruct/src/ilw.hpp b/applications/reconstruct/src/ilw.hpp new file mode 100644 index 0000000000000000000000000000000000000000..0be45d015e976b540263a2c16cc5605376092a43 --- /dev/null +++ b/applications/reconstruct/src/ilw.hpp @@ -0,0 +1,66 @@ +#ifndef _FTL_RECONSTRUCT_ILW_HPP_ +#define _FTL_RECONSTRUCT_ILW_HPP_ + +#include <ftl/cuda_common.hpp> +#include <ftl/rgbd/frameset.hpp> +#include <ftl/configurable.hpp> +#include <vector> + +namespace ftl { + +namespace detail { +struct ILWData{ + // x,y,z + confidence + ftl::cuda::TextureObject<float4> correspondence; + + ftl::cuda::TextureObject<float4> points; + + // Residual potential energy + ftl::cuda::TextureObject<float> residual; + + // Flow magnitude + ftl::cuda::TextureObject<float> flow; +}; +} + +/** + * For a set of sources, perform Iterative Lattice Warping to correct the + * location of points between the cameras. The algorithm finds possible + * correspondences and warps the original pixel lattice of points in each + * camera towards the correspondences, iterating the process as many times as + * possible. The result is that both local and global adjustment is made to the + * point clouds to improve micro alignment that may have been incorrect due to + * either inaccurate camera pose estimation or noise/errors in the depth maps. + */ +class ILW : public ftl::Configurable { + public: + explicit ILW(nlohmann::json &config); + ~ILW(); + + /** + * Take a frameset and perform the iterative lattice warping. + */ + bool process(ftl::rgbd::FrameSet &fs, cudaStream_t stream=0); + + private: + /* + * Initialise data. + */ + bool _phase0(ftl::rgbd::FrameSet &fs, cudaStream_t stream); + + /* + * Find possible correspondences and a confidence value. + */ + bool _phase1(ftl::rgbd::FrameSet &fs, cudaStream_t stream); + + /* + * Calculate energies and move the points. + */ + bool _phase2(ftl::rgbd::FrameSet &fs); + + std::vector<detail::ILWData> data_; +}; + +} + +#endif // _FTL_RECONSTRUCT_ILW_HPP_ diff --git a/applications/reconstruct/src/ilw_cuda.hpp b/applications/reconstruct/src/ilw_cuda.hpp new file mode 100644 index 0000000000000000000000000000000000000000..a01af75149409fe033ba39ffb0170489ee926be9 --- /dev/null +++ b/applications/reconstruct/src/ilw_cuda.hpp @@ -0,0 +1,26 @@ +#ifndef _FTL_ILW_CUDA_HPP_ +#define _FTL_ILW_CUDA_HPP_ + +#include <ftl/cuda_common.hpp> +#include <ftl/rgbd/camera.hpp> +#include <ftl/cuda_matrix_util.hpp> + +namespace ftl { +namespace cuda { + +void correspondence_energy_vector( + ftl::cuda::TextureObject<float4> &p1, + ftl::cuda::TextureObject<float4> &p2, + ftl::cuda::TextureObject<uchar4> &c1, + ftl::cuda::TextureObject<uchar4> &c2, + ftl::cuda::TextureObject<float4> &vout, + ftl::cuda::TextureObject<float> &eout, + float4x4 &pose2, + const ftl::rgbd::Camera &cam2, + cudaStream_t stream +); + +} +} + +#endif // _FTL_ILW_CUDA_HPP_ diff --git a/applications/reconstruct/src/integrators.cu b/applications/reconstruct/src/integrators.cu deleted file mode 100644 index 326c3dd9a59cd776e254149d47e02c684e1d6d36..0000000000000000000000000000000000000000 --- a/applications/reconstruct/src/integrators.cu +++ /dev/null @@ -1,342 +0,0 @@ -#include "integrators.hpp" -//#include <ftl/ray_cast_params.hpp> -#include <vector_types.h> -#include <cuda_runtime.h> -#include <ftl/cuda_matrix_util.hpp> -#include <ftl/cuda_util.hpp> -#include <ftl/cuda_common.hpp> - -#define T_PER_BLOCK 8 -#define NUM_CUDA_BLOCKS 10000 -#define WARP_SIZE 32 - -using ftl::voxhash::HashData; -using ftl::voxhash::HashParams; -using ftl::voxhash::Voxel; -using ftl::voxhash::HashEntry; -using ftl::voxhash::HashEntryHead; -using ftl::voxhash::FREE_ENTRY; - -extern __constant__ ftl::voxhash::DepthCameraCUDA c_cameras[MAX_CAMERAS]; -extern __constant__ HashParams c_hashParams; - -__device__ float4 make_float4(uchar4 c) { - return make_float4(static_cast<float>(c.x), static_cast<float>(c.y), static_cast<float>(c.z), static_cast<float>(c.w)); -} - -__device__ float colourDistance(const uchar4 &c1, const uchar3 &c2) { - float x = c1.x-c2.x; - float y = c1.y-c2.y; - float z = c1.z-c2.z; - return x*x + y*y + z*z; -} - -/* - * Kim, K., Chalidabhongse, T. H., Harwood, D., & Davis, L. (2005). - * Real-time foreground-background segmentation using codebook model. - * Real-Time Imaging. https://doi.org/10.1016/j.rti.2004.12.004 - */ -__device__ bool colordiff(const uchar4 &pa, const uchar3 &pb, float epsilon) { - float x_2 = pb.x * pb.x + pb.y * pb.y + pb.z * pb.z; - float v_2 = pa.x * pa.x + pa.y * pa.y + pa.z * pa.z; - float xv_2 = pow(pb.x * pa.x + pb.y * pa.y + pb.z * pa.z, 2); - float p_2 = xv_2 / v_2; - return sqrt(x_2 - p_2) < epsilon; -} - -/* - * Guennebaud, G.; Gross, M. Algebraic point set surfaces. ACMTransactions on Graphics Vol. 26, No. 3, Article No. 23, 2007. - * Used in: FusionMLS: Highly dynamic 3D reconstruction with consumer-grade RGB-D cameras - * r = distance between points - * h = smoothing parameter in meters (default 4cm) - */ -__device__ float spatialWeighting(float r) { - const float h = c_hashParams.m_spatialSmoothing; - if (r >= h) return 0.0f; - float rh = r / h; - rh = 1.0f - rh*rh; - return rh*rh*rh*rh; -} - -__device__ float spatialWeighting(float r, float h) { - //const float h = c_hashParams.m_spatialSmoothing; - if (r >= h) return 0.0f; - float rh = r / h; - rh = 1.0f - rh*rh; - return rh*rh*rh*rh; -} - - -__global__ void integrateDepthMapsKernel(HashData hashData, HashParams hashParams, int numcams) { - __shared__ uint all_warp_ballot; - __shared__ uint voxels[16]; - - const uint i = threadIdx.x; //inside of an SDF block - const int3 po = make_int3(hashData.delinearizeVoxelIndex(i)); - - // Stride over all allocated blocks - for (int bi=blockIdx.x; bi<*hashData.d_hashCompactifiedCounter; bi+=NUM_CUDA_BLOCKS) { - - //TODO check if we should load this in shared memory - //HashEntryHead entry = hashData.d_hashCompactified[bi]->head; - - int3 pi_base = hashData.SDFBlockToVirtualVoxelPos(make_int3(hashData.d_hashCompactified[bi]->head.posXYZ)); - - //uint idx = entry.offset + i; - int3 pi = pi_base + po; - float3 pfb = hashData.virtualVoxelPosToWorld(pi); - int count = 0; - //float camdepths[MAX_CAMERAS]; - - Voxel oldVoxel; // = hashData.d_SDFBlocks[idx]; - hashData.deleteVoxel(oldVoxel); - - for (uint cam=0; cam<numcams; ++cam) { - const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[cam]; - - float3 pf = camera.poseInverse * pfb; - uint2 screenPos = make_uint2(camera.params.cameraToKinectScreenInt(pf)); - - // For this voxel in hash, get its screen position and check it is on screen - if (screenPos.x < camera.params.m_imageWidth && screenPos.y < camera.params.m_imageHeight) { //on screen - - //float depth = g_InputDepth[screenPos]; - float depth = tex2D<float>(camera.depth, screenPos.x, screenPos.y); - //if (depth > 20.0f) return; - - //uchar4 color = make_uchar4(0, 0, 0, 0); - //if (cameraData.d_colorData) { - //color = (cam == 0) ? make_uchar4(255,0,0,255) : make_uchar4(0,0,255,255); - //color = tex2D<uchar4>(camera.colour, screenPos.x, screenPos.y); - //color = bilinearFilterColor(cameraData.cameraToKinectScreenFloat(pf)); - //} - - //printf("screen pos %d\n", color.x); - //return; - - // TODO:(Nick) Accumulate weighted positions - // TODO:(Nick) Accumulate weighted normals - // TODO:(Nick) Accumulate weights - - // Depth is within accepted max distance from camera - if (depth > 0.01f && depth < hashParams.m_maxIntegrationDistance) { // valid depth and color (Nick: removed colour check) - //camdepths[count] = depth; - ++count; - - // Calculate SDF of this voxel wrt the depth map value - float sdf = depth - pf.z; - float truncation = hashData.getTruncation(depth); - float depthZeroOne = camera.params.cameraToKinectProjZ(depth); - - // Is this voxel close enough to cam for depth map value - // CHECK Nick: If is too close then free space violation so remove? - if (sdf > -truncation) // && depthZeroOne >= 0.0f && depthZeroOne <= 1.0f) //check if in truncation range should already be made in depth map computation - { - float weightUpdate = max(hashParams.m_integrationWeightSample * 1.5f * (1.0f-depthZeroOne), 1.0f); - - Voxel curr; //construct current voxel - curr.sdf = sdf; - curr.weight = weightUpdate; - //curr.color = make_uchar3(color.x, color.y, color.z); - - - //if (entry.flags != cameraParams.flags & 0xFF) { - // entry.flags = cameraParams.flags & 0xFF; - //hashData.d_SDFBlocks[idx].color = make_uchar3(0,0,0); - //} - - Voxel newVoxel; - //if (color.x == MINF) hashData.combineVoxelDepthOnly(hashData.d_SDFBlocks[idx], curr, newVoxel); - //else hashData.combineVoxel(hashData.d_SDFBlocks[idx], curr, newVoxel); - hashData.combineVoxel(oldVoxel, curr, newVoxel); - - oldVoxel = newVoxel; - - //Voxel prev = getVoxel(g_SDFBlocksSDFUAV, g_SDFBlocksRGBWUAV, idx); - //Voxel newVoxel = combineVoxel(curr, prev); - //setVoxel(g_SDFBlocksSDFUAV, g_SDFBlocksRGBWUAV, idx, newVoxel); - } - } else { - // Depth is invalid so what to do here? - // TODO(Nick) Use past voxel if available (set weight from 0 to 1) - - // Naive: need to know if this is a foreground voxel - //bool coldist = colordiff(color, hashData.d_SDFBlocks[idx].color, 5.0f); - //if (!coldist) ++count; - - } - } - } - - // Calculate voxel sign values across a warp - int warpNum = i / WARP_SIZE; - //uint ballot_result = __ballot_sync(0xFFFFFFFF, (oldVoxel.sdf >= 0.0f) ? 0 : 1); - uint ballot_result = __ballot_sync(0xFFFFFFFF, (fabs(oldVoxel.sdf) <= hashParams.m_virtualVoxelSize && oldVoxel.weight > 0) ? 1 : 0); - - // Aggregate each warp result into voxel mask - if (i % WARP_SIZE == 0) { - voxels[warpNum] = ballot_result; - } - - __syncthreads(); - - // Work out if block is occupied or not and save voxel masks - // TODO:(Nick) Is it faster to do this in a separate garbage kernel? - if (i < 16) { - const uint v = voxels[i]; - hashData.d_hashCompactified[bi]->voxels[i] = v; - const uint mask = 0x0000FFFF; - uint b1 = __ballot_sync(mask, v == 0xFFFFFFFF); - uint b2 = __ballot_sync(mask, v == 0); - if (i == 0) { - if (b1 != mask && b2 != mask) hashData.d_hashCompactified[bi]->head.flags |= ftl::voxhash::kFlagSurface; - else hashData.d_hashCompactified[bi]->head.flags &= ~ftl::voxhash::kFlagSurface; - } - } - - } -} - -#define WINDOW_RADIUS 1 -#define PATCH_SIZE 32 - -__global__ void integrateMLSKernel(HashData hashData, HashParams hashParams, int numcams) { - __shared__ uint voxels[16]; - - const uint i = threadIdx.x; //inside of an SDF block - const int3 po = make_int3(hashData.delinearizeVoxelIndex(i)); - const int warpNum = i / WARP_SIZE; - const int lane = i % WARP_SIZE; - - // Stride over all allocated blocks - for (int bi=blockIdx.x; bi<*hashData.d_hashCompactifiedCounter; bi+=NUM_CUDA_BLOCKS) { - - //TODO check if we should load this in shared memory - //HashEntryHead entry = hashData.d_hashCompactified[bi]->head; - - const int3 pi_base = hashData.SDFBlockToVirtualVoxelPos(make_int3(hashData.d_hashCompactified[bi]->head.posXYZ)); - - //uint idx = entry.offset + i; - const int3 pi = pi_base + po; - const float3 pfb = hashData.virtualVoxelPosToWorld(pi); - //int count = 0; - //float camdepths[MAX_CAMERAS]; - - //Voxel oldVoxel; // = hashData.d_SDFBlocks[idx]; - //hashData.deleteVoxel(oldVoxel); - - //float3 awpos = make_float3(0.0f); - //float3 awnorm = make_float3(0.0f); - //float aweights = 0.0f; - float sdf = 0.0f; - float weights = 0.0f; - - // Preload depth values - // 1. Find min and max screen positions - // 2. Subtract/Add WINDOW_RADIUS to min/max - // ... check that the buffer is not too small to cover this - // ... if buffer not big enough then don't buffer at all. - // 3. Populate shared mem depth map buffer using all threads - // 4. Adjust window lookups to use shared mem buffer - - //uint cam=0; - for (uint cam=0; cam<numcams; ++cam) { - const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[cam]; - const uint height = camera.params.m_imageHeight; - const uint width = camera.params.m_imageWidth; - - const float3 pf = camera.poseInverse * pfb; - const uint2 screenPos = make_uint2(camera.params.cameraToKinectScreenInt(pf)); - - //float3 wpos = make_float3(0.0f); - float3 wnorm = make_float3(0.0f); - - - #pragma unroll - for (int v=-WINDOW_RADIUS; v<=WINDOW_RADIUS; ++v) { - for (int u=-WINDOW_RADIUS; u<=WINDOW_RADIUS; ++u) { - if (screenPos.x+u < width && screenPos.y+v < height) { //on screen - float4 depth = tex2D<float4>(camera.points, screenPos.x+u, screenPos.y+v); - if (depth.z == MINF) continue; - - //float4 normal = tex2D<float4>(camera.normal, screenPos.x+u, screenPos.y+v); - const float3 camPos = camera.poseInverse * make_float3(depth); //camera.pose * camera.params.kinectDepthToSkeleton(screenPos.x+u, screenPos.y+v, depth); - const float weight = spatialWeighting(length(pf - camPos)); - - //wpos += weight*worldPos; - sdf += weight*(camPos.z - pf.z); - //sdf += camPos.z - pf.z; - //wnorm += weight*make_float3(normal); - //weights += 1.0f; - weights += weight; - } - } - } - - //awpos += wpos; - //aweights += weights; - } - - //awpos /= aweights; - //wnorm /= weights; - - sdf /= weights; - - //float sdf = (aweights == 0.0f) ? MINF : length(pfb - awpos); - //float sdf = wnorm.x * (pfb.x - wpos.x) + wnorm.y * (pfb.y - wpos.y) + wnorm.z * (pfb.z - wpos.z); - - //printf("WEIGHTS: %f\n", weights); - - //if (weights < 0.00001f) sdf = 0.0f; - - // Calculate voxel sign values across a warp - int warpNum = i / WARP_SIZE; - - //uint solid_ballot = __ballot_sync(0xFFFFFFFF, (fabs(sdf) < hashParams.m_virtualVoxelSize && aweights >= 0.5f) ? 1 : 0); - //uint solid_ballot = __ballot_sync(0xFFFFFFFF, (fabs(sdf) <= hashParams.m_virtualVoxelSize) ? 1 : 0); - //uint solid_ballot = __ballot_sync(0xFFFFFFFF, (aweights >= 0.0f) ? 1 : 0); - uint solid_ballot = __ballot_sync(0xFFFFFFFF, (sdf < 0.0f ) ? 1 : 0); - - // Aggregate each warp result into voxel mask - if (i % WARP_SIZE == 0) { - voxels[warpNum] = solid_ballot; - //valid[warpNum] = valid_ballot; - } - - __syncthreads(); - - // Work out if block is occupied or not and save voxel masks - // TODO:(Nick) Is it faster to do this in a separate garbage kernel? - if (i < 16) { - const uint v = voxels[i]; - hashData.d_hashCompactified[bi]->voxels[i] = v; - //hashData.d_hashCompactified[bi]->validity[i] = valid[i]; - const uint mask = 0x0000FFFF; - uint b1 = __ballot_sync(mask, v == 0xFFFFFFFF); - uint b2 = __ballot_sync(mask, v == 0); - if (i == 0) { - if (b1 != mask && b2 != mask) hashData.d_hashCompactified[bi]->head.flags |= ftl::voxhash::kFlagSurface; - else hashData.d_hashCompactified[bi]->head.flags &= ~ftl::voxhash::kFlagSurface; - } - } - - } -} - - - -void ftl::cuda::integrateDepthMaps(HashData& hashData, const HashParams& hashParams, int numcams, cudaStream_t stream) { -const unsigned int threadsPerBlock = SDF_BLOCK_SIZE*SDF_BLOCK_SIZE*SDF_BLOCK_SIZE; -const dim3 gridSize(NUM_CUDA_BLOCKS, 1); -const dim3 blockSize(threadsPerBlock, 1); - -//if (hashParams.m_numOccupiedBlocks > 0) { //this guard is important if there is no depth in the current frame (i.e., no blocks were allocated) - integrateMLSKernel << <gridSize, blockSize, 0, stream >> >(hashData, hashParams, numcams); -//} - -//cudaSafeCall( cudaGetLastError() ); -#ifdef _DEBUG -cudaSafeCall(cudaDeviceSynchronize()); -//cutilCheckMsg(__FUNCTION__); -#endif -} diff --git a/applications/reconstruct/src/integrators.hpp b/applications/reconstruct/src/integrators.hpp deleted file mode 100644 index 789551dd1fa7347bf02c518c8c5a73f6ae4269b4..0000000000000000000000000000000000000000 --- a/applications/reconstruct/src/integrators.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef _FTL_RECONSTRUCTION_INTEGRATORS_HPP_ -#define _FTL_RECONSTRUCTION_INTEGRATORS_HPP_ - -#include <ftl/voxel_hash.hpp> -#include <ftl/depth_camera.hpp> - -namespace ftl { -namespace cuda { - -/*void integrateDepthMap(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams, - const DepthCameraData& depthCameraData, const DepthCameraParams& depthCameraParams, cudaStream_t stream); - -void integrateRegistration(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams, - const DepthCameraData& depthCameraData, const DepthCameraParams& depthCameraParams, cudaStream_t stream); -*/ - -void integrateDepthMaps(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams, int numcams, cudaStream_t stream); - -} -} - -#endif // _FTL_RECONSTRUCTION_INTEGRATORS_HPP_ diff --git a/applications/reconstruct/src/main.cpp b/applications/reconstruct/src/main.cpp index 46629bd0d8a5a2ec0e2ebbf8f2168bd0ecdccb57..107ed5978cb65b28f19e4a1e6fa2cb346846b64a 100644 --- a/applications/reconstruct/src/main.cpp +++ b/applications/reconstruct/src/main.cpp @@ -9,13 +9,15 @@ #include <ftl/config.h> #include <ftl/configuration.hpp> #include <ftl/depth_camera.hpp> -#include <ftl/voxel_scene.hpp> #include <ftl/rgbd.hpp> -#include <ftl/virtual_source.hpp> +#include <ftl/rgbd/virtual.hpp> #include <ftl/rgbd/streamer.hpp> #include <ftl/slave.hpp> +#include <ftl/rgbd/group.hpp> +#include <ftl/threads.hpp> -#include "splat_render.hpp" +#include "ilw.hpp" +#include <ftl/render/splat_render.hpp> #include <string> #include <vector> @@ -36,6 +38,7 @@ using std::string; using std::vector; using ftl::rgbd::Source; using ftl::config::json_t; +using ftl::rgbd::Channel; using json = nlohmann::json; using std::this_thread::sleep_for; @@ -90,61 +93,77 @@ static void run(ftl::Configurable *root) { } } - ftl::voxhash::SceneRep *scene = ftl::create<ftl::voxhash::SceneRep>(root, "voxelhash"); - ftl::rgbd::Streamer *stream = ftl::create<ftl::rgbd::Streamer>(root, "stream", net); - ftl::rgbd::Source *virt = ftl::create<ftl::rgbd::Source>(root, "virtual", net); - ftl::render::Splatter *splat = new ftl::render::Splatter(scene); + ftl::rgbd::FrameSet scene_A; // Output of align process + ftl::rgbd::FrameSet scene_B; // Input of render process - //auto virtimpl = new ftl::rgbd::VirtualSource(virt); - //virt->customImplementation(virtimpl); - //virtimpl->setScene(scene); + //ftl::voxhash::SceneRep *scene = ftl::create<ftl::voxhash::SceneRep>(root, "voxelhash"); + ftl::rgbd::Streamer *stream = ftl::create<ftl::rgbd::Streamer>(root, "stream", net); + ftl::rgbd::VirtualSource *virt = ftl::create<ftl::rgbd::VirtualSource>(root, "virtual"); + ftl::render::Splatter *splat = ftl::create<ftl::render::Splatter>(root, "renderer", &scene_B); + ftl::rgbd::Group group; + ftl::ILW *align = ftl::create<ftl::ILW>(root, "merge"); + + // Generate virtual camera render when requested by streamer + virt->onRender([splat,virt,&scene_B](ftl::rgbd::Frame &out) { + virt->setTimestamp(scene_B.timestamp); + splat->render(virt, out); + }); stream->add(virt); for (size_t i=0; i<sources.size(); i++) { Source *in = sources[i]; - in->setChannel(ftl::rgbd::kChanDepth); - stream->add(in); - scene->addSource(in); + in->setChannel(Channel::Depth); + group.addSource(in); } - int active = sources.size(); - while (ftl::running) { - if (active == 0) { - LOG(INFO) << "Waiting for sources..."; - sleep_for(milliseconds(1000)); - } - - active = 0; - - if (!slave.isPaused()) { - // Mark voxels as cleared - scene->nextFrame(); - - // Grab, upload frames and allocate voxel blocks - active = scene->upload(); - - // Make sure previous virtual camera frame has finished rendering - //stream->wait(); - cudaSafeCall(cudaStreamSynchronize(scene->getIntegrationStream())); + stream->setLatency(4); // FIXME: This depends on source!? + stream->run(); + bool busy = false; - // Merge new frames into the voxel structure - scene->integrate(); + group.setLatency(4); + group.setName("ReconGroup"); + group.sync([splat,virt,&busy,&slave,&scene_A,&scene_B,&align](ftl::rgbd::FrameSet &fs) -> bool { + //cudaSetDevice(scene->getCUDADevice()); - //LOG(INFO) << "Allocated: " << scene->getOccupiedCount(); - - // Remove any redundant voxels - scene->garbage(); - - } else { - active = 1; + if (slave.isPaused()) return true; + + if (busy) { + LOG(INFO) << "Group frameset dropped: " << fs.timestamp; + return true; } - - splat->render(virt, scene->getIntegrationStream()); - - // Start virtual camera rendering and previous frame compression - stream->poll(); - } + busy = true; + + // Swap the entire frameset to allow rapid return + fs.swapTo(scene_A); + + ftl::pool.push([&scene_B,&scene_A,&busy,&slave,&align](int id) { + //cudaSetDevice(scene->getCUDADevice()); + // TODO: Release frameset here... + //cudaSafeCall(cudaStreamSynchronize(scene->getIntegrationStream())); + + UNIQUE_LOCK(scene_A.mtx, lk); + + // Send all frames to GPU, block until done? + scene_A.upload(Channel::Colour + Channel::Depth); // TODO: (Nick) Add scene stream. + //align->process(scene_A); + + // TODO: To use second GPU, could do a download, swap, device change, + // then upload to other device. Or some direct device-2-device copy. + scene_A.swapTo(scene_B); + LOG(INFO) << "Align complete... " << scene_A.timestamp; + busy = false; + }); + return true; + }); + + ftl::timer::stop(); + net->shutdown(); + delete align; + delete splat; + delete virt; + delete stream; + delete net; } int main(int argc, char **argv) { diff --git a/applications/reconstruct/src/mls.cu b/applications/reconstruct/src/mls.cu new file mode 100644 index 0000000000000000000000000000000000000000..4de8a07db023d21620f605cd3556913aeafe06cb --- /dev/null +++ b/applications/reconstruct/src/mls.cu @@ -0,0 +1,273 @@ +#include <ftl/cuda_common.hpp> +#include <ftl/cuda_util.hpp> +#include <ftl/depth_camera.hpp> +#include "depth_camera_cuda.hpp" + +#include "mls_cuda.hpp" + +#define T_PER_BLOCK 16 +#define MINF __int_as_float(0xff800000) + +using ftl::voxhash::DepthCameraCUDA; +using ftl::voxhash::HashData; +using ftl::voxhash::HashParams; +using ftl::cuda::TextureObject; +using ftl::render::SplatParams; + +extern __constant__ ftl::voxhash::DepthCameraCUDA c_cameras[MAX_CAMERAS]; +extern __constant__ HashParams c_hashParams; + +/// ===== MLS Smooth + +/* + * Kim, K., Chalidabhongse, T. H., Harwood, D., & Davis, L. (2005). + * Real-time foreground-background segmentation using codebook model. + * Real-Time Imaging. https://doi.org/10.1016/j.rti.2004.12.004 + */ + __device__ float colordiffFloat(const uchar4 &pa, const uchar4 &pb) { + const float x_2 = pb.x * pb.x + pb.y * pb.y + pb.z * pb.z; + const float v_2 = pa.x * pa.x + pa.y * pa.y + pa.z * pa.z; + const float xv_2 = powf(float(pb.x * pa.x + pb.y * pa.y + pb.z * pa.z), 2.0f); + const float p_2 = xv_2 / v_2; + return sqrt(x_2 - p_2); +} + +__device__ float colordiffFloat2(const uchar4 &pa, const uchar4 &pb) { + float3 delta = make_float3((float)pa.x - (float)pb.x, (float)pa.y - (float)pb.y, (float)pa.z - (float)pb.z); + return length(delta); +} + +/* + * Colour weighting as suggested in: + * C. Kuster et al. Spatio-Temporal Geometry Fusion for Multiple Hybrid Cameras using Moving Least Squares Surfaces. 2014. + * c = colour distance + */ + __device__ float colourWeighting(float c) { + const float h = c_hashParams.m_colourSmoothing; + if (c >= h) return 0.0f; + float ch = c / h; + ch = 1.0f - ch*ch; + return ch*ch*ch*ch; +} + +#define WINDOW_RADIUS 5 + +__device__ float mlsCamera(int cam, const float3 &mPos, uchar4 c1, float3 &wpos) { + const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[cam]; + + const float3 pf = camera.poseInverse * mPos; + float3 pos = make_float3(0.0f, 0.0f, 0.0f); + const uint2 screenPos = make_uint2(camera.params.cameraToKinectScreenInt(pf)); + float weights = 0.0f; + + //#pragma unroll + for (int v=-WINDOW_RADIUS; v<=WINDOW_RADIUS; ++v) { + for (int u=-WINDOW_RADIUS; u<=WINDOW_RADIUS; ++u) { + //if (screenPos.x+u < width && screenPos.y+v < height) { //on screen + float depth = tex2D<float>(camera.depth, screenPos.x+u, screenPos.y+v); + const float3 camPos = camera.params.kinectDepthToSkeleton(screenPos.x+u, screenPos.y+v, depth); + float weight = ftl::cuda::spatialWeighting(length(pf - camPos), c_hashParams.m_spatialSmoothing); + + if (weight > 0.0f) { + uchar4 c2 = tex2D<uchar4>(camera.colour, screenPos.x+u, screenPos.y+v); + weight *= colourWeighting(colordiffFloat2(c1,c2)); + + if (weight > 0.0f) { + wpos += weight* (camera.pose * camPos); + weights += weight; + } + } + //} + } + } + + //wpos += (camera.pose * pos); + + return weights; +} + +__device__ float mlsCameraNoColour(int cam, const float3 &mPos, uchar4 c1, const float4 &norm, float3 &wpos, float h) { + const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[cam]; + + const float3 pf = camera.poseInverse * mPos; + float3 pos = make_float3(0.0f, 0.0f, 0.0f); + const uint2 screenPos = make_uint2(camera.params.cameraToKinectScreenInt(pf)); + float weights = 0.0f; + + //#pragma unroll + for (int v=-WINDOW_RADIUS; v<=WINDOW_RADIUS; ++v) { + for (int u=-WINDOW_RADIUS; u<=WINDOW_RADIUS; ++u) { + //if (screenPos.x+u < width && screenPos.y+v < height) { //on creen + float depth = tex2D<float>(camera.depth, screenPos.x+u, screenPos.y+v); + const float3 camPos = camera.params.kinectDepthToSkeleton(screenPos.x+u, screenPos.y+v, depth); + + // TODO:(Nick) dot product of normals < 0 means the point + // should be ignored with a weight of 0 since it is facing the wrong direction + // May be good to simply weight using the dot product to give + // a stronger weight to those whose normals are closer + + float weight = ftl::cuda::spatialWeighting(length(pf - camPos), h); + + if (weight > 0.0f) { + float4 n2 = tex2D<float4>(camera.normal, screenPos.x+u, screenPos.y+v); + if (dot(make_float3(norm), make_float3(n2)) > 0.0f) { + + uchar4 c2 = tex2D<uchar4>(camera.colour, screenPos.x+u, screenPos.y+v); + + if (colourWeighting(colordiffFloat2(c1,c2)) > 0.0f) { + pos += weight*camPos; // (camera.pose * camPos); + weights += weight; + } + } + } + //} + } + } + + if (weights > 0.0f) wpos += (camera.pose * (pos / weights)) * weights; + + return weights; +} + + +__global__ void mls_smooth_kernel(ftl::cuda::TextureObject<float4> output, HashParams hashParams, int numcams, int cam) { + const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x; + const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y; + + const int width = output.width(); + const int height = output.height(); + + const DepthCameraCUDA &mainCamera = c_cameras[cam]; + + if (x < width && y < height) { + + const float depth = tex2D<float>(mainCamera.depth, x, y); + const uchar4 c1 = tex2D<uchar4>(mainCamera.colour, x, y); + const float4 norm = tex2D<float4>(mainCamera.normal, x, y); + //if (x == 400 && y == 200) printf("NORMX: %f\n", norm.x); + + float3 wpos = make_float3(0.0f); + float3 wnorm = make_float3(0.0f); + float weights = 0.0f; + + if (depth >= mainCamera.params.m_sensorDepthWorldMin && depth <= mainCamera.params.m_sensorDepthWorldMax) { + float3 mPos = mainCamera.pose * mainCamera.params.kinectDepthToSkeleton(x, y, depth); + + if ((!(hashParams.m_flags & ftl::voxhash::kFlagClipping)) || (mPos.x > hashParams.m_minBounds.x && mPos.x < hashParams.m_maxBounds.x && + mPos.y > hashParams.m_minBounds.y && mPos.y < hashParams.m_maxBounds.y && + mPos.z > hashParams.m_minBounds.z && mPos.z < hashParams.m_maxBounds.z)) { + + if (hashParams.m_flags & ftl::voxhash::kFlagMLS) { + for (uint cam2=0; cam2<numcams; ++cam2) { + //if (cam2 == cam) weights += mlsCameraNoColour(cam2, mPos, c1, wpos, c_hashParams.m_spatialSmoothing*0.1f); //weights += 0.5*mlsCamera(cam2, mPos, c1, wpos); + weights += mlsCameraNoColour(cam2, mPos, c1, norm, wpos, c_hashParams.m_spatialSmoothing); //*((cam == cam2)? 0.1f : 5.0f)); + + // Previous approach + //if (cam2 == cam) continue; + //weights += mlsCameraBest(cam2, mPos, c1, wpos); + } + wpos /= weights; + } else { + weights = 1000.0f; + wpos = mPos; + } + + //output(x,y) = (weights >= hashParams.m_confidenceThresh) ? make_float4(wpos, 0.0f) : make_float4(MINF,MINF,MINF,MINF); + + if (weights >= hashParams.m_confidenceThresh) output(x,y) = make_float4(wpos, 0.0f); + + //const uint2 screenPos = make_uint2(mainCamera.params.cameraToKinectScreenInt(mainCamera.poseInverse * wpos)); + //if (screenPos.x < output.width() && screenPos.y < output.height()) { + // output(screenPos.x,screenPos.y) = (weights >= hashParams.m_confidenceThresh) ? make_float4(wpos, 0.0f) : make_float4(MINF,MINF,MINF,MINF); + //} + } + } + } +} + +void ftl::cuda::mls_smooth(TextureObject<float4> &output, const HashParams &hashParams, int numcams, int cam, cudaStream_t stream) { + const dim3 gridSize((output.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (output.height() + T_PER_BLOCK - 1)/T_PER_BLOCK); + const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK); + + mls_smooth_kernel<<<gridSize, blockSize, 0, stream>>>(output, hashParams, numcams, cam); + +#ifdef _DEBUG + cudaSafeCall(cudaDeviceSynchronize()); +#endif +} + + +// ===== Render Depth using MLS ================================================ + +#define MAX_UPSAMPLE 5 +#define SAMPLE_BUFFER ((2*MAX_UPSAMPLE+1)*(2*MAX_UPSAMPLE+1)) +#define WARP_SIZE 32 +#define BLOCK_WIDTH 4 +#define MLS_RADIUS 5 +#define MLS_WIDTH (2*MLS_RADIUS+1) +#define MLS_SAMPLES (MLS_WIDTH*MLS_WIDTH) + +__global__ void mls_render_depth_kernel(const TextureObject<int> input, TextureObject<int> output, SplatParams params, int numcams) { + /*const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x; + const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y; + + const int width = output.width(); + const int height = output.height(); + + if (x < width && y < height) { + + const float depth = tex2D<float>(mainCamera.depth, x, y); + const uchar4 c1 = tex2D<uchar4>(mainCamera.colour, x, y); + const float4 norm = tex2D<float4>(mainCamera.normal, x, y); + //if (x == 400 && y == 200) printf("NORMX: %f\n", norm.x); + + float3 wpos = make_float3(0.0f); + float3 wnorm = make_float3(0.0f); + float weights = 0.0f; + + if (depth >= mainCamera.params.m_sensorDepthWorldMin && depth <= mainCamera.params.m_sensorDepthWorldMax) { + float3 mPos = mainCamera.pose * mainCamera.params.kinectDepthToSkeleton(x, y, depth); + + if ((!(hashParams.m_flags & ftl::voxhash::kFlagClipping)) || (mPos.x > hashParams.m_minBounds.x && mPos.x < hashParams.m_maxBounds.x && + mPos.y > hashParams.m_minBounds.y && mPos.y < hashParams.m_maxBounds.y && + mPos.z > hashParams.m_minBounds.z && mPos.z < hashParams.m_maxBounds.z)) { + + if (hashParams.m_flags & ftl::voxhash::kFlagMLS) { + for (uint cam2=0; cam2<numcams; ++cam2) { + //if (cam2 == cam) weights += mlsCameraNoColour(cam2, mPos, c1, wpos, c_hashParams.m_spatialSmoothing*0.1f); //weights += 0.5*mlsCamera(cam2, mPos, c1, wpos); + weights += mlsCameraNoColour(cam2, mPos, c1, norm, wpos, c_hashParams.m_spatialSmoothing); //*((cam == cam2)? 0.1f : 5.0f)); + + // Previous approach + //if (cam2 == cam) continue; + //weights += mlsCameraBest(cam2, mPos, c1, wpos); + } + wpos /= weights; + } else { + weights = 1000.0f; + wpos = mPos; + } + + //output(x,y) = (weights >= hashParams.m_confidenceThresh) ? make_float4(wpos, 0.0f) : make_float4(MINF,MINF,MINF,MINF); + + //if (weights >= hashParams.m_confidenceThresh) output(x,y) = make_float4(wpos, 0.0f); + + const uint2 screenPos = make_uint2(mainCamera.params.cameraToKinectScreenInt(mainCamera.poseInverse * wpos)); + if (screenPos.x < output.width() && screenPos.y < output.height()) { + output(screenPos.x,screenPos.y) = (weights >= hashParams.m_confidenceThresh) ? make_float4(wpos, 0.0f) : make_float4(MINF,MINF,MINF,MINF); + } + } + } + }*/ +} + + +void ftl::cuda::mls_render_depth(const TextureObject<int> &input, TextureObject<int> &output, const SplatParams ¶ms, int numcams, cudaStream_t stream) { + const dim3 gridSize((output.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (output.height() + T_PER_BLOCK - 1)/T_PER_BLOCK); + const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK); + + mls_render_depth_kernel<<<gridSize, blockSize, 0, stream>>>(input, output, params, numcams); + +#ifdef _DEBUG + cudaSafeCall(cudaDeviceSynchronize()); +#endif +} diff --git a/applications/reconstruct/src/ray_cast_sdf.cu b/applications/reconstruct/src/ray_cast_sdf.cu index a43b608429b1fad1ac160b188c9be6b084274154..10fd3e0b7b84ca694432abc7dc68fc513ad483eb 100644 --- a/applications/reconstruct/src/ray_cast_sdf.cu +++ b/applications/reconstruct/src/ray_cast_sdf.cu @@ -1,5 +1,5 @@ -#include <cuda_runtime.h> +//#include <cuda_runtime.h> #include <ftl/cuda_matrix_util.hpp> diff --git a/applications/reconstruct/src/registration.cpp b/applications/reconstruct/src/registration.cpp index 5b828242e168e08c9524e02470c7d187ff4a1279..dab5235445a61460c3f65fd35f75ab694cdb8128 100644 --- a/applications/reconstruct/src/registration.cpp +++ b/applications/reconstruct/src/registration.cpp @@ -1,26 +1,8 @@ #include <ftl/registration.hpp> - -#ifdef HAVE_PCL - +#include <fstream> #define LOGURU_WITH_STREAMS 1 #include <loguru.hpp> -#include <pcl/common/transforms.h> - -#include <pcl/registration/transformation_estimation_svd.h> -#include <pcl/registration/transformation_estimation_svd_scale.h> - -#include <pcl/segmentation/sac_segmentation.h> -#include <pcl/sample_consensus/method_types.h> -#include <pcl/sample_consensus/model_types.h> -#include <pcl/filters/project_inliers.h> -#include <pcl/ModelCoefficients.h> - -#include <pcl/io/pcd_io.h> -#include <pcl/registration/transformation_validation.h> -#include <pcl/registration/transformation_validation_euclidean.h> -#include <pcl/common/geometry.h> -//#include <pcl/registration/icp_nl.h> namespace ftl { namespace registration { @@ -34,10 +16,6 @@ using std::pair; using std::map; using std::optional; -using pcl::PointCloud; -using pcl::PointXYZ; -using pcl::PointXYZRGB; - using cv::Mat; using Eigen::Matrix4f; using Eigen::Matrix4d; @@ -86,489 +64,6 @@ bool saveTransformations(const string &path, map<string, Matrix4d> &data) { return true; } -// todo template: fitPlane<typename T>(PointCloud<T> cloud_in, PointCloud<T> cloud_out) -// -// Fit calibration pattern into plane using RANSAC + project points -// -pcl::ModelCoefficients::Ptr fitPlane(PointCloud<PointXYZ>::Ptr cloud_in, float distance_threshold=5.0) { - // TODO: include pattern in model (find best alignment of found points and return transformed reference?) - - pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients); - pcl::PointIndices::Ptr inliers(new pcl::PointIndices); - - // Estimate plane with RANSAC - pcl::SACSegmentation<PointXYZ> seg; - - seg.setOptimizeCoefficients(true); - seg.setModelType(pcl::SACMODEL_PLANE); - seg.setMethodType(pcl::SAC_RANSAC); - seg.setDistanceThreshold(distance_threshold); - seg.setInputCloud(cloud_in); - seg.segment(*inliers, *coefficients); - - return coefficients; -} - -float fitPlaneError(PointCloud<PointXYZ>::Ptr cloud_in, float distance_threshold=5.0) { - auto coefficients = fitPlane(cloud_in, distance_threshold); - PointCloud<PointXYZ> cloud_proj; - - // Project points into plane - pcl::ProjectInliers<PointXYZ> proj; - proj.setModelType(pcl::SACMODEL_PLANE); - proj.setInputCloud(cloud_in); - proj.setModelCoefficients(coefficients); - proj.filter(cloud_proj); - - CHECK(cloud_in->size() == cloud_proj.size()); - - // todo: which error score is suitable? (using MSE) - float score = 0.0; - for(size_t i = 0; i < cloud_proj.size(); i++) { - float d = pcl::geometry::distance(cloud_in->points[i], cloud_proj.points[i]); - score += d * d; - } - - return (score / cloud_proj.size()) * 10000000.0f; -} - -//template<typename T = PointXYZ> typename -PointCloud<PointXYZ>::Ptr cornersToPointCloud(const vector<cv::Point2f> &corners, const Mat &depth, const Camera &p) { - - int corners_len = corners.size(); - vector<cv::Vec3f> points(corners_len); - - const double CX = p.cx; - const double CY = p.cy; - const double FX = p.fx; - const double FY = p.fy; - - // Output point cloud - PointCloud<PointXYZ>::Ptr cloud(new PointCloud<PointXYZ>); - cloud->width = corners_len; - cloud->height = 1; - - // Follows cv::reprojectImageTo3D(..) - // https://github.com/opencv/opencv/blob/371bba8f54560b374fbcd47e7e02f015ac4969ad/modules/calib3d/src/calibration.cpp#L2998 - // documentation suggests using cv::perspectiveTransform(...) with sparse set of points - - for (int i = 0; i < corners_len; i++) { - double x = corners[i].x; - double y = corners[i].y; - double d = depth.at<float>((int) y, (int) x); // * 1000.0f; // todo: better estimation - - //cv::Vec4d homg_pt = Q_ * cv::Vec4d(x, y, d, 1.0); - //cv::Vec3d p = cv::Vec3d(homg_pt.val) / homg_pt[3]; - - PointXYZ point; - point.x = (((double)x + CX) / FX) * d; // / 1000.0f; - point.y = (((double)y + CY) / FY) * d; // / 1000.0f; - point.z = d; - - cloud->push_back(point); - } - - return cloud; -} - -bool findChessboardCorners(Mat &rgb, const Mat &depth, const Camera &p, const cv::Size pattern_size, PointCloud<PointXYZ>::Ptr &out, float error_threshold) { - vector<cv::Point2f> corners(pattern_size.width * pattern_size.height); - -#if CV_VERSION_MAJOR >= 4 - bool retval = cv::findChessboardCornersSB(rgb, pattern_size, corners); -#else - bool retval = cv::findChessboardCorners(rgb, pattern_size, corners); -#endif - - cv::drawChessboardCorners(rgb, pattern_size, Mat(corners), retval); - if (!retval) { return false; } - - auto corners_cloud = cornersToPointCloud(corners, depth, p); - // simple check that the values make some sense - float error = fitPlaneError(corners_cloud, error_threshold); // should use different parameter? - LOG(INFO) << "MSE against estimated plane: " << error; - - if (error > error_threshold) { - LOG(WARNING) << "too high error score for calibration pattern, threshold " << error_threshold; - return false; - } - - if (out) { *out += *corners_cloud; } // if cloud is valid, add the points - else { out = corners_cloud; } - return true; -} - -Eigen::Matrix4f findTransformation(vector<PointCloud<PointXYZ>::Ptr> clouds_source, vector<PointCloud<PointXYZ>::Ptr> clouds_target) { - size_t n_clouds = clouds_source.size(); - - Eigen::Matrix4f T, T_tmp, T_new; - T.setIdentity(); - - if ((clouds_source.size() != clouds_target.size()) || (n_clouds == 0)) { - LOG(ERROR) << "Input vectors have invalid sizes: clouds_source " << clouds_source.size() - << ", clouds_target " << clouds_target.size() << ", transformation can not be estimated"; - - return T; // identity - } - - // corresponding points have same indices (!!!) - int n_points = clouds_source[0]->width * clouds_source[0]->height; - vector<int> idx(n_points); - for (int i = 0; i < n_points; i++) { idx[i] = i; } - - pcl::registration::TransformationValidationEuclidean<PointXYZ, PointXYZ> validate; - pcl::registration::TransformationEstimationSVD<PointXYZ,PointXYZ> svd; - - double score_prev = std::numeric_limits<float>::max(); - - for (size_t i = 0; i < n_clouds; ++i) { - PointCloud<PointXYZ> source; - PointCloud<PointXYZ> target = *clouds_target[i]; - - pcl::transformPointCloud(*clouds_source[i], source, T); - svd.estimateRigidTransformation(source, idx, target, idx, T_new); - - // calculate new transformation - T_tmp = T_new * T; - - // score new transformation - double score = 0.0; - for (size_t j = 0; j < n_clouds; ++j) { - score += validate.validateTransformation(clouds_source[j], clouds_target[j], T_tmp); // CHECK Is use of T here a mistake?? - } - score /= n_clouds; - - // if score doesn't improve, do not use as T, otherwise update T and score - if (score < score_prev) { - T = T_tmp; - score_prev = score; - } - - LOG(INFO) << "Validation score: " << score; - } - - return T; -} - -Registration::Registration(nlohmann::json &config) : - ftl::Configurable(config) { - target_source_ = get<string>("targetsource"); - if (!target_source_) { - LOG(WARNING) << "targetsource not set"; - } -} - -Source* Registration::getSource(size_t idx) { - return sources_[idx]; -} - -bool Registration::isTargetSourceSet() { - return (bool) target_source_; -} - -bool Registration::isTargetSourceFound() { - for (Source* source : sources_ ) { - if (isTargetSource(source)) return true; - } - return false; -} - -bool Registration::isTargetSource(Source *source) { - if (target_source_) { return source->getID() == *target_source_; } - return false; -} - -bool Registration::isTargetSource(size_t idx) { - if (idx >= sources_.size()) return false; // assert - return isTargetSource(sources_[idx]); -} - -size_t Registration::getTargetSourceIdx() { - if (!target_source_) return 0; - for (size_t idx = 0; idx < sources_.size(); ++idx) { - if (isTargetSource(sources_[idx])) return idx; - } - - return 0; -} - -void Registration::addSource(Source *source) { - // TODO: check that source is not already included - sources_.push_back(source); -} - -/** - * @param adjacency matrix - * @param index of starting vertex - * @param (out) edges connecting each level - * @returns true if graph connected (all vertices visited), otherwise false - * - * Breadth First Search - */ -bool isConnected(vector<vector<bool>> matrix, size_t start_idx, vector<vector<pair<size_t, size_t>>> &edges) { -vector<bool> visited(matrix.size(), false); - DCHECK(start_idx < matrix.size()); - - edges.clear(); - vector<size_t> level { start_idx }; - - visited[start_idx] = true; - size_t visited_count = 1; - - while(level.size() != 0) { - vector<size_t> level_prev = level; - level = {}; - - vector<pair<size_t, size_t>> new_edges; - - for (size_t current : level_prev) { - for (size_t i = 0; i < matrix.size(); ++i) { - if (matrix[current][i] && !visited[i]) { - visited[i] = true; - visited_count += 1; - level.push_back(i); - // could also save each level's vertices - - new_edges.push_back(pair(current, i)); - } - }} - if (new_edges.size() > 0) edges.push_back(new_edges); - } - - return visited_count == matrix.size(); -} - -bool isConnected(vector<vector<bool>> matrix, size_t start_idx = 0) { - vector<vector<pair<size_t, size_t>>> edges; - return isConnected(matrix, start_idx, edges); -} - -/** - * @param Adjacency matrix - * @returns Vector containing degree of each vertex -*/ -vector<uint> verticleDegrees(vector<vector<bool>> matrix) { - vector<uint> res(matrix.size(), 0); - for (size_t i = 0; i < matrix.size(); ++i) { - for (size_t j = 0; j < matrix.size(); ++j) { - if (matrix[i][j]) res[i] = res[i] + 1; - }} - return res; -} - -bool Registration::connectedVisibility() { - return isConnected(visibility_, getTargetSourceIdx()); -} - -void Registration::resetVisibility() { - visibility_ = vector(sources_.size(), vector<bool>(sources_.size(), false)); -} - -void Registration::run() { - resetVisibility(); - - do { - vector<bool> visible(sources_.size(), false); - - for (size_t i = 0; i < sources_.size(); ++i) { - bool retval = findFeatures(sources_[i], i); - visible[i] = retval; - } - - for (size_t i = 0; i < visible.size(); ++i) { - for (size_t j = 0; j < visible.size(); ++j) { - bool val = visible[i] && visible[j]; - visibility_[i][j] = visibility_[i][j] || val; - visibility_[j][i] = visibility_[j][i] || val; - }} - } - while(processData()); -} - -bool Registration::findTransformations(map<string, Matrix4f> &data) { - vector<Matrix4f> T; - data.clear(); - - if (!findTransformations(T)) return false; - for (size_t i = 0; i < sources_.size(); ++i) { - data[sources_[i]->getURI()] = T[i]; - } - return true; -} - -ChessboardRegistration* ChessboardRegistration::create(nlohmann::json &config) { - if (config.value<bool>("chain", false)) { - return new ChessboardRegistrationChain(config); - } - else { - return new ChessboardRegistration(config); - } -} - -ChessboardRegistration::ChessboardRegistration(nlohmann::json &config) : - Registration(config) { - - auto patternsize = get<vector<int>>("patternsize"); - if (!patternsize) { LOG(FATAL) << "Registration run enabled but pattern size not set"; } - pattern_size_ = cv::Size((*patternsize)[0], (*patternsize)[1]); - - auto maxerror = get<float>("maxerror"); - if (!maxerror) { LOG(WARNING) << "maxerror not set"; } - auto delay = get<int>("delay"); - if (!delay) { LOG(INFO) << "delay not set in configuration"; } - auto iter = get<int>("iterations"); - if (!iter) { LOG(INFO) << "iterations not set in configuration"; } - auto chain = get<bool>("chain"); - if (!chain) { LOG(INFO) << "input chaining disabled"; } - else { LOG(INFO) << "Input chaining enabled"; } - - error_threshold_ = maxerror ? *maxerror : std::numeric_limits<float>::infinity(); - iter_ = iter ? *iter : 10; - delay_ = delay ? *delay : 50; -} - -void ChessboardRegistration::run() { - if (!isTargetSourceFound()) { - LOG(WARNING) << "targetsource not found in sources"; - } - - if (data_.size() != getSourcesCount()) { - data_ = vector<vector<optional<PointCloud<PointXYZ>::Ptr>>>(getSourcesCount()); - } - iter_remaining_ = iter_; - - // TODO: Move GUI elsewhere. Also applies to processData() and findFeatures() - for (size_t i = 0; i < getSourcesCount(); ++i) { - cv::namedWindow("Registration: " + getSource(i)->getID(), - cv::WINDOW_KEEPRATIO|cv::WINDOW_NORMAL); - } - - Registration::run(); - - for (size_t i = 0; i < getSourcesCount(); ++i) { - cv::destroyWindow("Registration: " + getSource(i)->getID()); - } -} - -bool ChessboardRegistration::findFeatures(Source *source, size_t idx) { - optional<PointCloud<PointXYZ>::Ptr> result; - PointCloud<PointXYZ>::Ptr cloud(new PointCloud<PointXYZ>); - - Mat rgb, depth; - source->grab(); - source->getFrames(rgb, depth); - - bool retval = findChessboardCorners(rgb, depth, source->parameters(), pattern_size_, cloud, error_threshold_); - if (retval) { - result.emplace(cloud); - } - data_[idx].push_back(result); - - cv::imshow("Registration: " + source->getID(), rgb); - - return retval; -} - -bool ChessboardRegistration::processData() { - bool retval = connectedVisibility(); - resetVisibility(); - - if (retval) { - iter_remaining_--; - } - else{ - LOG(INFO) << "Pattern not visible in all inputs"; - for (auto &sample : data_) { sample.pop_back(); } - } - - //std::this_thread::sleep_for(std::chrono::milliseconds(delay_)); - cv::waitKey(delay_); // OpenCV GUI doesn't show otherwise - - return iter_remaining_ > 0; -} - -bool ChessboardRegistration::findTransformations(vector<Matrix4f> &data) { - data.clear(); - vector<bool> status(getSourcesCount(), false); - size_t idx_target = getTargetSourceIdx(); - - for (size_t idx = 0; idx < getSourcesCount(); ++idx) { - Matrix4f T; - if (idx == idx_target) { - T.setIdentity(); - } - else { - vector<PointCloud<PointXYZ>::Ptr> d; - vector<PointCloud<PointXYZ>::Ptr> d_target; - d.reserve(iter_); - d_target.reserve(iter_); - - for (size_t i = 0; i < iter_; ++i) { - auto val = data_[idx][i]; - auto val_target = data_[idx_target][i]; - if (val && val_target) { - d.push_back(*val); - d_target.push_back(*val_target); - } - } - T = findTransformation(d, d_target); - } - data.push_back(T); - } - return true; -} - -ChessboardRegistrationChain::ChessboardRegistrationChain(nlohmann::json &config) : - ChessboardRegistration(config) { - error_threshold_ = std::numeric_limits<float>::infinity(); -} - -bool ChessboardRegistrationChain::processData() { - for (auto &sample : data_ ) { sample.clear(); } - bool retval = isConnected(visibility_, getTargetSourceIdx(), edges_); - - if (retval) { - LOG(INFO) << "Chain complete, depth: " << edges_.size(); - return false; - } - else{ - LOG(5) << "Chain not complete "; - } - - return true; -} - -bool ChessboardRegistrationChain::findTransformations(vector<Matrix4f> &data) { - // TODO Change to group registration: register all sources which have visibility - // to the target source in chain. - - LOG(INFO) << "Running pairwise registration"; - data = vector<Matrix4f>(getSourcesCount(), Matrix4f::Identity()); - - for (vector<pair<size_t, size_t>> level : edges_) { - for (pair<size_t, size_t> edge : level) { - LOG(INFO) << "Registering source " - << getSource(edge.second)->getID() << " to source" - << getSource(edge.first)->getID(); - - nlohmann::json conf(getConfig()); - conf["targetsource"] = getSource(edge.first)->getID(); - conf["chain"] = false; - - vector<Matrix4f> result; - ChessboardRegistration reg(conf); - reg.addSource(getSource(edge.first)); - reg.addSource(getSource(edge.second)); - reg.run(); - if (!reg.findTransformations(result)) { return false; } - data[edge.second] = data[edge.first] * result[1]; - } - } - - return true; -} } // namespace registration } // namespace ftl - -#endif // HAVE_PCL \ No newline at end of file diff --git a/applications/reconstruct/src/scene_rep_hash_sdf.cu b/applications/reconstruct/src/scene_rep_hash_sdf.cu index 247247b6cc5279186f9f9bdc81bbe627ab0621ea..4750d3e7ed4f7aeb68875e0b5050d76efed5b715 100644 --- a/applications/reconstruct/src/scene_rep_hash_sdf.cu +++ b/applications/reconstruct/src/scene_rep_hash_sdf.cu @@ -2,8 +2,8 @@ //#include <cutil_inline.h> //#include <cutil_math.h> -#include <vector_types.h> -#include <cuda_runtime.h> +//#include <vector_types.h> +//#include <cuda_runtime.h> #include <ftl/cuda_matrix_util.hpp> diff --git a/applications/reconstruct/src/splat_params.hpp b/applications/reconstruct/src/splat_params.hpp deleted file mode 100644 index e38e4447286bf6c70f6c753deed35154b6aafd8a..0000000000000000000000000000000000000000 --- a/applications/reconstruct/src/splat_params.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef _FTL_RENDER_SPLAT_PARAMS_HPP_ -#define _FTL_RENDER_SPLAT_PARAMS_HPP_ - -#include <ftl/cuda_util.hpp> -#include <ftl/cuda_matrix_util.hpp> -#include <ftl/depth_camera_params.hpp> - -namespace ftl { -namespace render { - -static const uint kShowBlockBorders = 0x0001; - -struct __align__(16) SplatParams { - float4x4 m_viewMatrix; - float4x4 m_viewMatrixInverse; - - uint m_flags; - float voxelSize; - - DepthCameraParams camera; -}; - -} -} - -#endif diff --git a/applications/reconstruct/src/splat_render.cpp b/applications/reconstruct/src/splat_render.cpp deleted file mode 100644 index b1c39f6493a8b1849df664c092bb15737b76165a..0000000000000000000000000000000000000000 --- a/applications/reconstruct/src/splat_render.cpp +++ /dev/null @@ -1,126 +0,0 @@ -#include "splat_render.hpp" -#include "splat_render_cuda.hpp" -#include "compactors.hpp" -#include "depth_camera_cuda.hpp" - -using ftl::render::Splatter; - -Splatter::Splatter(ftl::voxhash::SceneRep *scene) : scene_(scene) { - -} - -Splatter::~Splatter() { - -} - -static Eigen::Matrix4f adjustPose(const Eigen::Matrix4f &pose, float baseline) { - Eigen::Affine3f transform(Eigen::Translation3f(baseline,0.0f,0.0f)); - Eigen::Matrix4f matrix = pose; - Eigen::Matrix4f rmat = pose; - rmat(0,3) = 0.0f; - rmat(1,3) = 0.0f; - rmat(2,3) = 0.0f; - Eigen::Matrix4f tmat = transform.matrix(); - tmat(0,0) = 0.0f; - tmat(1,1) = 0.0f; - tmat(2,2) = 0.0f; - tmat(3,3) = 0.0f; - return (tmat * pose) + pose; -} - -void Splatter::render(ftl::rgbd::Source *src, cudaStream_t stream) { - if (!src->isReady()) return; - - const auto &camera = src->parameters(); - - cudaSafeCall(cudaSetDevice(scene_->getCUDADevice())); - - // Create buffers if they don't exists - if ((unsigned int)depth1_.width() != camera.width || (unsigned int)depth1_.height() != camera.height) { - depth1_ = ftl::cuda::TextureObject<int>(camera.width, camera.height); - } - if ((unsigned int)colour1_.width() != camera.width || (unsigned int)colour1_.height() != camera.height) { - colour1_ = ftl::cuda::TextureObject<uchar4>(camera.width, camera.height); - } - if ((unsigned int)depth2_.width() != camera.width || (unsigned int)depth2_.height() != camera.height) { - depth2_ = ftl::cuda::TextureObject<float>(camera.width, camera.height); - } - if ((unsigned int)colour2_.width() != camera.width || (unsigned int)colour2_.height() != camera.height) { - colour2_ = ftl::cuda::TextureObject<uchar4>(camera.width, camera.height); - } - - // Parameters object to pass to CUDA describing the camera - SplatParams params; - params.m_flags = 0; - - // Adjust pose to left eye position - Eigen::Matrix4f matrix = adjustPose(src->getPose().cast<float>(), -camera.baseline/2.0f); - params.m_viewMatrix = MatrixConversion::toCUDA(matrix.inverse()); - params.m_viewMatrixInverse = MatrixConversion::toCUDA(matrix); - - //params.m_viewMatrix = MatrixConversion::toCUDA(src->getPose().cast<float>().inverse()); - //params.m_viewMatrixInverse = MatrixConversion::toCUDA(src->getPose().cast<float>()); - params.voxelSize = scene_->getHashParams().m_virtualVoxelSize; - params.camera.flags = 0; - params.camera.fx = camera.fx; - params.camera.fy = camera.fy; - params.camera.mx = -camera.cx; - params.camera.my = -camera.cy; - params.camera.m_imageWidth = camera.width; - params.camera.m_imageHeight = camera.height; - params.camera.m_sensorDepthWorldMax = camera.maxDepth; - params.camera.m_sensorDepthWorldMin = camera.minDepth; - - //ftl::cuda::compactifyAllocated(scene_->getHashData(), scene_->getHashParams(), stream); - //LOG(INFO) << "Occupied: " << scene_->getOccupiedCount(); - - if (scene_->value("voxels", false)) { - // TODO:(Nick) Stereo for voxel version - ftl::cuda::isosurface_point_image(scene_->getHashData(), depth1_, params, stream); - ftl::cuda::splat_points(depth1_, depth2_, params, stream); - ftl::cuda::dibr(depth2_, colour1_, scene_->cameraCount(), params, stream); - src->writeFrames(colour1_, depth2_, stream); - } else { - //ftl::cuda::clear_colour(colour1_, stream); - ftl::cuda::clear_depth(depth1_, stream); - ftl::cuda::clear_depth(depth2_, stream); - ftl::cuda::dibr(depth1_, colour1_, scene_->cameraCount(), params, stream); - //ftl::cuda::hole_fill(depth1_, depth2_, params.camera, stream); - - // Splat the depth from first DIBR to expand the points - ftl::cuda::splat_points(depth1_, depth2_, params, stream); - - // Alternative to above... - //ftl::cuda::mls_resample(depth1_, colour1_, depth2_, scene_->getHashParams(), scene_->cameraCount(), params, stream); - - - // Use reverse sampling to obtain more colour details - // Should use nearest neighbor point depths to verify which camera to use - //ftl::cuda::dibr(depth2_, colour1_, scene_->cameraCount(), params, stream); - - if (src->getChannel() == ftl::rgbd::kChanDepth) { - ftl::cuda::int_to_float(depth1_, depth2_, 1.0f / 1000.0f, stream); - src->writeFrames(colour1_, depth2_, stream); - } else if (src->getChannel() == ftl::rgbd::kChanRight) { - // Adjust pose to right eye position - Eigen::Matrix4f matrix = adjustPose(src->getPose().cast<float>(), camera.baseline/2.0f); - params.m_viewMatrix = MatrixConversion::toCUDA(matrix.inverse()); - params.m_viewMatrixInverse = MatrixConversion::toCUDA(matrix); - - ftl::cuda::clear_depth(depth1_, stream); - ftl::cuda::dibr(depth1_, colour2_, scene_->cameraCount(), params, stream); - src->writeFrames(colour1_, colour2_, stream); - } else { - src->writeFrames(colour1_, depth2_, stream); - } - } - - //ftl::cuda::median_filter(depth1_, depth2_, stream); - //ftl::cuda::splat_points(depth1_, depth2_, params, stream); - - // TODO: Second pass -} - -void Splatter::setOutputDevice(int device) { - device_ = device; -} diff --git a/applications/reconstruct/src/splat_render.cu b/applications/reconstruct/src/splat_render.cu deleted file mode 100644 index 3addf108dcf89ea8f3069e3ba0daea59e67ae918..0000000000000000000000000000000000000000 --- a/applications/reconstruct/src/splat_render.cu +++ /dev/null @@ -1,400 +0,0 @@ -#include "splat_render_cuda.hpp" -#include <cuda_runtime.h> - -#include <ftl/cuda_matrix_util.hpp> - -#include "splat_params.hpp" - -#define T_PER_BLOCK 8 -#define NUM_GROUPS_X 1024 - -#define NUM_CUDA_BLOCKS 10000 - -using ftl::cuda::TextureObject; -using ftl::render::SplatParams; - -__global__ void clearDepthKernel(ftl::voxhash::HashData hashData, TextureObject<int> depth) { - const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x; - const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y; - - if (x < depth.width() && y < depth.height()) { - depth(x,y) = 0x7f800000; //PINF; - //colour(x,y) = make_uchar4(76,76,82,0); - } -} - -#define SDF_BLOCK_SIZE_PAD 8 -#define SDF_BLOCK_BUFFER 512 // > 8x8x8 -#define SDF_DX 1 -#define SDF_DY SDF_BLOCK_SIZE_PAD -#define SDF_DZ (SDF_BLOCK_SIZE_PAD*SDF_BLOCK_SIZE_PAD) - -#define LOCKED 0x7FFFFFFF - -//! computes the (local) virtual voxel pos of an index; idx in [0;511] -__device__ -int3 pdelinVoxelIndex(uint idx) { - int x = idx % SDF_BLOCK_SIZE_PAD; - int y = (idx % (SDF_BLOCK_SIZE_PAD * SDF_BLOCK_SIZE_PAD)) / SDF_BLOCK_SIZE_PAD; - int z = idx / (SDF_BLOCK_SIZE_PAD * SDF_BLOCK_SIZE_PAD); - return make_int3(x,y,z); -} - -//! computes the linearized index of a local virtual voxel pos; pos in [0;7]^3 -__device__ -uint plinVoxelPos(const int3& virtualVoxelPos) { - return - virtualVoxelPos.z * SDF_BLOCK_SIZE_PAD * SDF_BLOCK_SIZE_PAD + - virtualVoxelPos.y * SDF_BLOCK_SIZE_PAD + - virtualVoxelPos.x; -} - -//! computes the linearized index of a local virtual voxel pos; pos in [0;7]^3 -__device__ -uint plinVoxelPos(int x, int y, int z) { - return - z * SDF_BLOCK_SIZE_PAD * SDF_BLOCK_SIZE_PAD + - y * SDF_BLOCK_SIZE_PAD + x; -} - -__device__ -void deleteVoxel(ftl::voxhash::Voxel& v) { - v.color = make_uchar3(0,0,0); - v.weight = 0; - v.sdf = PINF; -} - -__device__ inline int3 blockDelinear(const int3 &base, uint i) { - return make_int3(base.x + (i & 0x1), base.y + (i & 0x2), base.z + (i & 0x4)); -} - -__device__ inline uint blockLinear(int x, int y, int z) { - return x + (y << 1) + (z << 2); -} - -__device__ inline bool getVoxel(uint *voxels, int ix) { - return voxels[ix/32] & (0x1 << (ix % 32)); -} - -__global__ void occupied_image_kernel(ftl::voxhash::HashData hashData, TextureObject<int> depth, SplatParams params) { - __shared__ uint voxels[16]; - __shared__ ftl::voxhash::HashEntryHead block; - - // Stride over all allocated blocks - for (int bi=blockIdx.x; bi<*hashData.d_hashCompactifiedCounter; bi+=NUM_CUDA_BLOCKS) { - __syncthreads(); - - const uint i = threadIdx.x; //inside of an SDF block - - if (i == 0) block = hashData.d_hashCompactified[bi]->head; - if (i < 16) { - voxels[i] = hashData.d_hashCompactified[bi]->voxels[i]; - //valid[i] = hashData.d_hashCompactified[bi]->validity[i]; - } - - // Make sure all hash entries are cached - __syncthreads(); - - const int3 pi_base = hashData.SDFBlockToVirtualVoxelPos(make_int3(block.posXYZ)); - const int3 vp = make_int3(hashData.delinearizeVoxelIndex(i)); - const int3 pi = pi_base + vp; - const float3 worldPos = hashData.virtualVoxelPosToWorld(pi); - - const bool v = getVoxel(voxels, i); - - uchar4 color = make_uchar4(255,0,0,255); - bool is_surface = v; //((params.m_flags & ftl::render::kShowBlockBorders) && edgeX + edgeY + edgeZ >= 2); - - - // Only for surface voxels, work out screen coordinates - if (!is_surface) continue; - - // TODO: For each original camera, render a new depth map - - const float3 camPos = params.m_viewMatrix * worldPos; - const float2 screenPosf = params.camera.cameraToKinectScreenFloat(camPos); - const uint2 screenPos = make_uint2(make_int2(screenPosf)); // + make_float2(0.5f, 0.5f) - - //printf("Worldpos: %f,%f,%f\n", camPos.x, camPos.y, camPos.z); - - if (camPos.z < params.camera.m_sensorDepthWorldMin) continue; - - const unsigned int x = screenPos.x; - const unsigned int y = screenPos.y; - const int idepth = static_cast<int>(camPos.z * 1000.0f); - - // See: Gunther et al. 2013. A GPGPU-based Pipeline for Accelerated Rendering of Point Clouds - if (x < depth.width() && y < depth.height()) { - atomicMin(&depth(x,y), idepth); - } - - } // Stride -} - -__global__ void isosurface_image_kernel(ftl::voxhash::HashData hashData, TextureObject<int> depth, SplatParams params) { - // TODO:(Nick) Reduce bank conflicts by aligning these - __shared__ uint voxels[16]; - //__shared__ uint valid[16]; - __shared__ ftl::voxhash::HashEntryHead block; - - // Stride over all allocated blocks - for (int bi=blockIdx.x; bi<*hashData.d_hashCompactifiedCounter; bi+=NUM_CUDA_BLOCKS) { - __syncthreads(); - - const uint i = threadIdx.x; //inside of an SDF block - - if (i == 0) block = hashData.d_hashCompactified[bi]->head; - if (i < 16) { - voxels[i] = hashData.d_hashCompactified[bi]->voxels[i]; - //valid[i] = hashData.d_hashCompactified[bi]->validity[i]; - } - - // Make sure all hash entries are cached - __syncthreads(); - - const int3 pi_base = hashData.SDFBlockToVirtualVoxelPos(make_int3(block.posXYZ)); - const int3 vp = make_int3(hashData.delinearizeVoxelIndex(i)); - const int3 pi = pi_base + vp; - //const uint j = plinVoxelPos(vp); // Padded linear index - const float3 worldPos = hashData.virtualVoxelPosToWorld(pi); - - // Load distances and colours into shared memory + padding - //const ftl::voxhash::Voxel &v = hashData.d_SDFBlocks[block.ptr + i]; - //voxels[j] = v; - const bool v = getVoxel(voxels, i); - - //__syncthreads(); - - //if (voxels[j].weight == 0) continue; - if (vp.x == 7 || vp.y == 7 || vp.z == 7) continue; - - - int edgeX = (vp.x == 0 ) ? 1 : 0; - int edgeY = (vp.y == 0 ) ? 1 : 0; - int edgeZ = (vp.z == 0 ) ? 1 : 0; - - uchar4 color = make_uchar4(255,0,0,255); - bool is_surface = v; //((params.m_flags & ftl::render::kShowBlockBorders) && edgeX + edgeY + edgeZ >= 2); - //if (is_surface) color = make_uchar4(255,(vp.x == 0 && vp.y == 0 && vp.z == 0) ? 255 : 0,0,255); - - if (v) continue; // !getVoxel(valid, i) - - //if (vp.z == 7) voxels[j].color = make_uchar3(0,255,(voxels[j].sdf < 0.0f) ? 255 : 0); - - // Identify surfaces through sign change. Since we only check in one direction - // it is fine to check for any sign change? - - -#pragma unroll - for (int u=0; u<=1; u++) { - for (int v=0; v<=1; v++) { - for (int w=0; w<=1; w++) { - const int3 uvi = make_int3(vp.x+u,vp.y+v,vp.z+w); - - // Skip these cases since we didn't load voxels properly - //if (uvi.x == 8 || uvi.z == 8 || uvi.y == 8) continue; - - const bool vox = getVoxel(voxels, hashData.linearizeVoxelPos(uvi)); - if (vox) { //getVoxel(valid, hashData.linearizeVoxelPos(uvi))) { - is_surface = true; - // Should break but is slower? - } - } - } - } - - // Only for surface voxels, work out screen coordinates - if (!is_surface) continue; - - // TODO: For each original camera, render a new depth map - - const float3 camPos = params.m_viewMatrix * worldPos; - const float2 screenPosf = params.camera.cameraToKinectScreenFloat(camPos); - const uint2 screenPos = make_uint2(make_int2(screenPosf)); // + make_float2(0.5f, 0.5f) - - //printf("Worldpos: %f,%f,%f\n", camPos.x, camPos.y, camPos.z); - - if (camPos.z < params.camera.m_sensorDepthWorldMin) continue; - - // For this voxel in hash, get its screen position and check it is on screen - // Convert depth map to int by x1000 and use atomicMin - //const int pixsize = static_cast<int>((c_hashParams.m_virtualVoxelSize*params.camera.fx/(camPos.z*0.8f)))+1; // Magic number increase voxel to ensure coverage - - const unsigned int x = screenPos.x; - const unsigned int y = screenPos.y; - const int idepth = static_cast<int>(camPos.z * 1000.0f); - - // See: Gunther et al. 2013. A GPGPU-based Pipeline for Accelerated Rendering of Point Clouds - if (x < depth.width() && y < depth.height()) { - atomicMin(&depth(x,y), idepth); - } - - } // Stride -} - -void ftl::cuda::isosurface_point_image(const ftl::voxhash::HashData& hashData, - const TextureObject<int> &depth, - const SplatParams ¶ms, cudaStream_t stream) { - - const dim3 clear_gridSize((depth.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (depth.height() + T_PER_BLOCK - 1)/T_PER_BLOCK); - const dim3 clear_blockSize(T_PER_BLOCK, T_PER_BLOCK); - - clearDepthKernel<<<clear_gridSize, clear_blockSize, 0, stream>>>(hashData, depth); - -#ifdef _DEBUG - cudaSafeCall(cudaDeviceSynchronize()); -#endif - - const unsigned int threadsPerBlock = SDF_BLOCK_SIZE*SDF_BLOCK_SIZE*SDF_BLOCK_SIZE; - const dim3 gridSize(NUM_CUDA_BLOCKS, 1); - const dim3 blockSize(threadsPerBlock, 1); - - occupied_image_kernel<<<gridSize, blockSize, 0, stream>>>(hashData, depth, params); - - cudaSafeCall( cudaGetLastError() ); - -#ifdef _DEBUG - cudaSafeCall(cudaDeviceSynchronize()); -#endif -} - -// ---- Pass 2: Expand the point splats ---------------------------------------- - -#define SPLAT_RADIUS 7 -#define SPLAT_BOUNDS (2*SPLAT_RADIUS+T_PER_BLOCK+1) -#define SPLAT_BUFFER_SIZE (SPLAT_BOUNDS*SPLAT_BOUNDS) -#define MAX_VALID 100 - -__device__ float distance2(float3 a, float3 b) { - const float x = a.x-b.x; - const float y = a.y-b.y; - const float z = a.z-b.z; - return x*x+y*y+z*z; -} - -__global__ void splatting_kernel( - TextureObject<int> depth_in, - TextureObject<float> depth_out, SplatParams params) { - // Read an NxN region and - // - interpolate a depth value for this pixel - // - interpolate an rgb value for this pixel - // Must respect depth discontinuities. - // How much influence a given neighbour has depends on its depth value - - __shared__ float3 positions[SPLAT_BUFFER_SIZE]; - - const int i = threadIdx.y*blockDim.y + threadIdx.x; - const int bx = blockIdx.x*blockDim.x; - const int by = blockIdx.y*blockDim.y; - const int x = bx + threadIdx.x; - const int y = by + threadIdx.y; - - // const float camMinDepth = params.camera.m_sensorDepthWorldMin; - // const float camMaxDepth = params.camera.m_sensorDepthWorldMax; - - for (int j=i; j<SPLAT_BUFFER_SIZE; j+=T_PER_BLOCK) { - const unsigned int sx = (j % SPLAT_BOUNDS)+bx-SPLAT_RADIUS; - const unsigned int sy = (j / SPLAT_BOUNDS)+by-SPLAT_RADIUS; - if (sx >= depth_in.width() || sy >= depth_in.height()) { - positions[j] = make_float3(1000.0f,1000.0f, 1000.0f); - } else { - positions[j] = params.camera.kinectDepthToSkeleton(sx, sy, (float)depth_in.tex2D((int)sx,(int)sy) / 1000.0f); - } - } - - __syncthreads(); - - if (x >= depth_in.width() || y >= depth_in.height()) return; - - const float voxelSquared = params.voxelSize*params.voxelSize; - float mindepth = 1000.0f; - int minidx = -1; - // float3 minpos; - - //float3 validPos[MAX_VALID]; - int validIndices[MAX_VALID]; - int validix = 0; - - for (int v=-SPLAT_RADIUS; v<=SPLAT_RADIUS; ++v) { - for (int u=-SPLAT_RADIUS; u<=SPLAT_RADIUS; ++u) { - //const int idx = (threadIdx.y+v)*SPLAT_BOUNDS+threadIdx.x+u; - const int idx = (threadIdx.y+v+SPLAT_RADIUS)*SPLAT_BOUNDS+threadIdx.x+u+SPLAT_RADIUS; - - float3 posp = positions[idx]; - const float d = posp.z; - //if (d < camMinDepth || d > camMaxDepth) continue; - - float3 pos = params.camera.kinectDepthToSkeleton(x, y, d); - float dist = distance2(pos, posp); - - if (dist < voxelSquared) { - // Valid so check for minimum - //validPos[validix] = pos; - //validIndices[validix++] = idx; - if (d < mindepth) { - mindepth = d; - minidx = idx; - // minpos = pos; - } - } - } - } - - if (minidx == -1) { - depth_out(x,y) = 0.0f; - //colour_out(x,y) = make_uchar4(76,76,82,255); - return; - } - - //float3 colour = make_float3(0.0f, 0.0f, 0.0f); - float depth = 0.0f; - float contrib = 0.0f; - float3 pos = params.camera.kinectDepthToSkeleton(x, y, mindepth); // TODO:(Nick) Mindepth assumption is poor choice. - - //for (int j=0; j<validix; ++j) { - const int idx = minidx; //validIndices[j]; - float3 posp = positions[idx]; - //float3 pos = params.camera.kinectDepthToSkeleton(x, y, posp.z); - float3 delta = (posp - pos) / 2*params.voxelSize; - float dist = delta.x*delta.x + delta.y*delta.y + delta.z*delta.z; - - // It contributes to pixel - if (dist < 1.0f && fabs(posp.z - mindepth) < 2*params.voxelSize) { - const unsigned int sx = (idx % SPLAT_BOUNDS)+bx-SPLAT_RADIUS; - const unsigned int sy = (idx / SPLAT_BOUNDS)+by-SPLAT_RADIUS; - - // Fast and simple trilinear interpolation - float c = fabs((1.0f - delta.x) * (1.0f - delta.y) * (1.0f - delta.z)); - //uchar4 col = colour_in.tex2D((int)sx, (int)sy); - //colour.x += col.x*c; - //colour.y += col.y*c; - //colour.z += col.z*c; - contrib += c; - depth += posp.z * c; - } - //} - - // Normalise - //colour.x /= contrib; - //colour.y /= contrib; - //colour.z /= contrib; - depth /= contrib; - - depth_out(x,y) = depth; - //colour_out(x,y) = make_uchar4(colour.x, colour.y, colour.z, 255); -} - -void ftl::cuda::splat_points(const TextureObject<int> &depth_in, - const TextureObject<float> &depth_out, const SplatParams ¶ms, cudaStream_t stream) -{ - - const dim3 gridSize((depth_in.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (depth_in.height() + T_PER_BLOCK - 1)/T_PER_BLOCK); - const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK); - - splatting_kernel<<<gridSize, blockSize, 0, stream>>>(depth_in, depth_out, params); - cudaSafeCall( cudaGetLastError() ); - -#ifdef _DEBUG - cudaSafeCall(cudaDeviceSynchronize()); -#endif -} diff --git a/applications/reconstruct/src/splat_render_cuda.hpp b/applications/reconstruct/src/splat_render_cuda.hpp deleted file mode 100644 index d8fd9cf55649280c20805c8d4f8b9f7b758b2223..0000000000000000000000000000000000000000 --- a/applications/reconstruct/src/splat_render_cuda.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef _FTL_RECONSTRUCTION_SPLAT_CUDA_HPP_ -#define _FTL_RECONSTRUCTION_SPLAT_CUDA_HPP_ - -#include <ftl/depth_camera.hpp> -#include <ftl/voxel_hash.hpp> -//#include <ftl/ray_cast_util.hpp> - -#include "splat_params.hpp" - -namespace ftl { -namespace cuda { - -/** - * NOTE: Not strictly isosurface currently since it includes the internals - * of objects up to at most truncation depth. - */ -void isosurface_point_image(const ftl::voxhash::HashData& hashData, - const ftl::cuda::TextureObject<int> &depth, - const ftl::render::SplatParams ¶ms, cudaStream_t stream); - -//void isosurface_point_image_stereo(const ftl::voxhash::HashData& hashData, -// const ftl::voxhash::HashParams& hashParams, -// const RayCastData &rayCastData, const RayCastParams ¶ms, -// cudaStream_t stream); - -// TODO: isosurface_point_cloud - -void splat_points(const ftl::cuda::TextureObject<int> &depth_in, - const ftl::cuda::TextureObject<float> &depth_out, - const ftl::render::SplatParams ¶ms, cudaStream_t stream); - -void dibr(const ftl::cuda::TextureObject<int> &depth_out, - const ftl::cuda::TextureObject<uchar4> &colour_out, int numcams, - const ftl::render::SplatParams ¶ms, cudaStream_t stream); - -void dibr(const ftl::cuda::TextureObject<float> &depth_out, - const ftl::cuda::TextureObject<uchar4> &colour_out, int numcams, const ftl::render::SplatParams ¶ms, cudaStream_t stream); - -} -} - -#endif // _FTL_RECONSTRUCTION_SPLAT_CUDA_HPP_ diff --git a/applications/reconstruct/src/voxel_hash.cpp b/applications/reconstruct/src/voxel_hash.cpp deleted file mode 100644 index 14cb75078652001275404e87c09f77f0b9d19385..0000000000000000000000000000000000000000 --- a/applications/reconstruct/src/voxel_hash.cpp +++ /dev/null @@ -1,94 +0,0 @@ -#include <ftl/voxel_hash.hpp> - -using ftl::voxhash::HashData; -using ftl::voxhash::HashParams; - -void HashData::allocate(const HashParams& params, bool dataOnGPU) { - m_bIsOnGPU = dataOnGPU; - if (m_bIsOnGPU) { - cudaSafeCall(cudaMalloc(&d_hash, sizeof(HashEntry)* params.m_hashNumBuckets)); - cudaSafeCall(cudaMalloc(&d_hashDecision, sizeof(int)* params.m_hashNumBuckets)); - cudaSafeCall(cudaMalloc(&d_hashDecisionPrefix, sizeof(int)* params.m_hashNumBuckets)); - cudaSafeCall(cudaMalloc(&d_hashCompactified, sizeof(HashEntry*)* params.m_hashNumBuckets)); - cudaSafeCall(cudaMalloc(&d_hashCompactifiedCounter, sizeof(int))); - cudaSafeCall(cudaMalloc(&d_hashBucketMutex, sizeof(int)* params.m_hashNumBuckets)); - } else { - d_hash = new HashEntry[params.m_hashNumBuckets]; - d_hashDecision = new int[params.m_hashNumBuckets]; - d_hashDecisionPrefix = new int[params.m_hashNumBuckets]; - d_hashCompactified = new HashEntry*[params.m_hashNumBuckets]; - d_hashCompactifiedCounter = new int[1]; - d_hashBucketMutex = new int[params.m_hashNumBuckets]; - } - - updateParams(params); -} - -void HashData::updateParams(const HashParams& params) { - if (m_bIsOnGPU) { - updateConstantHashParams(params); - } -} - -void HashData::free() { - if (m_bIsOnGPU) { - cudaSafeCall(cudaFree(d_hash)); - cudaSafeCall(cudaFree(d_hashDecision)); - cudaSafeCall(cudaFree(d_hashDecisionPrefix)); - cudaSafeCall(cudaFree(d_hashCompactified)); - cudaSafeCall(cudaFree(d_hashCompactifiedCounter)); - cudaSafeCall(cudaFree(d_hashBucketMutex)); - } else { - if (d_hash) delete[] d_hash; - if (d_hashDecision) delete[] d_hashDecision; - if (d_hashDecisionPrefix) delete[] d_hashDecisionPrefix; - if (d_hashCompactified) delete[] d_hashCompactified; - if (d_hashCompactifiedCounter) delete[] d_hashCompactifiedCounter; - if (d_hashBucketMutex) delete[] d_hashBucketMutex; - } - - d_hash = NULL; - d_hashDecision = NULL; - d_hashDecisionPrefix = NULL; - d_hashCompactified = NULL; - d_hashCompactifiedCounter = NULL; - d_hashBucketMutex = NULL; -} - -HashData HashData::download() const { - if (!m_bIsOnGPU) return *this; - HashParams params; - - HashData hashData; - hashData.allocate(params, false); //allocate the data on the CPU - cudaSafeCall(cudaMemcpy(hashData.d_hash, d_hash, sizeof(HashEntry)* params.m_hashNumBuckets, cudaMemcpyDeviceToHost)); - cudaSafeCall(cudaMemcpy(hashData.d_hashDecision, d_hashDecision, sizeof(int)*params.m_hashNumBuckets, cudaMemcpyDeviceToHost)); - cudaSafeCall(cudaMemcpy(hashData.d_hashDecisionPrefix, d_hashDecisionPrefix, sizeof(int)*params.m_hashNumBuckets, cudaMemcpyDeviceToHost)); - cudaSafeCall(cudaMemcpy(hashData.d_hashCompactified, d_hashCompactified, sizeof(HashEntry*)* params.m_hashNumBuckets, cudaMemcpyDeviceToHost)); - cudaSafeCall(cudaMemcpy(hashData.d_hashCompactifiedCounter, d_hashCompactifiedCounter, sizeof(unsigned int), cudaMemcpyDeviceToHost)); - cudaSafeCall(cudaMemcpy(hashData.d_hashBucketMutex, d_hashBucketMutex, sizeof(int)* params.m_hashNumBuckets, cudaMemcpyDeviceToHost)); - - return hashData; -} - -HashData HashData::upload() const { - if (m_bIsOnGPU) return *this; - HashParams params; - - HashData hashData; - hashData.allocate(params, false); //allocate the data on the CPU - cudaSafeCall(cudaMemcpy(hashData.d_hash, d_hash, sizeof(HashEntry)* params.m_hashNumBuckets, cudaMemcpyHostToDevice)); - cudaSafeCall(cudaMemcpy(hashData.d_hashDecision, d_hashDecision, sizeof(int)*params.m_hashNumBuckets, cudaMemcpyHostToDevice)); - cudaSafeCall(cudaMemcpy(hashData.d_hashDecisionPrefix, d_hashDecisionPrefix, sizeof(int)*params.m_hashNumBuckets, cudaMemcpyHostToDevice)); - cudaSafeCall(cudaMemcpy(hashData.d_hashCompactified, d_hashCompactified, sizeof(HashEntry)* params.m_hashNumBuckets, cudaMemcpyHostToDevice)); - cudaSafeCall(cudaMemcpy(hashData.d_hashCompactifiedCounter, d_hashCompactifiedCounter, sizeof(unsigned int), cudaMemcpyHostToDevice)); - cudaSafeCall(cudaMemcpy(hashData.d_hashBucketMutex, d_hashBucketMutex, sizeof(int)* params.m_hashNumBuckets, cudaMemcpyHostToDevice)); - - return hashData; -} - -/*size_t HashData::getAllocatedBlocks() const { - unsigned int count; - cudaSafeCall(cudaMemcpy(d_heapCounter, &count, sizeof(unsigned int), cudaMemcpyDeviceToHost)); - return count; -}*/ diff --git a/applications/reconstruct/src/voxel_hash.cu b/applications/reconstruct/src/voxel_hash.cu deleted file mode 100644 index c2d07c391a6e48d2b45cc23dbf32b00878ffd5c9..0000000000000000000000000000000000000000 --- a/applications/reconstruct/src/voxel_hash.cu +++ /dev/null @@ -1,257 +0,0 @@ -#include <ftl/voxel_hash.hpp> - -using namespace ftl::voxhash; - -#define COLLISION_LIST_SIZE 6 - -__device__ inline uint64_t compactPosition(const int3 &pos) { - union __align__(8) { - short4 posXYZ; - uint64_t pos64; - }; - posXYZ.x = pos.x; posXYZ.y = pos.y; posXYZ.z = pos.z; posXYZ.w = 0; - return pos64; -} - -//! returns the hash entry for a given sdf block id; if there was no hash entry the returned entry will have a ptr with FREE_ENTRY set -__device__ -int HashData::getHashEntryForSDFBlockPos(const int3& sdfBlock) const { - uint h = computeHashPos(sdfBlock); //hash - uint64_t pos = compactPosition(sdfBlock); - - HashEntryHead curr; - - int i = h; - unsigned int maxIter = 0; - - #pragma unroll 2 - while (maxIter < COLLISION_LIST_SIZE) { - curr = d_hash[i].head; - - if (curr.pos == pos && curr.offset != FREE_ENTRY) return i; - if (curr.offset == 0 || curr.offset == FREE_ENTRY) break; - - i += curr.offset; //go to next element in the list - i %= (params().m_hashNumBuckets); //check for overflow - ++maxIter; - } - - // Could not find - return -1; -} - -//for histogram (collisions traversal only) -__device__ -unsigned int HashData::getNumHashLinkedList(unsigned int bucketID) { - unsigned int listLen = 0; - - unsigned int i = bucketID; //start with the last entry of the current bucket - HashEntryHead curr; curr.offset = 0; - - unsigned int maxIter = 0; - - #pragma unroll 2 - while (maxIter < COLLISION_LIST_SIZE) { - curr = d_hash[i].head; - - if (curr.offset == 0 || curr.offset == FREE_ENTRY) break; - - i += curr.offset; //go to next element in the list - i %= (params().m_hashNumBuckets); //check for overflow - ++listLen; - ++maxIter; - } - - return listLen; -} - -//pos in SDF block coordinates -__device__ -void HashData::allocBlock(const int3& pos) { - uint h = computeHashPos(pos); //hash bucket - uint i = h; - HashEntryHead curr; //curr.offset = 0; - const uint64_t pos64 = compactPosition(pos); - - unsigned int maxIter = 0; - #pragma unroll 2 - while (maxIter < COLLISION_LIST_SIZE) { - //offset = curr.offset; - curr = d_hash[i].head; //TODO MATTHIAS do by reference - if (curr.pos == pos64 && curr.offset != FREE_ENTRY) return; - if (curr.offset == 0 || curr.offset == FREE_ENTRY) break; - - i += curr.offset; //go to next element in the list - i %= (params().m_hashNumBuckets); //check for overflow - ++maxIter; - } - - // Limit reached... - //if (maxIter == COLLISION_LIST_SIZE) return; - - int j = i; - while (maxIter < COLLISION_LIST_SIZE) { - //offset = curr.offset; - - if (curr.offset == FREE_ENTRY) { - int prevValue = atomicExch(&d_hashBucketMutex[i], LOCK_ENTRY); - if (prevValue != LOCK_ENTRY) { - if (i == j) { - HashEntryHead& entry = d_hash[j].head; - entry.pos = pos64; - entry.offset = 0; - entry.flags = 0; - } else { - //InterlockedExchange(g_HashBucketMutex[h], LOCK_ENTRY, prevValue); //lock the hash bucket where we have found a free entry - prevValue = atomicExch(&d_hashBucketMutex[j], LOCK_ENTRY); - if (prevValue != LOCK_ENTRY) { //only proceed if the bucket has been locked - HashEntryHead& entry = d_hash[j].head; - entry.pos = pos64; - entry.offset = 0; - entry.flags = 0; // Flag block as valid in this frame (Nick) - //entry.ptr = consumeHeap() * SDF_BLOCK_SIZE*SDF_BLOCK_SIZE*SDF_BLOCK_SIZE; //memory alloc - d_hash[i].head.offset = j-i; - //setHashEntry(g_Hash, idxLastEntryInBucket, lastEntryInBucket); - } - } - } - return; //bucket was already locked - } - - ++j; - j %= (params().m_hashNumBuckets); //check for overflow - curr = d_hash[j].head; //TODO MATTHIAS do by reference - ++maxIter; - } -} - - -//!inserts a hash entry without allocating any memory: used by streaming: TODO MATTHIAS check the atomics in this function -/*__device__ -bool HashData::insertHashEntry(HashEntry entry) -{ - uint h = computeHashPos(entry.pos); - uint hp = h * HASH_BUCKET_SIZE; - - for (uint j = 0; j < HASH_BUCKET_SIZE; j++) { - uint i = j + hp; - //const HashEntry& curr = d_hash[i]; - int prevWeight = 0; - //InterlockedCompareExchange(hash[3*i+2], FREE_ENTRY, LOCK_ENTRY, prevWeight); - prevWeight = atomicCAS(&d_hash[i].ptr, FREE_ENTRY, LOCK_ENTRY); - if (prevWeight == FREE_ENTRY) { - d_hash[i] = entry; - //setHashEntry(hash, i, entry); - return true; - } - } - -#ifdef HANDLE_COLLISIONS - //updated variables as after the loop - const uint idxLastEntryInBucket = (h+1)*HASH_BUCKET_SIZE - 1; //get last index of bucket - - uint i = idxLastEntryInBucket; //start with the last entry of the current bucket - HashEntry curr; - - unsigned int maxIter = 0; - //[allow_uav_condition] - uint g_MaxLoopIterCount = params().m_hashMaxCollisionLinkedListSize; - #pragma unroll 1 - while (maxIter < g_MaxLoopIterCount) { //traverse list until end // why find the end? we you are inserting at the start !!! - //curr = getHashEntry(hash, i); - curr = d_hash[i]; //TODO MATTHIAS do by reference - if (curr.offset == 0) break; //we have found the end of the list - i = idxLastEntryInBucket + curr.offset; //go to next element in the list - i %= (HASH_BUCKET_SIZE * params().m_hashNumBuckets); //check for overflow - - maxIter++; - } - - maxIter = 0; - int offset = 0; - #pragma unroll 1 - while (maxIter < g_MaxLoopIterCount) { //linear search for free entry - offset++; - uint i = (idxLastEntryInBucket + offset) % (HASH_BUCKET_SIZE * params().m_hashNumBuckets); //go to next hash element - if ((offset % HASH_BUCKET_SIZE) == 0) continue; //cannot insert into a last bucket element (would conflict with other linked lists) - - int prevWeight = 0; - //InterlockedCompareExchange(hash[3*i+2], FREE_ENTRY, LOCK_ENTRY, prevWeight); //check for a free entry - uint* d_hashUI = (uint*)d_hash; - prevWeight = prevWeight = atomicCAS(&d_hashUI[3*idxLastEntryInBucket+1], (uint)FREE_ENTRY, (uint)LOCK_ENTRY); - if (prevWeight == FREE_ENTRY) { //if free entry found set prev->next = curr & curr->next = prev->next - //[allow_uav_condition] - //while(hash[3*idxLastEntryInBucket+2] == LOCK_ENTRY); // expects setHashEntry to set the ptr last, required because pos.z is packed into the same value -> prev->next = curr -> might corrput pos.z - - HashEntry lastEntryInBucket = d_hash[idxLastEntryInBucket]; //get prev (= lastEntry in Bucket) - - int newOffsetPrev = (offset << 16) | (lastEntryInBucket.pos.z & 0x0000ffff); //prev->next = curr (maintain old z-pos) - int oldOffsetPrev = 0; - //InterlockedExchange(hash[3*idxLastEntryInBucket+1], newOffsetPrev, oldOffsetPrev); //set prev offset atomically - uint* d_hashUI = (uint*)d_hash; - oldOffsetPrev = prevWeight = atomicExch(&d_hashUI[3*idxLastEntryInBucket+1], newOffsetPrev); - entry.offset = oldOffsetPrev >> 16; //remove prev z-pos from old offset - - //setHashEntry(hash, i, entry); //sets the current hashEntry with: curr->next = prev->next - d_hash[i] = entry; - return true; - } - - maxIter++; - } -#endif - - return false; -}*/ - - - -//! deletes a hash entry position for a given sdfBlock index (returns true uppon successful deletion; otherwise returns false) -__device__ -bool HashData::deleteHashEntryElement(const int3& sdfBlock) { - uint h = computeHashPos(sdfBlock); //hash bucket - const uint64_t pos = compactPosition(sdfBlock); - - int i = h; - int prev = -1; - HashEntryHead curr; - unsigned int maxIter = 0; - - #pragma unroll 2 - while (maxIter < COLLISION_LIST_SIZE) { - curr = d_hash[i].head; - - //found that dude that we need/want to delete - if (curr.pos == pos && curr.offset != FREE_ENTRY) { - //int prevValue = 0; - //InterlockedExchange(bucketMutex[h], LOCK_ENTRY, prevValue); //lock the hash bucket - int prevValue = atomicExch(&d_hashBucketMutex[i], LOCK_ENTRY); - if (prevValue == LOCK_ENTRY) return false; - if (prevValue != LOCK_ENTRY) { - prevValue = (prev >= 0) ? atomicExch(&d_hashBucketMutex[prev], LOCK_ENTRY) : 0; - if (prevValue == LOCK_ENTRY) return false; - if (prevValue != LOCK_ENTRY) { - //const uint linBlockSize = SDF_BLOCK_SIZE * SDF_BLOCK_SIZE * SDF_BLOCK_SIZE; - //appendHeap(curr.ptr / linBlockSize); - deleteHashEntry(i); - - if (prev >= 0) { - d_hash[prev].head.offset = curr.offset; - } - return true; - } - } - } - - if (curr.offset == 0 || curr.offset == FREE_ENTRY) { //we have found the end of the list - return false; //should actually never happen because we need to find that guy before - } - prev = i; - i += curr.offset; //go to next element in the list - i %= (params().m_hashNumBuckets); //check for overflow - - ++maxIter; - } - - return false; -} \ No newline at end of file diff --git a/applications/reconstruct/src/voxel_scene.cpp b/applications/reconstruct/src/voxel_scene.cpp index 5ec9204e9899c885e5ad3188c7127b7269b4d1f0..09067b1f2334e0190e27022b64d374e31532f8c2 100644 --- a/applications/reconstruct/src/voxel_scene.cpp +++ b/applications/reconstruct/src/voxel_scene.cpp @@ -1,7 +1,4 @@ #include <ftl/voxel_scene.hpp> -#include "compactors.hpp" -#include "garbage.hpp" -#include "integrators.hpp" #include "depth_camera_cuda.hpp" #include <opencv2/core/cuda_stream_accessor.hpp> @@ -10,15 +7,16 @@ using namespace ftl::voxhash; using ftl::rgbd::Source; +using ftl::rgbd::Channel; using ftl::Configurable; using cv::Mat; using std::vector; #define SAFE_DELETE_ARRAY(a) { delete [] (a); (a) = NULL; } -extern "C" void resetCUDA(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams); -extern "C" void resetHashBucketMutexCUDA(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams, cudaStream_t); -extern "C" void allocCUDA(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams, int camid, const DepthCameraParams &depthCameraParams, cudaStream_t); +//extern "C" void resetCUDA(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams); +//extern "C" void resetHashBucketMutexCUDA(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams, cudaStream_t); +//extern "C" void allocCUDA(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams, int camid, const DepthCameraParams &depthCameraParams, cudaStream_t); //extern "C" void fillDecisionArrayCUDA(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams, const DepthCameraData& depthCameraData); //extern "C" void compactifyHashCUDA(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams); //extern "C" unsigned int compactifyHashAllInOneCUDA(ftl::voxhash::HashData& hashData, const ftl::voxhash::HashParams& hashParams); @@ -98,6 +96,7 @@ void SceneRep::addSource(ftl::rgbd::Source *src) { auto &cam = cameras_.emplace_back(); cam.source = src; cam.params.m_imageWidth = 0; + src->setChannel(Channel::Depth); } extern "C" void updateCUDACameraConstant(ftl::voxhash::DepthCameraCUDA *data, int count); @@ -183,7 +182,89 @@ int SceneRep::upload() { //if (i > 0) cudaSafeCall(cudaStreamSynchronize(cv::cuda::StreamAccessor::getStream(cameras_[i-1].stream))); //allocate all hash blocks which are corresponding to depth map entries - if (value("voxels", false)) _alloc(i, cv::cuda::StreamAccessor::getStream(cam.stream)); + //if (value("voxels", false)) _alloc(i, cv::cuda::StreamAccessor::getStream(cam.stream)); + + // Calculate normals + } + + // Must have finished all allocations and rendering before next integration + cudaSafeCall(cudaDeviceSynchronize()); + + return active; +} + +int SceneRep::upload(ftl::rgbd::FrameSet &fs) { + int active = 0; + + for (size_t i=0; i<cameras_.size(); ++i) { + auto &cam = cameras_[i]; + + if (!cam.source->isReady()) { + cam.params.m_imageWidth = 0; + // TODO(Nick) : Free gpu allocs if was ready before + LOG(INFO) << "Source not ready: " << cam.source->getURI(); + continue; + } else { + auto in = cam.source; + + cam.params.fx = in->parameters().fx; + cam.params.fy = in->parameters().fy; + cam.params.mx = -in->parameters().cx; + cam.params.my = -in->parameters().cy; + + // Only now do we have camera parameters for allocations... + if (cam.params.m_imageWidth == 0) { + cam.params.m_imageWidth = in->parameters().width; + cam.params.m_imageHeight = in->parameters().height; + cam.params.m_sensorDepthWorldMax = in->parameters().maxDepth; + cam.params.m_sensorDepthWorldMin = in->parameters().minDepth; + cam.gpu.alloc(cam.params, true); + LOG(INFO) << "GPU Allocated camera " << i; + } + } + + cam.params.flags = m_frameCount; + } + + _updateCameraConstant(); + //cudaSafeCall(cudaDeviceSynchronize()); + + for (size_t i=0; i<cameras_.size(); ++i) { + auto &cam = cameras_[i]; + auto &chan1 = fs.frames[i].get<cv::Mat>(Channel::Colour); + auto &chan2 = fs.frames[i].get<cv::Mat>(fs.sources[i]->getChannel()); + + auto test = fs.frames[i].createTexture<uchar4>(Channel::Flow, ftl::rgbd::Format<uchar4>(100,100)); + + // Get the RGB-Depth frame from input + Source *input = cam.source; + //Mat rgb, depth; + + // TODO(Nick) Direct GPU upload to save copy + //input->getFrames(rgb,depth); + + active += 1; + + //if (depth.cols == 0) continue; + + // Must be in RGBA for GPU + Mat rgbt, rgba; + cv::cvtColor(chan1,rgbt, cv::COLOR_BGR2Lab); + cv::cvtColor(rgbt,rgba, cv::COLOR_BGR2BGRA); + + // Send to GPU and merge view into scene + //cam.gpu.updateParams(cam.params); + cam.gpu.updateData(chan2, rgba, cam.stream); + + //setLastRigidTransform(input->getPose().cast<float>()); + + //make the rigid transform available on the GPU + //m_hashData.updateParams(m_hashParams, cv::cuda::StreamAccessor::getStream(cam.stream)); + + //if (i > 0) cudaSafeCall(cudaStreamSynchronize(cv::cuda::StreamAccessor::getStream(cameras_[i-1].stream))); + + //allocate all hash blocks which are corresponding to depth map entries + //if (value("voxels", false)) _alloc(i, cv::cuda::StreamAccessor::getStream(cam.stream)); // Calculate normals } @@ -218,7 +299,7 @@ void SceneRep::integrate() { void SceneRep::garbage() { //_compactifyAllocated(); - if (value("voxels", false)) _garbageCollect(); + //if (value("voxels", false)) _garbageCollect(); //cudaSafeCall(cudaStreamSynchronize(integ_stream_)); } @@ -294,14 +375,14 @@ HashParams SceneRep::_parametersFromConfig() { params.m_flags = 0; params.m_flags |= (value("clipping", false)) ? ftl::voxhash::kFlagClipping : 0; params.m_flags |= (value("mls", false)) ? ftl::voxhash::kFlagMLS : 0; - params.m_maxBounds = make_int3( - value("bbox_x_max", 2.0f) / (params.m_virtualVoxelSize*SDF_BLOCK_SIZE), - value("bbox_y_max", 2.0f) / (params.m_virtualVoxelSize*SDF_BLOCK_SIZE), - value("bbox_z_max", 2.0f) / (params.m_virtualVoxelSize*SDF_BLOCK_SIZE)); - params.m_minBounds = make_int3( - value("bbox_x_min", -2.0f) / (params.m_virtualVoxelSize*SDF_BLOCK_SIZE), - value("bbox_y_min", -2.0f) / (params.m_virtualVoxelSize*SDF_BLOCK_SIZE), - value("bbox_z_min", -2.0f) / (params.m_virtualVoxelSize*SDF_BLOCK_SIZE)); + params.m_maxBounds = make_float3( + value("bbox_x_max", 2.0f), + value("bbox_y_max", 2.0f), + value("bbox_z_max", 2.0f)); + params.m_minBounds = make_float3( + value("bbox_x_min", -2.0f), + value("bbox_y_min", -2.0f), + value("bbox_z_min", -2.0f)); return params; } @@ -338,19 +419,19 @@ void SceneRep::_alloc(int camid, cudaStream_t stream) { } else {*/ //this version is faster, but it doesn't guarantee that all blocks are allocated (staggers alloc to the next frame) - resetHashBucketMutexCUDA(m_hashData, m_hashParams, stream); - allocCUDA(m_hashData, m_hashParams, camid, cameras_[camid].params, stream); + //resetHashBucketMutexCUDA(m_hashData, m_hashParams, stream); + //allocCUDA(m_hashData, m_hashParams, camid, cameras_[camid].params, stream); //} } void SceneRep::_compactifyVisible(const DepthCameraParams &camera) { //const DepthCameraData& depthCameraData) { - ftl::cuda::compactifyOccupied(m_hashData, m_hashParams, integ_stream_); //this version uses atomics over prefix sums, which has a much better performance + //ftl::cuda::compactifyOccupied(m_hashData, m_hashParams, integ_stream_); //this version uses atomics over prefix sums, which has a much better performance //m_hashData.updateParams(m_hashParams); //make sure numOccupiedBlocks is updated on the GPU } void SceneRep::_compactifyAllocated() { - ftl::cuda::compactifyAllocated(m_hashData, m_hashParams, integ_stream_); //this version uses atomics over prefix sums, which has a much better performance + //ftl::cuda::compactifyAllocated(m_hashData, m_hashParams, integ_stream_); //this version uses atomics over prefix sums, which has a much better performance //std::cout << "Occ blocks = " << m_hashParams.m_numOccupiedBlocks << std::endl; //m_hashData.updateParams(m_hashParams); //make sure numOccupiedBlocks is updated on the GPU } @@ -360,7 +441,7 @@ void SceneRep::_compactifyAllocated() { else ftl::cuda::integrateRegistration(m_hashData, m_hashParams, depthCameraData, depthCameraParams, integ_stream_); }*/ -extern "C" void bilateralFilterFloatMap(float* d_output, float* d_input, float sigmaD, float sigmaR, unsigned int width, unsigned int height); +//extern "C" void bilateralFilterFloatMap(float* d_output, float* d_input, float sigmaD, float sigmaR, unsigned int width, unsigned int height); void SceneRep::_integrateDepthMaps() { //cudaSafeCall(cudaDeviceSynchronize()); @@ -375,11 +456,11 @@ void SceneRep::_integrateDepthMaps() { //ftl::cuda::hole_fill(*(cameras_[i].gpu.depth2_tex_), *(cameras_[i].gpu.depth_tex_), cameras_[i].params, integ_stream_); //bilateralFilterFloatMap(cameras_[i].gpu.depth_tex_->devicePtr(), cameras_[i].gpu.depth3_tex_->devicePtr(), 3, 7, cameras_[i].gpu.depth_tex_->width(), cameras_[i].gpu.depth_tex_->height()); } - if (value("voxels", false)) ftl::cuda::integrateDepthMaps(m_hashData, m_hashParams, cameras_.size(), integ_stream_); + //if (value("voxels", false)) ftl::cuda::integrateDepthMaps(m_hashData, m_hashParams, cameras_.size(), integ_stream_); } void SceneRep::_garbageCollect() { //ftl::cuda::garbageCollectIdentify(m_hashData, m_hashParams, integ_stream_); - resetHashBucketMutexCUDA(m_hashData, m_hashParams, integ_stream_); //needed if linked lists are enabled -> for memeory deletion - ftl::cuda::garbageCollectFree(m_hashData, m_hashParams, integ_stream_); + //resetHashBucketMutexCUDA(m_hashData, m_hashParams, integ_stream_); //needed if linked lists are enabled -> for memeory deletion + //ftl::cuda::garbageCollectFree(m_hashData, m_hashParams, integ_stream_); } diff --git a/applications/registration/src/aruco.cpp b/applications/registration/src/aruco.cpp index 8ff84854603123f0799455f223bcd98506b0b2e0..99d3552d9693cb548384043cef741703f115ce05 100644 --- a/applications/registration/src/aruco.cpp +++ b/applications/registration/src/aruco.cpp @@ -35,6 +35,8 @@ void ftl::registration::aruco(ftl::Configurable *root) { if (sources.size() == 0) return; + for (auto *src : sources) src->setChannel(ftl::rgbd::kChanDepth); + cv::namedWindow("Target", cv::WINDOW_KEEPRATIO); cv::namedWindow("Source", cv::WINDOW_KEEPRATIO); diff --git a/applications/vision/CMakeLists.txt b/applications/vision/CMakeLists.txt index 684b195b5a2a8605e9bc3e04e629d21a35dcd545..1753488f3407b74518efad555ecdd87be54ba1e9 100644 --- a/applications/vision/CMakeLists.txt +++ b/applications/vision/CMakeLists.txt @@ -21,6 +21,6 @@ set_property(TARGET ftl-vision PROPERTY CUDA_SEPARABLE_COMPILATION OFF) endif() #target_include_directories(cv-node PUBLIC ${PROJECT_SOURCE_DIR}/include) -target_link_libraries(ftl-vision ftlrgbd ftlcommon ftlctrl ftlrender ${OpenCV_LIBS} ${LIBSGM_LIBRARIES} ${CUDA_LIBRARIES} ftlnet) +target_link_libraries(ftl-vision ftlrgbd ftlcommon ftlctrl ${OpenCV_LIBS} ${LIBSGM_LIBRARIES} ${CUDA_LIBRARIES} ftlnet) diff --git a/applications/vision/src/main.cpp b/applications/vision/src/main.cpp index 1ce6cb162c322d82a51988fe63a9256505283b12..ba07e90424db91c5cc36e18d0aa40a68109bddbd 100644 --- a/applications/vision/src/main.cpp +++ b/applications/vision/src/main.cpp @@ -19,7 +19,7 @@ #include <opencv2/opencv.hpp> #include <ftl/rgbd.hpp> #include <ftl/middlebury.hpp> -#include <ftl/display.hpp> +//#include <ftl/display.hpp> #include <ftl/rgbd/streamer.hpp> #include <ftl/net/universe.hpp> #include <ftl/slave.hpp> @@ -36,7 +36,7 @@ using ftl::rgbd::Source; using ftl::rgbd::Camera; -using ftl::Display; +//using ftl::Display; using ftl::rgbd::Streamer; using ftl::net::Universe; using std::string; @@ -87,7 +87,7 @@ static void run(ftl::Configurable *root) { if (file != "") source->set("uri", file); - Display *display = ftl::create<Display>(root, "display", "local"); + //Display *display = ftl::create<Display>(root, "display", "local"); Streamer *stream = ftl::create<Streamer>(root, "stream", net); stream->add(source); @@ -95,7 +95,7 @@ static void run(ftl::Configurable *root) { net->start(); LOG(INFO) << "Running..."; - if (display->hasDisplays()) { + /*if (display->hasDisplays()) { stream->run(); while (ftl::running && display->active()) { cv::Mat rgb, depth; @@ -103,9 +103,9 @@ static void run(ftl::Configurable *root) { if (!rgb.empty()) display->render(rgb, depth, source->parameters()); display->wait(10); } - } else { + } else {*/ stream->run(true); - } + //} LOG(INFO) << "Stopping..."; slave.stop(); @@ -115,7 +115,7 @@ static void run(ftl::Configurable *root) { ftl::pool.stop(); delete stream; - delete display; + //delete display; //delete source; // TODO(Nick) Add ftl::destroy delete net; } diff --git a/components/codecs/CMakeLists.txt b/components/codecs/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..f951f94a6c10127c989862d154dbe6cee896812a --- /dev/null +++ b/components/codecs/CMakeLists.txt @@ -0,0 +1,26 @@ +set(CODECSRC + src/bitrates.cpp + src/encoder.cpp + src/decoder.cpp + src/opencv_encoder.cpp + src/opencv_decoder.cpp + src/generate.cpp +) + +if (HAVE_NVPIPE) + list(APPEND CODECSRC src/nvpipe_encoder.cpp) + list(APPEND CODECSRC src/nvpipe_decoder.cpp) +endif() + +add_library(ftlcodecs ${CODECSRC}) + +target_include_directories(ftlcodecs PUBLIC + $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> + $<INSTALL_INTERFACE:include> + PRIVATE src) + +#target_include_directories(cv-node PUBLIC ${PROJECT_SOURCE_DIR}/include) +target_link_libraries(ftlcodecs ftlcommon ${OpenCV_LIBS} ${CUDA_LIBRARIES} Eigen3::Eigen nvpipe) + +add_subdirectory(test) + diff --git a/components/codecs/README.md b/components/codecs/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/components/codecs/include/ftl/codecs/bitrates.hpp b/components/codecs/include/ftl/codecs/bitrates.hpp new file mode 100644 index 0000000000000000000000000000000000000000..19572daa5600fd7b78106e951307e92e17f51e33 --- /dev/null +++ b/components/codecs/include/ftl/codecs/bitrates.hpp @@ -0,0 +1,118 @@ +#ifndef _FTL_CODECS_BITRATES_HPP_ +#define _FTL_CODECS_BITRATES_HPP_ + +#include <cstdint> +#include <msgpack.hpp> + +namespace ftl { +namespace codecs { + +/** + * Compression format used. + */ +enum struct codec_t : uint8_t { + JPG = 0, + PNG, + H264, + HEVC // H265 +}; + +/** + * Resolution of encoding. + */ +enum struct definition_t : uint8_t { + UHD8k = 0, + UHD4k = 1, + HD1080 = 2, + HD720 = 3, + SD576 = 4, + SD480 = 5, + LD360 = 6, + Any = 7 +}; + +/** + * Get width in pixels of definition. + */ +int getWidth(definition_t); + +/** + * Get height in pixels of definition. + */ +int getHeight(definition_t); + +/** + * General indication of desired quality. Exact bitrate numbers depends also + * upon chosen definition and codec. + */ +enum struct bitrate_t { + High, + Standard, + Low +}; + +/** + * Pre-specified definition and quality levels for encoding where kPreset0 is + * the best quality and kPreset9 is the worst. The use of presets is useful for + * adaptive bitrate scenarios where the numbers are increased or decreased. + */ +typedef uint8_t preset_t; +static const preset_t kPreset0 = 0; +static const preset_t kPreset1 = 1; +static const preset_t kPreset2 = 2; +static const preset_t kPreset3 = 3; +static const preset_t kPreset4 = 4; +static const preset_t kPreset5 = 5; +static const preset_t kPreset6 = 6; +static const preset_t kPreset7 = 7; +static const preset_t kPreset8 = 8; +static const preset_t kPreset9 = 9; +static const preset_t kPresetBest = 0; +static const preset_t kPresetWorst = 9; +static const preset_t kPresetLQThreshold = 4; + +/** + * Represents the details of each preset codec configuration. + */ +struct CodecPreset { + definition_t colour_res; + definition_t depth_res; + bitrate_t colour_qual; + bitrate_t depth_qual; +}; + +/** + * Get preset details structure from preset number. + */ +const CodecPreset &getPreset(preset_t); + +/** + * Get preset based upon a requested definition. If multiple presets match then + * the highest quality one is returned. + */ +const CodecPreset &getPreset(definition_t, definition_t); + +/** + * Get preset based upon a requested definition for colour channel. + * If multiple presets match then the highest quality one is returned. + */ +const CodecPreset &getPreset(definition_t); + +/** + * Get the preset id nearest to requested definition for colour and depth. + */ +preset_t findPreset(definition_t, definition_t); + +/** + * Get the preset id nearest to requested colour definition. If multiple presets + * match then return highest quality. + */ +preset_t findPreset(definition_t); + +} +} + +MSGPACK_ADD_ENUM(ftl::codecs::codec_t); +MSGPACK_ADD_ENUM(ftl::codecs::definition_t); + +#endif // _FTL_CODECS_BITRATES_HPP_ diff --git a/components/codecs/include/ftl/codecs/decoder.hpp b/components/codecs/include/ftl/codecs/decoder.hpp new file mode 100644 index 0000000000000000000000000000000000000000..e6883680f34085f8b39fbcd02959b19ea4a2ca9e --- /dev/null +++ b/components/codecs/include/ftl/codecs/decoder.hpp @@ -0,0 +1,46 @@ +#ifndef _FTL_CODECS_DECODER_HPP_ +#define _FTL_CODECS_DECODER_HPP_ + +#include <opencv2/opencv.hpp> +#include <opencv2/core/cuda.hpp> + +#include <ftl/codecs/packet.hpp> + + +namespace ftl { +namespace codecs { + +class Decoder; + +/** + * Allocate a decoder that can handle the received packet. + */ +Decoder *allocateDecoder(const ftl::codecs::Packet &); + +/** + * Release a decoder to be reused by some other stream. + */ +void free(Decoder *&e); + +/** + * An abstract interface for a frame decoder. An implementation of this class + * will take codec packets and reconstruct an image or partial image into a + * specified OpenCV Mat. It is not the job of the decoder to check when a frame + * is completed, the user of the decoder should check the packet block number + * and total to ensure all packets are received for a given frame. The decoder + * is threadsafe. + */ +class Decoder { + public: + Decoder() {}; + virtual ~Decoder() {}; + + virtual bool decode(const ftl::codecs::Packet &pkt, cv::Mat &out)=0; + + virtual bool accepts(const ftl::codecs::Packet &)=0; +}; + +} +} + +#endif // _FTL_CODECS_DECODER_HPP_ diff --git a/components/codecs/include/ftl/codecs/encoder.hpp b/components/codecs/include/ftl/codecs/encoder.hpp new file mode 100644 index 0000000000000000000000000000000000000000..560810b84060b3b062b426614da40836634fdee5 --- /dev/null +++ b/components/codecs/include/ftl/codecs/encoder.hpp @@ -0,0 +1,99 @@ +#ifndef _FTL_CODECS_ENCODER_HPP_ +#define _FTL_CODECS_ENCODER_HPP_ + +#include <ftl/cuda_util.hpp> +#include <opencv2/opencv.hpp> +#include <opencv2/core/cuda.hpp> + +#include <ftl/codecs/bitrates.hpp> +#include <ftl/codecs/packet.hpp> + +namespace ftl { +namespace codecs { + +static const unsigned int kVideoBufferSize = 10*1024*1024; + +class Encoder; + +enum class device_t { + Any = 0, + Hardware, + Software, + NVIDIA, + x265, + OpenCV +}; + +/** + * Allocate a high quality encoder. This is a hardware encoding device if + * available, or otherwise a CPU encoder. There are a restricted number of + * high quality encoders due to system resources. A nullptr is returned if no + * encoders are available, however, it will return a low quality encoder if no + * high quality encoders are available. + */ +Encoder *allocateEncoder( + ftl::codecs::definition_t maxdef=ftl::codecs::definition_t::HD1080, + ftl::codecs::device_t dev=ftl::codecs::device_t::Any); + +/** + * Release an encoder to be reused by some other stream. + */ +void free(Encoder *&e); + +/** + * Abstract encoder interface to be implemented. Anything implementing this will + * convert an OpenCV Mat or GpuMat into a compressed byte array of some form. + */ +class Encoder { + public: + friend Encoder *allocateEncoder(ftl::codecs::definition_t, + ftl::codecs::device_t); + friend void free(Encoder *&); + + public: + Encoder(ftl::codecs::definition_t maxdef, + ftl::codecs::definition_t mindef, + ftl::codecs::device_t dev); + virtual ~Encoder(); + + /** + * Wrapper encode to allow use of presets. + */ + virtual bool encode(const cv::Mat &in, ftl::codecs::preset_t preset, + const std::function<void(const ftl::codecs::Packet&)> &cb); + + /** + * Encode a frame at specified preset and call a callback function for each + * block as it is encoded. The callback may be called only once with the + * entire frame or it may be called many times from many threads with + * partial blocks of a frame. Each packet should be sent over the network + * to all listening clients. + * + * @param in An OpenCV image of the frame to compress + * @param preset Codec preset for resolution and quality + * @param iframe Full frame if true, else might be a delta frame + * @param cb Callback containing compressed data + * @return True if succeeded with encoding. + */ + virtual bool encode( + const cv::Mat &in, + ftl::codecs::definition_t definition, + ftl::codecs::bitrate_t bitrate, + const std::function<void(const ftl::codecs::Packet&)> &cb)=0; + + // TODO: Eventually, use GPU memory directly since some encoders can support this + //virtual bool encode(const cv::cuda::GpuMat &in, std::vector<uint8_t> &out, bitrate_t bix, bool)=0; + + virtual void reset() {} + + protected: + bool available; + const ftl::codecs::definition_t max_definition; + const ftl::codecs::definition_t min_definition; + const ftl::codecs::device_t device; +}; + +} +} + +#endif // _FTL_CODECS_ENCODER_HPP_ diff --git a/components/codecs/include/ftl/codecs/nvpipe_decoder.hpp b/components/codecs/include/ftl/codecs/nvpipe_decoder.hpp new file mode 100644 index 0000000000000000000000000000000000000000..c13b26ec0c3ab72198f357bdb1f1ad8fae8d1f5e --- /dev/null +++ b/components/codecs/include/ftl/codecs/nvpipe_decoder.hpp @@ -0,0 +1,31 @@ +#ifndef _FTL_CODECS_NVPIPE_DECODER_HPP_ +#define _FTL_CODECS_NVPIPE_DECODER_HPP_ + +#include <ftl/codecs/decoder.hpp> +#include <ftl/threads.hpp> + +#include <NvPipe.h> + +namespace ftl { +namespace codecs { + +class NvPipeDecoder : public ftl::codecs::Decoder { + public: + NvPipeDecoder(); + ~NvPipeDecoder(); + + bool decode(const ftl::codecs::Packet &pkt, cv::Mat &out); + + bool accepts(const ftl::codecs::Packet &pkt); + + private: + NvPipe *nv_decoder_; + bool is_float_channel_; + ftl::codecs::definition_t last_definition_; + MUTEX mutex_; +}; + +} +} + +#endif // _FTL_CODECS_NVPIPE_DECODER_HPP_ diff --git a/components/codecs/include/ftl/codecs/nvpipe_encoder.hpp b/components/codecs/include/ftl/codecs/nvpipe_encoder.hpp new file mode 100644 index 0000000000000000000000000000000000000000..3f8de66cf7b77265e245a30da4522936058426c0 --- /dev/null +++ b/components/codecs/include/ftl/codecs/nvpipe_encoder.hpp @@ -0,0 +1,42 @@ +#ifndef _FTL_CODECS_NVPIPE_ENCODER_HPP_ +#define _FTL_CODECS_NVPIPE_ENCODER_HPP_ + +#include <ftl/codecs/encoder.hpp> +#include <NvPipe.h> + +namespace ftl { +namespace codecs { + +class NvPipeEncoder : public ftl::codecs::Encoder { + public: + NvPipeEncoder(ftl::codecs::definition_t maxdef, + ftl::codecs::definition_t mindef); + ~NvPipeEncoder(); + + bool encode(const cv::Mat &in, ftl::codecs::preset_t preset, + const std::function<void(const ftl::codecs::Packet&)> &cb) { + return Encoder::encode(in, preset, cb); + } + + bool encode(const cv::Mat &in, ftl::codecs::definition_t definition, ftl::codecs::bitrate_t bitrate, + const std::function<void(const ftl::codecs::Packet&)>&) override; + + //bool encode(const cv::cuda::GpuMat &in, std::vector<uint8_t> &out, bitrate_t bix, bool); + + void reset(); + + private: + NvPipe *nvenc_; + definition_t current_definition_; + bool is_float_channel_; + bool was_reset_; + + bool _encoderMatch(const cv::Mat &in, definition_t def); + bool _createEncoder(const cv::Mat &in, definition_t def, bitrate_t rate); + ftl::codecs::definition_t _verifiedDefinition(ftl::codecs::definition_t def, const cv::Mat &in); +}; + +} +} + +#endif // _FTL_CODECS_NVPIPE_ENCODER_HPP_ diff --git a/components/codecs/include/ftl/codecs/opencv_decoder.hpp b/components/codecs/include/ftl/codecs/opencv_decoder.hpp new file mode 100644 index 0000000000000000000000000000000000000000..c4ef5ab057b1e60da12f36fd603642ea16026888 --- /dev/null +++ b/components/codecs/include/ftl/codecs/opencv_decoder.hpp @@ -0,0 +1,22 @@ +#ifndef _FTL_CODECS_OPENCV_DECODER_HPP_ +#define _FTL_CODECS_OPENCV_DECODER_HPP_ + +#include <ftl/codecs/decoder.hpp> + +namespace ftl { +namespace codecs { + +class OpenCVDecoder : public ftl::codecs::Decoder { + public: + OpenCVDecoder(); + ~OpenCVDecoder(); + + bool decode(const ftl::codecs::Packet &pkt, cv::Mat &out); + + bool accepts(const ftl::codecs::Packet &pkt); +}; + +} +} + +#endif // _FTL_CODECS_OPENCV_DECODER_HPP_ diff --git a/components/codecs/include/ftl/codecs/opencv_encoder.hpp b/components/codecs/include/ftl/codecs/opencv_encoder.hpp new file mode 100644 index 0000000000000000000000000000000000000000..a34f8811de62277b5be53dd329517deb3ed06af3 --- /dev/null +++ b/components/codecs/include/ftl/codecs/opencv_encoder.hpp @@ -0,0 +1,48 @@ +#ifndef _FTL_CODECS_OPENCV_ENCODER_HPP_ +#define _FTL_CODECS_OPENCV_ENCODER_HPP_ + +#include <ftl/codecs/encoder.hpp> +#include <ftl/threads.hpp> +#include <condition_variable> + +namespace ftl { +namespace codecs { + +/** + * Use OpenCV imencode to encode frames as images. This codec divides each + * image into blocks and uses a thread per block to compress each one. It is + * a simplistic codec that can function at high resolution but is best used for + * low definition encoding due to the poor compressed data size. + */ +class OpenCVEncoder : public ftl::codecs::Encoder { + public: + OpenCVEncoder(ftl::codecs::definition_t maxdef, + ftl::codecs::definition_t mindef); + ~OpenCVEncoder(); + + bool encode(const cv::Mat &in, ftl::codecs::preset_t preset, + const std::function<void(const ftl::codecs::Packet&)> &cb) { + return Encoder::encode(in, preset, cb); + } + + bool encode(const cv::Mat &in, ftl::codecs::definition_t definition, ftl::codecs::bitrate_t bitrate, + const std::function<void(const ftl::codecs::Packet&)>&) override; + + //bool encode(const cv::cuda::GpuMat &in, std::vector<uint8_t> &out, bitrate_t bix, bool); + + private: + int chunk_count_; + int chunk_dim_; + ftl::codecs::preset_t current_preset_; + ftl::codecs::definition_t current_definition_; + std::atomic<int> jobs_; + std::mutex job_mtx_; + std::condition_variable job_cv_; + + bool _encodeBlock(const cv::Mat &in, ftl::codecs::Packet &pkt, ftl::codecs::bitrate_t); +}; + +} +} + +#endif // _FTL_CODECS_OPENCV_ENCODER_HPP_ diff --git a/components/codecs/include/ftl/codecs/packet.hpp b/components/codecs/include/ftl/codecs/packet.hpp new file mode 100644 index 0000000000000000000000000000000000000000..98e46a60122e6813fd22b0c0be0f09e40fbc96ce --- /dev/null +++ b/components/codecs/include/ftl/codecs/packet.hpp @@ -0,0 +1,44 @@ +#ifndef _FTL_CODECS_PACKET_HPP_ +#define _FTL_CODECS_PACKET_HPP_ + +#include <cstdint> +#include <vector> +#include <ftl/codecs/bitrates.hpp> + +#include <msgpack.hpp> + +namespace ftl { +namespace codecs { + +/** + * A single network packet for the compressed video stream. It includes the raw + * data along with any block metadata required to reconstruct. The underlying + * codec may use its own blocks and packets, in which case this is essentially + * an empty wrapper around that. It is used in the encoding callback. + */ +struct Packet { + ftl::codecs::codec_t codec; + ftl::codecs::definition_t definition; + uint8_t block_total; // Packets expected per frame + uint8_t block_number; // This packets number within a frame + std::vector<uint8_t> data; + + MSGPACK_DEFINE(codec, definition, block_total, block_number, data); +}; + +/** + * Add timestamp and channel information to a raw encoded frame packet. This + * allows the packet to be located within a larger stream and should be sent + * or included before a frame packet structure. + */ +struct StreamPacket { + int64_t timestamp; + uint8_t channel; // first bit = channel, second bit indicates second channel being sent + + MSGPACK_DEFINE(timestamp, channel); +}; + +} +} + +#endif // _FTL_CODECS_PACKET_HPP_ diff --git a/components/codecs/src/bitrates.cpp b/components/codecs/src/bitrates.cpp new file mode 100644 index 0000000000000000000000000000000000000000..519c86712487a8be9285f28392342e6ca6c16713 --- /dev/null +++ b/components/codecs/src/bitrates.cpp @@ -0,0 +1,53 @@ + +#include <ftl/codecs/bitrates.hpp> +#include <cmath> + +using ftl::codecs::CodecPreset; +using ftl::codecs::bitrate_t; +using ftl::codecs::preset_t; +using ftl::codecs::definition_t; +using ftl::codecs::codec_t; + +static const CodecPreset presets[] = { + definition_t::HD1080, definition_t::HD1080, bitrate_t::High, bitrate_t::High, + definition_t::HD1080, definition_t::HD720, bitrate_t::Standard, bitrate_t::Standard, + definition_t::HD720, definition_t::HD720, bitrate_t::High, bitrate_t::High, + definition_t::HD720, definition_t::SD576, bitrate_t::Standard, bitrate_t::Standard, + definition_t::SD576, definition_t::SD576, bitrate_t::High, bitrate_t::High, + definition_t::SD576, definition_t::SD480, bitrate_t::Standard, bitrate_t::Standard, + definition_t::SD480, definition_t::SD480, bitrate_t::High, bitrate_t::High, + definition_t::SD480, definition_t::LD360, bitrate_t::Standard, bitrate_t::Standard, + definition_t::LD360, definition_t::LD360, bitrate_t::Standard, bitrate_t::Standard, + definition_t::LD360, definition_t::LD360, bitrate_t::Low, bitrate_t::Low +}; + +static const float kAspectRatio = 1.777778f; + +struct Resolution { + int width; + int height; +}; + +static const Resolution resolutions[] = { + 7680, 4320, // UHD8k + 3840, 2160, // UHD4k + 1920, 1080, // HD1080 + 1280, 720, // HD720 + 1024, 576, // SD576 + 854, 480, // SD480 + 640, 360 // LD360 +}; + +int ftl::codecs::getWidth(definition_t d) { + return resolutions[static_cast<int>(d)].width; +} + +int ftl::codecs::getHeight(definition_t d) { + return resolutions[static_cast<int>(d)].height; +} + +const CodecPreset &ftl::codecs::getPreset(preset_t p) { + if (p > kPresetWorst) return presets[kPresetWorst]; + if (p < kPresetBest) return presets[kPresetBest]; + return presets[p]; +}; diff --git a/components/codecs/src/decoder.cpp b/components/codecs/src/decoder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4cd5437d0cb6ea84251b55bdd139a7c0b799f411 --- /dev/null +++ b/components/codecs/src/decoder.cpp @@ -0,0 +1,25 @@ +#include <ftl/codecs/decoder.hpp> + +#include <ftl/codecs/opencv_decoder.hpp> +#include <ftl/codecs/nvpipe_decoder.hpp> + +using ftl::codecs::Decoder; +using ftl::codecs::codec_t; + +Decoder *ftl::codecs::allocateDecoder(const ftl::codecs::Packet &pkt) { + switch(pkt.codec) { + case codec_t::JPG : + case codec_t::PNG : return new ftl::codecs::OpenCVDecoder; + case codec_t::HEVC : return new ftl::codecs::NvPipeDecoder; + } + + return nullptr; +} + +/** + * Release a decoder to be reused by some other stream. + */ +void ftl::codecs::free(Decoder *&e) { + delete e; + e = nullptr; +} diff --git a/components/codecs/src/encoder.cpp b/components/codecs/src/encoder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3ebe40cf91ad75cec92937b013d3215de4104004 --- /dev/null +++ b/components/codecs/src/encoder.cpp @@ -0,0 +1,77 @@ +#include <ftl/codecs/encoder.hpp> +#include <ftl/threads.hpp> + +#include <list> + +#include <ftl/config.h> +#include <loguru.hpp> + +using ftl::codecs::Encoder; +using ftl::codecs::bitrate_t; +using ftl::codecs::definition_t; +using ftl::codecs::preset_t; +using ftl::codecs::device_t; +using ftl::codecs::kPresetBest; +using ftl::codecs::kPresetWorst; +using ftl::codecs::kPresetLQThreshold; + +namespace ftl { +namespace codecs { +namespace internal { + +std::list<Encoder*> encoders; + +bool has_been_init = false; + +void init_encoders(); + +} +} +} + +using namespace ftl::codecs::internal; + +static MUTEX mutex; + +Encoder *ftl::codecs::allocateEncoder(ftl::codecs::definition_t maxdef, + ftl::codecs::device_t dev) { + UNIQUE_LOCK(mutex, lk); + if (!has_been_init) init_encoders(); + + for (auto i=encoders.begin(); i!=encoders.end(); ++i) { + auto *e = *i; + if (!e->available) continue; + if (dev != device_t::Any && dev != e->device) continue; + if (maxdef != definition_t::Any && (maxdef < e->max_definition || maxdef > e->min_definition)) continue; + + e->available = false; + return e; + } + + LOG(ERROR) << "No encoder found"; + return nullptr; +} + +void ftl::codecs::free(Encoder *&enc) { + UNIQUE_LOCK(mutex, lk); + enc->reset(); + enc->available = true; + enc = nullptr; +} + +Encoder::Encoder(definition_t maxdef, definition_t mindef, device_t dev) : + available(true), max_definition(maxdef), min_definition(mindef), device(dev) { + +} + +Encoder::~Encoder() { + +} + +bool Encoder::encode(const cv::Mat &in, preset_t preset, + const std::function<void(const ftl::codecs::Packet&)> &cb) { + const auto &settings = ftl::codecs::getPreset(preset); + const definition_t definition = (in.type() == CV_32F) ? settings.depth_res : settings.colour_res; + const bitrate_t bitrate = (in.type() == CV_32F) ? settings.depth_qual : settings.colour_qual; + return encode(in, definition, bitrate, cb); +} diff --git a/components/codecs/src/generate.cpp b/components/codecs/src/generate.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b6b3c3be98aeef8a1672ea1322c359c0632f3197 --- /dev/null +++ b/components/codecs/src/generate.cpp @@ -0,0 +1,45 @@ +#include <ftl/codecs/encoder.hpp> +#include <ftl/codecs/opencv_encoder.hpp> +#include <ftl/configurable.hpp> + +#include <ftl/config.h> +#include <loguru.hpp> + +#ifdef HAVE_NVPIPE +#include <ftl/codecs/nvpipe_encoder.hpp> +#endif + +namespace ftl { +namespace codecs { +namespace internal { + +extern std::list<Encoder*> encoders; +extern bool has_been_init; + +void fin_encoders() { + LOG(INFO) << "Destroying encoders..."; + for (auto *s : encoders) delete s; +} + +void init_encoders() { + #ifdef HAVE_NVPIPE + LOG(INFO) << "Adding NvPipe Encoders"; + encoders.push_back(new ftl::codecs::NvPipeEncoder(definition_t::UHD4k, definition_t::HD720)); + encoders.push_back(new ftl::codecs::NvPipeEncoder(definition_t::UHD4k, definition_t::HD720)); + #endif + + encoders.push_back(new ftl::codecs::OpenCVEncoder(definition_t::HD1080, definition_t::HD720)); + encoders.push_back(new ftl::codecs::OpenCVEncoder(definition_t::HD1080, definition_t::HD720)); + encoders.push_back(new ftl::codecs::OpenCVEncoder(definition_t::SD576, definition_t::LD360)); + encoders.push_back(new ftl::codecs::OpenCVEncoder(definition_t::SD576, definition_t::LD360)); + encoders.push_back(new ftl::codecs::OpenCVEncoder(definition_t::SD576, definition_t::LD360)); + encoders.push_back(new ftl::codecs::OpenCVEncoder(definition_t::SD576, definition_t::LD360)); + + has_been_init = true; + + atexit(fin_encoders); +} + +} +} +} \ No newline at end of file diff --git a/components/codecs/src/nvpipe_decoder.cpp b/components/codecs/src/nvpipe_decoder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0dda5884cc7bf156e7cb134795e6f0ff2c180130 --- /dev/null +++ b/components/codecs/src/nvpipe_decoder.cpp @@ -0,0 +1,80 @@ +#include <ftl/codecs/nvpipe_decoder.hpp> + +#include <loguru.hpp> + +#include <ftl/cuda_util.hpp> +//#include <cuda_runtime.h> + +using ftl::codecs::NvPipeDecoder; + +NvPipeDecoder::NvPipeDecoder() { + nv_decoder_ = nullptr; +} + +NvPipeDecoder::~NvPipeDecoder() { + if (nv_decoder_ != nullptr) { + NvPipe_Destroy(nv_decoder_); + } +} + +bool NvPipeDecoder::decode(const ftl::codecs::Packet &pkt, cv::Mat &out) { + cudaSetDevice(0); + UNIQUE_LOCK(mutex_,lk); + if (pkt.codec != codec_t::HEVC) return false; + + bool is_float_frame = out.type() == CV_32F; + + // Is the previous decoder still valid for current resolution and type? + if (nv_decoder_ != nullptr && (last_definition_ != pkt.definition || is_float_channel_ != is_float_frame)) { + NvPipe_Destroy(nv_decoder_); + nv_decoder_ = nullptr; + } + + is_float_channel_ = is_float_frame; + last_definition_ = pkt.definition; + + // Build a decoder instance of the correct kind + if (nv_decoder_ == nullptr) { + nv_decoder_ = NvPipe_CreateDecoder( + (is_float_frame) ? NVPIPE_UINT16 : NVPIPE_RGBA32, + NVPIPE_HEVC, + ftl::codecs::getWidth(pkt.definition), + ftl::codecs::getHeight(pkt.definition)); + if (!nv_decoder_) { + //LOG(INFO) << "Bitrate=" << (int)bitrate << " width=" << ABRController::getColourWidth(bitrate); + LOG(FATAL) << "Could not create decoder: " << NvPipe_GetError(NULL); + } else { + LOG(INFO) << "Decoder created"; + } + } + + // TODO: (Nick) Move to member variable to prevent re-creation + cv::Mat tmp(cv::Size(ftl::codecs::getWidth(pkt.definition),ftl::codecs::getHeight(pkt.definition)), (is_float_frame) ? CV_16U : CV_8UC4); + + int rc = NvPipe_Decode(nv_decoder_, pkt.data.data(), pkt.data.size(), tmp.data, tmp.cols, tmp.rows); + if (rc == 0) LOG(ERROR) << "NvPipe decode error: " << NvPipe_GetError(nv_decoder_); + + if (is_float_frame) { + // Is the received frame the same size as requested output? + if (out.rows == ftl::codecs::getHeight(pkt.definition)) { + tmp.convertTo(out, CV_32FC1, 1.0f/1000.0f); + } else { + tmp.convertTo(tmp, CV_32FC1, 1.0f/1000.0f); + cv::resize(tmp, out, out.size()); + } + } else { + // Is the received frame the same size as requested output? + if (out.rows == ftl::codecs::getHeight(pkt.definition)) { + cv::cvtColor(tmp, out, cv::COLOR_BGRA2BGR); + } else { + cv::cvtColor(tmp, tmp, cv::COLOR_BGRA2BGR); + cv::resize(tmp, out, out.size()); + } + } + + return rc > 0; +} + +bool NvPipeDecoder::accepts(const ftl::codecs::Packet &pkt) { + return pkt.codec == codec_t::HEVC; +} diff --git a/components/codecs/src/nvpipe_encoder.cpp b/components/codecs/src/nvpipe_encoder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..42374bedecf95a749e842a3a2ed22b5226d6d390 --- /dev/null +++ b/components/codecs/src/nvpipe_encoder.cpp @@ -0,0 +1,123 @@ +#include <ftl/codecs/nvpipe_encoder.hpp> +#include <loguru.hpp> +#include <ftl/timer.hpp> +#include <ftl/codecs/bitrates.hpp> +#include <ftl/cuda_util.hpp> + +using ftl::codecs::NvPipeEncoder; +using ftl::codecs::bitrate_t; +using ftl::codecs::codec_t; +using ftl::codecs::definition_t; +using ftl::codecs::preset_t; +using ftl::codecs::CodecPreset; +using ftl::codecs::Packet; + +NvPipeEncoder::NvPipeEncoder(definition_t maxdef, + definition_t mindef) : Encoder(maxdef, mindef, ftl::codecs::device_t::Hardware) { + nvenc_ = nullptr; + current_definition_ = definition_t::HD1080; + is_float_channel_ = false; + was_reset_ = false; +} + +NvPipeEncoder::~NvPipeEncoder() { + if (nvenc_) NvPipe_Destroy(nvenc_); +} + +void NvPipeEncoder::reset() { + was_reset_ = true; +} + +/* Check preset resolution is not better than actual resolution. */ +definition_t NvPipeEncoder::_verifiedDefinition(definition_t def, const cv::Mat &in) { + int height = ftl::codecs::getHeight(def); + + // FIXME: Make sure this can't go forever + while (height > in.rows) { + def = static_cast<definition_t>(int(def)+1); + height = ftl::codecs::getHeight(def); + } + + return def; +} + +bool NvPipeEncoder::encode(const cv::Mat &in, definition_t odefinition, bitrate_t bitrate, const std::function<void(const ftl::codecs::Packet&)> &cb) { + cudaSetDevice(0); + auto definition = _verifiedDefinition(odefinition, in); + + //LOG(INFO) << "Definition: " << ftl::codecs::getWidth(definition) << "x" << ftl::codecs::getHeight(definition); + + if (!_createEncoder(in, definition, bitrate)) return false; + + //LOG(INFO) << "NvPipe Encode: " << int(definition) << " " << in.cols; + + cv::Mat tmp; + if (in.type() == CV_32F) { + in.convertTo(tmp, CV_16UC1, 1000); + } else if (in.type() == CV_8UC3) { + cv::cvtColor(in, tmp, cv::COLOR_BGR2BGRA); + } else { + tmp = in; + } + + Packet pkt; + pkt.codec = codec_t::HEVC; + pkt.definition = definition; + pkt.block_total = 1; + pkt.block_number = 0; + + pkt.data.resize(ftl::codecs::kVideoBufferSize); + uint64_t cs = NvPipe_Encode( + nvenc_, + tmp.data, + tmp.step, + pkt.data.data(), + ftl::codecs::kVideoBufferSize, + in.cols, + in.rows, + was_reset_ // Force IFrame! + ); + pkt.data.resize(cs); + was_reset_ = false; + + if (cs == 0) { + LOG(ERROR) << "Could not encode video frame: " << NvPipe_GetError(nvenc_); + return false; + } else { + cb(pkt); + return true; + } +} + +bool NvPipeEncoder::_encoderMatch(const cv::Mat &in, definition_t def) { + return ((in.type() == CV_32F && is_float_channel_) || + ((in.type() == CV_8UC3 || in.type() == CV_8UC4) && !is_float_channel_)) && current_definition_ == def; +} + +bool NvPipeEncoder::_createEncoder(const cv::Mat &in, definition_t def, bitrate_t rate) { + if (_encoderMatch(in, def) && nvenc_) return true; + + if (in.type() == CV_32F) is_float_channel_ = true; + else is_float_channel_ = false; + current_definition_ = def; + + if (nvenc_) NvPipe_Destroy(nvenc_); + const int fps = 1000/ftl::timer::getInterval(); + nvenc_ = NvPipe_CreateEncoder( + (is_float_channel_) ? NVPIPE_UINT16 : NVPIPE_RGBA32, + NVPIPE_HEVC, + (is_float_channel_) ? NVPIPE_LOSSLESS : NVPIPE_LOSSY, + 16*1000*1000, + fps, // FPS + ftl::codecs::getWidth(def), // Output Width + ftl::codecs::getHeight(def) // Output Height + ); + + if (!nvenc_) { + LOG(ERROR) << "Could not create video encoder: " << NvPipe_GetError(NULL); + return false; + } else { + LOG(INFO) << "NvPipe encoder created"; + return true; + } +} diff --git a/components/codecs/src/opencv_decoder.cpp b/components/codecs/src/opencv_decoder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3bbd82fa21ae4b60fef00b81c409ae9c59311d39 --- /dev/null +++ b/components/codecs/src/opencv_decoder.cpp @@ -0,0 +1,65 @@ +#include <ftl/codecs/opencv_decoder.hpp> + +#include <loguru.hpp> + +using ftl::codecs::OpenCVDecoder; +using ftl::codecs::codec_t; + +OpenCVDecoder::OpenCVDecoder() { + +} + +OpenCVDecoder::~OpenCVDecoder() { + +} + +bool OpenCVDecoder::accepts(const ftl::codecs::Packet &pkt) { + return (pkt.codec == codec_t::JPG || pkt.codec == codec_t::PNG); +} + +bool OpenCVDecoder::decode(const ftl::codecs::Packet &pkt, cv::Mat &out) { + cv::Mat tmp; + + int chunk_dim = std::sqrt(pkt.block_total); + int chunk_width = out.cols / chunk_dim; + int chunk_height = out.rows / chunk_dim; + + // Build chunk head + int cx = (pkt.block_number % chunk_dim) * chunk_width; + int cy = (pkt.block_number / chunk_dim) * chunk_height; + cv::Rect roi(cx,cy,chunk_width,chunk_height); + cv::Mat chunkHead = out(roi); + + // Decode in temporary buffers to prevent long locks + cv::imdecode(pkt.data, cv::IMREAD_UNCHANGED, &tmp); + + // Apply colour correction to chunk + //ftl::rgbd::colourCorrection(tmp_rgb, gamma_, temperature_); + + + // TODO:(Nick) Decode directly into double buffer if no scaling + // Can either check JPG/PNG headers or just use pkt definition. + + // Original size so just copy + if (tmp.cols == chunkHead.cols) { + if (!tmp.empty() && tmp.type() == CV_16U && chunkHead.type() == CV_32F) { + tmp.convertTo(chunkHead, CV_32FC1, 1.0f/1000.0f); //(16.0f*10.0f)); + } else if (!tmp.empty() && tmp.type() == CV_8UC3 && chunkHead.type() == CV_8UC3) { + tmp.copyTo(chunkHead); + } else { + // Silent ignore? + } + // Downsized so needs a scale up + } else { + if (!tmp.empty() && tmp.type() == CV_16U && chunkHead.type() == CV_32F) { + tmp.convertTo(tmp, CV_32FC1, 1.0f/1000.0f); //(16.0f*10.0f)); + cv::resize(tmp, chunkHead, chunkHead.size(), 0, 0, cv::INTER_NEAREST); + } else if (!tmp.empty() && tmp.type() == CV_8UC3 && chunkHead.type() == CV_8UC3) { + cv::resize(tmp, chunkHead, chunkHead.size()); + } else { + // Silent ignore? + } + } + + return true; +} diff --git a/components/codecs/src/opencv_encoder.cpp b/components/codecs/src/opencv_encoder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5dafabf29a64c14969504960da8af2bc2e27fd2a --- /dev/null +++ b/components/codecs/src/opencv_encoder.cpp @@ -0,0 +1,116 @@ +#include <ftl/codecs/opencv_encoder.hpp> + +#include <loguru.hpp> +#include <vector> + +using ftl::codecs::definition_t; +using ftl::codecs::codec_t; +using ftl::codecs::bitrate_t; +using ftl::codecs::OpenCVEncoder; +using ftl::codecs::preset_t; +using ftl::codecs::CodecPreset; +using std::vector; + +OpenCVEncoder::OpenCVEncoder(ftl::codecs::definition_t maxdef, + ftl::codecs::definition_t mindef) : Encoder(maxdef, mindef, ftl::codecs::device_t::Software) { + jobs_ = 0; +} + +OpenCVEncoder::~OpenCVEncoder() { + +} + +bool OpenCVEncoder::encode(const cv::Mat &in, definition_t definition, bitrate_t bitrate, const std::function<void(const ftl::codecs::Packet&)> &cb) { + cv::Mat tmp; + bool is_colour = in.type() != CV_32F; + current_definition_ = definition; + + // Scale down image to match requested definition... + if (ftl::codecs::getHeight(current_definition_) < in.rows) { + cv::resize(in, tmp, cv::Size(ftl::codecs::getWidth(current_definition_), ftl::codecs::getHeight(current_definition_)), 0, 0, (is_colour) ? 1 : cv::INTER_NEAREST); + } else { + tmp = in; + } + + // Represent float at 16bit int + if (!is_colour) { + tmp.convertTo(tmp, CV_16UC1, 1000); + } + + // TODO: Choose these base upon resolution + chunk_count_ = 16; + chunk_dim_ = 4; + jobs_ = chunk_count_; + + for (int i=0; i<chunk_count_; ++i) { + // Add chunk job to thread pool + ftl::pool.push([this,i,&tmp,cb,is_colour,bitrate](int id) { + ftl::codecs::Packet pkt; + pkt.block_number = i; + pkt.block_total = chunk_count_; + pkt.definition = current_definition_; + pkt.codec = (is_colour) ? codec_t::JPG : codec_t::PNG; + + try { + _encodeBlock(tmp, pkt, bitrate); + } catch(...) { + LOG(ERROR) << "OpenCV encode block exception: " << i; + } + + try { + cb(pkt); + } catch(...) { + LOG(ERROR) << "OpenCV encoder callback exception"; + } + + std::unique_lock<std::mutex> lk(job_mtx_); + --jobs_; + if (jobs_ == 0) job_cv_.notify_one(); + }); + } + + std::unique_lock<std::mutex> lk(job_mtx_); + job_cv_.wait_for(lk, std::chrono::seconds(20), [this]{ return jobs_ == 0; }); + if (jobs_ != 0) { + LOG(FATAL) << "Deadlock detected (" << jobs_ << ")"; + } + + return true; +} + +bool OpenCVEncoder::_encodeBlock(const cv::Mat &in, ftl::codecs::Packet &pkt, bitrate_t bitrate) { + int chunk_width = in.cols / chunk_dim_; + int chunk_height = in.rows / chunk_dim_; + + // Build chunk heads + int cx = (pkt.block_number % chunk_dim_) * chunk_width; + int cy = (pkt.block_number / chunk_dim_) * chunk_height; + cv::Rect roi(cx,cy,chunk_width,chunk_height); + cv::Mat chunkHead = in(roi); + + if (pkt.codec == codec_t::PNG) { + vector<int> params = {cv::IMWRITE_PNG_COMPRESSION, 1}; + if (!cv::imencode(".png", chunkHead, pkt.data, params)) { + LOG(ERROR) << "PNG Encoding error"; + return false; + } + return true; + } else if (pkt.codec == codec_t::JPG) { + int q = 95; + + switch (bitrate) { + case bitrate_t::High : q = 95; break; + case bitrate_t::Standard : q = 75; break; + case bitrate_t::Low : q = 50; break; + } + + vector<int> params = {cv::IMWRITE_JPEG_QUALITY, q}; + cv::imencode(".jpg", chunkHead, pkt.data, params); + return true; + } else { + LOG(ERROR) << "Bad channel configuration: imagetype=" << in.type(); + } + + return false; +} + diff --git a/components/codecs/test/CMakeLists.txt b/components/codecs/test/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..534336fed6ce5135199105f0784fa260204f64de --- /dev/null +++ b/components/codecs/test/CMakeLists.txt @@ -0,0 +1,32 @@ +### OpenCV Codec Unit ################################################################ +add_executable(opencv_codec_unit + ./tests.cpp + ../src/bitrates.cpp + ../src/encoder.cpp + ../src/opencv_encoder.cpp + ../src/opencv_decoder.cpp + ./opencv_codec_unit.cpp +) +target_include_directories(opencv_codec_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include") +target_link_libraries(opencv_codec_unit + Threads::Threads ${OS_LIBS} ${OpenCV_LIBS} ftlcommon) + + +add_test(OpenCVCodecUnitTest opencv_codec_unit) + + +### NvPipe Codec Unit ################################################################ +add_executable(nvpipe_codec_unit + ./tests.cpp + ../src/bitrates.cpp + ../src/encoder.cpp + ../src/nvpipe_encoder.cpp + ../src/nvpipe_decoder.cpp + ./nvpipe_codec_unit.cpp +) +target_include_directories(nvpipe_codec_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include") +target_link_libraries(nvpipe_codec_unit + Threads::Threads ${OS_LIBS} ${OpenCV_LIBS} ${CUDA_LIBRARIES} ftlcommon nvpipe) + + +add_test(NvPipeCodecUnitTest nvpipe_codec_unit) diff --git a/components/codecs/test/catch.hpp b/components/codecs/test/catch.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b1b2411d24885571e21ec4b3653af58c57011c3e --- /dev/null +++ b/components/codecs/test/catch.hpp @@ -0,0 +1,14362 @@ +/* + * Catch v2.5.0 + * Generated: 2018-11-26 20:46:12.165372 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2018 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +// start catch.hpp + + +#define CATCH_VERSION_MAJOR 2 +#define CATCH_VERSION_MINOR 5 +#define CATCH_VERSION_PATCH 0 + +#ifdef __clang__ +# pragma clang system_header +#elif defined __GNUC__ +# pragma GCC system_header +#endif + +// start catch_suppress_warnings.h + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(push) +# pragma warning(disable: 161 1682) +# else // __ICC +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +# pragma clang diagnostic ignored "-Wswitch-enum" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +# endif +#elif defined __GNUC__ + // GCC likes to warn on REQUIREs, and we cannot suppress them + // locally because g++'s support for _Pragma is lacking in older, + // still supported, versions +# pragma GCC diagnostic ignored "-Wparentheses" +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic ignored "-Wpadded" +#endif +// end catch_suppress_warnings.h +#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) +# define CATCH_IMPL +# define CATCH_CONFIG_ALL_PARTS +#endif + +// In the impl file, we want to have access to all parts of the headers +// Can also be used to sanely support PCHs +#if defined(CATCH_CONFIG_ALL_PARTS) +# define CATCH_CONFIG_EXTERNAL_INTERFACES +# if defined(CATCH_CONFIG_DISABLE_MATCHERS) +# undef CATCH_CONFIG_DISABLE_MATCHERS +# endif +# if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +# endif +#endif + +#if !defined(CATCH_CONFIG_IMPL_ONLY) +// start catch_platform.h + +#ifdef __APPLE__ +# include <TargetConditionals.h> +# if TARGET_OS_OSX == 1 +# define CATCH_PLATFORM_MAC +# elif TARGET_OS_IPHONE == 1 +# define CATCH_PLATFORM_IPHONE +# endif + +#elif defined(linux) || defined(__linux) || defined(__linux__) +# define CATCH_PLATFORM_LINUX + +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) +# define CATCH_PLATFORM_WINDOWS +#endif + +// end catch_platform.h + +#ifdef CATCH_IMPL +# ifndef CLARA_CONFIG_MAIN +# define CLARA_CONFIG_MAIN_NOT_DEFINED +# define CLARA_CONFIG_MAIN +# endif +#endif + +// start catch_user_interfaces.h + +namespace Catch { + unsigned int rngSeed(); +} + +// end catch_user_interfaces.h +// start catch_tag_alias_autoregistrar.h + +// start catch_common.h + +// start catch_compiler_capabilities.h + +// Detect a number of compiler features - by compiler +// The following features are defined: +// +// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? +// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? +// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + +// In general each macro has a _NO_<feature name> form +// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +#ifdef __cplusplus + +# if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) +# define CATCH_CPP14_OR_GREATER +# endif + +# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define CATCH_CPP17_OR_GREATER +# endif + +#endif + +#if defined(CATCH_CPP17_OR_GREATER) +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + +#ifdef __clang__ + +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ + _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") +# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) +# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) +# define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// Assume that non-Windows platforms support posix signals by default +#if !defined(CATCH_PLATFORM_WINDOWS) + #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS +#endif + +//////////////////////////////////////////////////////////////////////////////// +// We know some environments not to support full POSIX signals +#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) + #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +#endif + +#ifdef __OS400__ +# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +# define CATCH_CONFIG_COLOUR_NONE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Android somehow still does not support std::to_string +#if defined(__ANDROID__) +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Not all Windows environments support SEH properly +#if defined(__MINGW32__) +# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#endif + +//////////////////////////////////////////////////////////////////////////////// +// PS4 +#if defined(__ORBIS__) +# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Cygwin +#ifdef __CYGWIN__ + +// Required for some versions of Cygwin to declare gettimeofday +// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +# define _BSD_SOURCE +// some versions of cygwin (most) do not support std::to_string. Use the libstd check. +// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 +# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ + && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) + +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING + +# endif +#endif // __CYGWIN__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#ifdef _MSC_VER + +# if _MSC_VER >= 1900 // Visual Studio 2015 or newer +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +# endif + +// Universal Windows platform does not support SEH +// Or console colours (or console at all...) +# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +# define CATCH_CONFIG_COLOUR_NONE +# else +# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH +# endif + +// MSVC traditional preprocessor needs some workaround for __VA_ARGS__ +// _MSVC_TRADITIONAL == 0 means new conformant preprocessor +// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor +# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) +# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +# endif + +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// +// Check if we are compiled with -fno-exceptions or equivalent +#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) +# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED +#endif + +//////////////////////////////////////////////////////////////////////////////// +// DJGPP +#ifdef __DJGPP__ +# define CATCH_INTERNAL_CONFIG_NO_WCHAR +#endif // __DJGPP__ + +//////////////////////////////////////////////////////////////////////////////// +// Embarcadero C++Build +#if defined(__BORLANDC__) + #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// Use of __COUNTER__ is suppressed during code analysis in +// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly +// handled by it. +// Otherwise all supported compilers support COUNTER macro, +// but user still might want to turn it off +#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) + #define CATCH_INTERNAL_CONFIG_COUNTER +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Check if string_view is available and usable +// The check is split apart to work around v140 (VS2015) preprocessor issue... +#if defined(__has_include) +#if __has_include(<string_view>) && defined(CATCH_CPP17_OR_GREATER) +# define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW +#endif +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Check if variant is available and usable +#if defined(__has_include) +# if __has_include(<variant>) && defined(CATCH_CPP17_OR_GREATER) +# if defined(__clang__) && (__clang_major__ < 8) + // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 + // fix should be in clang 8, workaround in libstdc++ 8.2 +# include <ciso646> +# if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) +# define CATCH_CONFIG_NO_CPP17_VARIANT +# else +# define CATCH_INTERNAL_CONFIG_CPP17_VARIANT +# endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) +# endif // defined(__clang__) && (__clang_major__ < 8) +# endif // __has_include(<variant>) && defined(CATCH_CPP17_OR_GREATER) +#endif // __has_include + +#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) +# define CATCH_CONFIG_COUNTER +#endif +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) +# define CATCH_CONFIG_WINDOWS_SEH +#endif +// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. +#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_CONFIG_POSIX_SIGNALS +#endif +// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. +#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) +# define CATCH_CONFIG_WCHAR +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) +# define CATCH_CONFIG_CPP11_TO_STRING +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) +# define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) +# define CATCH_CONFIG_CPP17_STRING_VIEW +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) +# define CATCH_CONFIG_CPP17_VARIANT +#endif + +#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) +# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) +# define CATCH_CONFIG_NEW_CAPTURE +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +# define CATCH_CONFIG_DISABLE_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) +# define CATCH_CONFIG_POLYFILL_ISNAN +#endif + +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS +#endif + +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +#define CATCH_TRY if ((true)) +#define CATCH_CATCH_ALL if ((false)) +#define CATCH_CATCH_ANON(type) if ((false)) +#else +#define CATCH_TRY try +#define CATCH_CATCH_ALL catch (...) +#define CATCH_CATCH_ANON(type) catch (type) +#endif + +#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) +#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#endif + +// end catch_compiler_capabilities.h +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif + +#include <iosfwd> +#include <string> +#include <cstdint> + +// We need a dummy global operator<< so we can bring it into Catch namespace later +struct Catch_global_namespace_dummy {}; +std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); + +namespace Catch { + + struct CaseSensitive { enum Choice { + Yes, + No + }; }; + + class NonCopyable { + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable& operator = ( NonCopyable const& ) = delete; + NonCopyable& operator = ( NonCopyable && ) = delete; + + protected: + NonCopyable(); + virtual ~NonCopyable(); + }; + + struct SourceLineInfo { + + SourceLineInfo() = delete; + SourceLineInfo( char const* _file, std::size_t _line ) noexcept + : file( _file ), + line( _line ) + {} + + SourceLineInfo( SourceLineInfo const& other ) = default; + SourceLineInfo( SourceLineInfo && ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo& operator = ( SourceLineInfo && ) = default; + + bool empty() const noexcept; + bool operator == ( SourceLineInfo const& other ) const noexcept; + bool operator < ( SourceLineInfo const& other ) const noexcept; + + char const* file; + std::size_t line; + }; + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); + + // Bring in operator<< from global namespace into Catch namespace + // This is necessary because the overload of operator<< above makes + // lookup stop at namespace Catch + using ::operator<<; + + // Use this in variadic streaming macros to allow + // >> +StreamEndStop + // as well as + // >> stuff +StreamEndStop + struct StreamEndStop { + std::string operator+() const; + }; + template<typename T> + T const& operator + ( T const& value, StreamEndStop ) { + return value; + } +} + +#define CATCH_INTERNAL_LINEINFO \ + ::Catch::SourceLineInfo( __FILE__, static_cast<std::size_t>( __LINE__ ) ) + +// end catch_common.h +namespace Catch { + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + +// end catch_tag_alias_autoregistrar.h +// start catch_test_registry.h + +// start catch_interfaces_testcase.h + +#include <vector> +#include <memory> + +namespace Catch { + + class TestSpec; + + struct ITestInvoker { + virtual void invoke () const = 0; + virtual ~ITestInvoker(); + }; + + using ITestCasePtr = std::shared_ptr<ITestInvoker>; + + class TestCase; + struct IConfig; + + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector<TestCase> const& getAllTests() const = 0; + virtual std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const = 0; + }; + + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ); + +} + +// end catch_interfaces_testcase.h +// start catch_stringref.h + +#include <cstddef> +#include <string> +#include <iosfwd> + +namespace Catch { + + class StringData; + + /// A non-owning string class (similar to the forthcoming std::string_view) + /// Note that, because a StringRef may be a substring of another string, + /// it may not be null terminated. c_str() must return a null terminated + /// string, however, and so the StringRef will internally take ownership + /// (taking a copy), if necessary. In theory this ownership is not externally + /// visible - but it does mean (substring) StringRefs should not be shared between + /// threads. + class StringRef { + public: + using size_type = std::size_t; + + private: + friend struct StringRefTestAccess; + + char const* m_start; + size_type m_size; + + char* m_data = nullptr; + + void takeOwnership(); + + static constexpr char const* const s_empty = ""; + + public: // construction/ assignment + StringRef() noexcept + : StringRef( s_empty, 0 ) + {} + + StringRef( StringRef const& other ) noexcept + : m_start( other.m_start ), + m_size( other.m_size ) + {} + + StringRef( StringRef&& other ) noexcept + : m_start( other.m_start ), + m_size( other.m_size ), + m_data( other.m_data ) + { + other.m_data = nullptr; + } + + StringRef( char const* rawChars ) noexcept; + + StringRef( char const* rawChars, size_type size ) noexcept + : m_start( rawChars ), + m_size( size ) + {} + + StringRef( std::string const& stdString ) noexcept + : m_start( stdString.c_str() ), + m_size( stdString.size() ) + {} + + ~StringRef() noexcept { + delete[] m_data; + } + + auto operator = ( StringRef const &other ) noexcept -> StringRef& { + delete[] m_data; + m_data = nullptr; + m_start = other.m_start; + m_size = other.m_size; + return *this; + } + + operator std::string() const; + + void swap( StringRef& other ) noexcept; + + public: // operators + auto operator == ( StringRef const& other ) const noexcept -> bool; + auto operator != ( StringRef const& other ) const noexcept -> bool; + + auto operator[] ( size_type index ) const noexcept -> char; + + public: // named queries + auto empty() const noexcept -> bool { + return m_size == 0; + } + auto size() const noexcept -> size_type { + return m_size; + } + + auto numberOfCharacters() const noexcept -> size_type; + auto c_str() const -> char const*; + + public: // substrings and searches + auto substr( size_type start, size_type size ) const noexcept -> StringRef; + + // Returns the current start pointer. + // Note that the pointer can change when if the StringRef is a substring + auto currentData() const noexcept -> char const*; + + private: // ownership queries - may not be consistent between calls + auto isOwned() const noexcept -> bool; + auto isSubstring() const noexcept -> bool; + }; + + auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string; + auto operator + ( StringRef const& lhs, char const* rhs ) -> std::string; + auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string; + + auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; + auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; + + inline auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { + return StringRef( rawChars, size ); + } + +} // namespace Catch + +inline auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { + return Catch::StringRef( rawChars, size ); +} + +// end catch_stringref.h +// start catch_type_traits.hpp + + +namespace Catch{ + +#ifdef CATCH_CPP17_OR_GREATER + template <typename...> + inline constexpr auto is_unique = std::true_type{}; + + template <typename T, typename... Rest> + inline constexpr auto is_unique<T, Rest...> = std::bool_constant< + (!std::is_same_v<T, Rest> && ...) && is_unique<Rest...> + >{}; +#else + +template <typename...> +struct is_unique : std::true_type{}; + +template <typename T0, typename T1, typename... Rest> +struct is_unique<T0, T1, Rest...> : std::integral_constant +<bool, + !std::is_same<T0, T1>::value + && is_unique<T0, Rest...>::value + && is_unique<T1, Rest...>::value +>{}; + +#endif +} + +// end catch_type_traits.hpp +// start catch_preprocessor.hpp + + +#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ +#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) + +#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ +// MSVC needs more evaluations +#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) +#else +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) +#endif + +#define CATCH_REC_END(...) +#define CATCH_REC_OUT + +#define CATCH_EMPTY() +#define CATCH_DEFER(id) id CATCH_EMPTY() + +#define CATCH_REC_GET_END2() 0, CATCH_REC_END +#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 +#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 +#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT +#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) +#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) + +#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) + +#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) + +// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, +// and passes userdata as the first parameter to each invocation, +// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) +#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) +#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ +#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ +#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF + +#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) + +#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME2(Name, ...) INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME3(Name, __VA_ARGS__) +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME3(Name,...) Name " - " #__VA_ARGS__ +#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME(Name,...) INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME2(Name, INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) +#else +// MSVC is adding extra space and needs more calls to properly remove () +#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME3(Name,...) Name " -" #__VA_ARGS__ +#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME1(Name, ...) INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME2(Name, __VA_ARGS__) +#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME(Name, ...) INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME1(Name, INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) +#endif + +// end catch_preprocessor.hpp +namespace Catch { + +template<typename C> +class TestInvokerAsMethod : public ITestInvoker { + void (C::*m_testAsMethod)(); +public: + TestInvokerAsMethod( void (C::*testAsMethod)() ) noexcept : m_testAsMethod( testAsMethod ) {} + + void invoke() const override { + C obj; + (obj.*m_testAsMethod)(); + } +}; + +auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker*; + +template<typename C> +auto makeTestInvoker( void (C::*testAsMethod)() ) noexcept -> ITestInvoker* { + return new(std::nothrow) TestInvokerAsMethod<C>( testAsMethod ); +} + +struct NameAndTags { + NameAndTags( StringRef const& name_ = StringRef(), StringRef const& tags_ = StringRef() ) noexcept; + StringRef name; + StringRef tags; +}; + +struct AutoReg : NonCopyable { + AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept; + ~AutoReg(); +}; + +} // end namespace Catch + +#if defined(CATCH_CONFIG_DISABLE) + #define INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( TestName, ... ) \ + static void TestName() + #define INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION( TestName, ClassName, ... ) \ + namespace{ \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ + void test(); \ + }; \ + } \ + void TestName::test() + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION( TestName, ... ) \ + template<typename TestType> \ + static void TestName() + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION( TestName, ClassName, ... ) \ + namespace{ \ + template<typename TestType> \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName <TestType>) { \ + void test(); \ + }; \ + } \ + template<typename TestType> \ + void TestName::test() +#endif + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ + static void TestName(); \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &TestName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + static void TestName() + #define INTERNAL_CATCH_TESTCASE( ... ) \ + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &QualifiedMethod ), CATCH_INTERNAL_LINEINFO, "&" #QualifiedMethod, Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ + void test(); \ + }; \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ + } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + void TestName::test() + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ + INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( Function ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_2(TestName, TestFunc, Name, Tags, ... )\ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + template<typename TestType> \ + static void TestFunc();\ + namespace {\ + template<typename...Types> \ + struct TestName{\ + template<typename...Ts> \ + TestName(Ts...names){\ + CATCH_INTERNAL_CHECK_UNIQUE_TYPES(CATCH_REC_LIST(INTERNAL_CATCH_REMOVE_PARENS, __VA_ARGS__)) \ + using expander = int[];\ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFunc<Types> ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ names, Tags } ), 0)... };/* NOLINT */ \ + }\ + };\ + INTERNAL_CATCH_TEMPLATE_REGISTRY_INITIATE(TestName, Name, __VA_ARGS__) \ + }\ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + template<typename TestType> \ + static void TestFunc() + +#if defined(CATCH_CPP17_OR_GREATER) +#define CATCH_INTERNAL_CHECK_UNIQUE_TYPES(...) static_assert(Catch::is_unique<__VA_ARGS__>,"Duplicate type detected in declaration of template test case"); +#else +#define CATCH_INTERNAL_CHECK_UNIQUE_TYPES(...) static_assert(Catch::is_unique<__VA_ARGS__>::value,"Duplicate type detected in declaration of template test case"); +#endif + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, __VA_ARGS__ ) +#else + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, __VA_ARGS__ ) ) +#endif + + #define INTERNAL_CATCH_TEMPLATE_REGISTRY_INITIATE(TestName, Name, ...)\ + static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ + TestName<CATCH_REC_LIST(INTERNAL_CATCH_REMOVE_PARENS, __VA_ARGS__)>(CATCH_REC_LIST_UD(INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME,Name, __VA_ARGS__));\ + return 0;\ + }(); + + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( TestNameClass, TestName, ClassName, Name, Tags, ... ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ \ + template<typename TestType> \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName <TestType>) { \ + void test();\ + };\ + template<typename...Types> \ + struct TestNameClass{\ + template<typename...Ts> \ + TestNameClass(Ts...names){\ + CATCH_INTERNAL_CHECK_UNIQUE_TYPES(CATCH_REC_LIST(INTERNAL_CATCH_REMOVE_PARENS, __VA_ARGS__)) \ + using expander = int[];\ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ names, Tags } ), 0)... };/* NOLINT */ \ + }\ + };\ + INTERNAL_CATCH_TEMPLATE_REGISTRY_INITIATE(TestNameClass, Name, __VA_ARGS__)\ + }\ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS\ + template<typename TestType> \ + void TestName<TestType>::test() + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( ClassName, Name, Tags,... ) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, __VA_ARGS__ ) +#else + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( ClassName, Name, Tags,... ) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, __VA_ARGS__ ) ) +#endif + +// end catch_test_registry.h +// start catch_capture.hpp + +// start catch_assertionhandler.h + +// start catch_assertioninfo.h + +// start catch_result_type.h + +namespace Catch { + + // ResultWas::OfType enum + struct ResultWas { enum OfType { + Unknown = -1, + Ok = 0, + Info = 1, + Warning = 2, + + FailureBit = 0x10, + + ExpressionFailed = FailureBit | 1, + ExplicitFailure = FailureBit | 2, + + Exception = 0x100 | FailureBit, + + ThrewException = Exception | 1, + DidntThrowException = Exception | 2, + + FatalErrorCondition = 0x200 | FailureBit + + }; }; + + bool isOk( ResultWas::OfType resultType ); + bool isJustInfo( int flags ); + + // ResultDisposition::Flags enum + struct ResultDisposition { enum Flags { + Normal = 0x01, + + ContinueOnFailure = 0x02, // Failures fail test, but execution continues + FalseTest = 0x04, // Prefix expression with ! + SuppressFail = 0x08 // Failures are reported but do not fail the test + }; }; + + ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ); + + bool shouldContinueOnFailure( int flags ); + inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } + bool shouldSuppressFailure( int flags ); + +} // end namespace Catch + +// end catch_result_type.h +namespace Catch { + + struct AssertionInfo + { + StringRef macroName; + SourceLineInfo lineInfo; + StringRef capturedExpression; + ResultDisposition::Flags resultDisposition; + + // We want to delete this constructor but a compiler bug in 4.8 means + // the struct is then treated as non-aggregate + //AssertionInfo() = delete; + }; + +} // end namespace Catch + +// end catch_assertioninfo.h +// start catch_decomposer.h + +// start catch_tostring.h + +#include <vector> +#include <cstddef> +#include <type_traits> +#include <string> +// start catch_stream.h + +#include <iosfwd> +#include <cstddef> +#include <ostream> + +namespace Catch { + + std::ostream& cout(); + std::ostream& cerr(); + std::ostream& clog(); + + class StringRef; + + struct IStream { + virtual ~IStream(); + virtual std::ostream& stream() const = 0; + }; + + auto makeStream( StringRef const &filename ) -> IStream const*; + + class ReusableStringStream { + std::size_t m_index; + std::ostream* m_oss; + public: + ReusableStringStream(); + ~ReusableStringStream(); + + auto str() const -> std::string; + + template<typename T> + auto operator << ( T const& value ) -> ReusableStringStream& { + *m_oss << value; + return *this; + } + auto get() -> std::ostream& { return *m_oss; } + }; +} + +// end catch_stream.h + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW +#include <string_view> +#endif + +#ifdef __OBJC__ +// start catch_objc_arc.hpp + +#import <Foundation/Foundation.h> + +#ifdef __has_feature +#define CATCH_ARC_ENABLED __has_feature(objc_arc) +#else +#define CATCH_ARC_ENABLED 0 +#endif + +void arcSafeRelease( NSObject* obj ); +id performOptionalSelector( id obj, SEL sel ); + +#if !CATCH_ARC_ENABLED +inline void arcSafeRelease( NSObject* obj ) { + [obj release]; +} +inline id performOptionalSelector( id obj, SEL sel ) { + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; + return nil; +} +#define CATCH_UNSAFE_UNRETAINED +#define CATCH_ARC_STRONG +#else +inline void arcSafeRelease( NSObject* ){} +inline id performOptionalSelector( id obj, SEL sel ) { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" +#endif + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + return nil; +} +#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained +#define CATCH_ARC_STRONG __strong +#endif + +// end catch_objc_arc.hpp +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4180) // We attempt to stream a function (address) by const&, which MSVC complains about but is harmless +#endif + +namespace Catch { + namespace Detail { + + extern const std::string unprintableString; + + std::string rawMemoryToString( const void *object, std::size_t size ); + + template<typename T> + std::string rawMemoryToString( const T& object ) { + return rawMemoryToString( &object, sizeof(object) ); + } + + template<typename T> + class IsStreamInsertable { + template<typename SS, typename TT> + static auto test(int) + -> decltype(std::declval<SS&>() << std::declval<TT>(), std::true_type()); + + template<typename, typename> + static auto test(...)->std::false_type; + + public: + static const bool value = decltype(test<std::ostream, const T&>(0))::value; + }; + + template<typename E> + std::string convertUnknownEnumToString( E e ); + + template<typename T> + typename std::enable_if< + !std::is_enum<T>::value && !std::is_base_of<std::exception, T>::value, + std::string>::type convertUnstreamable( T const& ) { + return Detail::unprintableString; + } + template<typename T> + typename std::enable_if< + !std::is_enum<T>::value && std::is_base_of<std::exception, T>::value, + std::string>::type convertUnstreamable(T const& ex) { + return ex.what(); + } + + template<typename T> + typename std::enable_if< + std::is_enum<T>::value + , std::string>::type convertUnstreamable( T const& value ) { + return convertUnknownEnumToString( value ); + } + +#if defined(_MANAGED) + //! Convert a CLR string to a utf8 std::string + template<typename T> + std::string clrReferenceToString( T^ ref ) { + if (ref == nullptr) + return std::string("null"); + auto bytes = System::Text::Encoding::UTF8->GetBytes(ref->ToString()); + cli::pin_ptr<System::Byte> p = &bytes[0]; + return std::string(reinterpret_cast<char const *>(p), bytes->Length); + } +#endif + + } // namespace Detail + + // If we decide for C++14, change these to enable_if_ts + template <typename T, typename = void> + struct StringMaker { + template <typename Fake = T> + static + typename std::enable_if<::Catch::Detail::IsStreamInsertable<Fake>::value, std::string>::type + convert(const Fake& value) { + ReusableStringStream rss; + // NB: call using the function-like syntax to avoid ambiguity with + // user-defined templated operator<< under clang. + rss.operator<<(value); + return rss.str(); + } + + template <typename Fake = T> + static + typename std::enable_if<!::Catch::Detail::IsStreamInsertable<Fake>::value, std::string>::type + convert( const Fake& value ) { +#if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER) + return Detail::convertUnstreamable(value); +#else + return CATCH_CONFIG_FALLBACK_STRINGIFIER(value); +#endif + } + }; + + namespace Detail { + + // This function dispatches all stringification requests inside of Catch. + // Should be preferably called fully qualified, like ::Catch::Detail::stringify + template <typename T> + std::string stringify(const T& e) { + return ::Catch::StringMaker<typename std::remove_cv<typename std::remove_reference<T>::type>::type>::convert(e); + } + + template<typename E> + std::string convertUnknownEnumToString( E e ) { + return ::Catch::Detail::stringify(static_cast<typename std::underlying_type<E>::type>(e)); + } + +#if defined(_MANAGED) + template <typename T> + std::string stringify( T^ e ) { + return ::Catch::StringMaker<T^>::convert(e); + } +#endif + + } // namespace Detail + + // Some predefined specializations + + template<> + struct StringMaker<std::string> { + static std::string convert(const std::string& str); + }; + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW + template<> + struct StringMaker<std::string_view> { + static std::string convert(std::string_view str); + }; +#endif + + template<> + struct StringMaker<char const *> { + static std::string convert(char const * str); + }; + template<> + struct StringMaker<char *> { + static std::string convert(char * str); + }; + +#ifdef CATCH_CONFIG_WCHAR + template<> + struct StringMaker<std::wstring> { + static std::string convert(const std::wstring& wstr); + }; + +# ifdef CATCH_CONFIG_CPP17_STRING_VIEW + template<> + struct StringMaker<std::wstring_view> { + static std::string convert(std::wstring_view str); + }; +# endif + + template<> + struct StringMaker<wchar_t const *> { + static std::string convert(wchar_t const * str); + }; + template<> + struct StringMaker<wchar_t *> { + static std::string convert(wchar_t * str); + }; +#endif + + // TBD: Should we use `strnlen` to ensure that we don't go out of the buffer, + // while keeping string semantics? + template<int SZ> + struct StringMaker<char[SZ]> { + static std::string convert(char const* str) { + return ::Catch::Detail::stringify(std::string{ str }); + } + }; + template<int SZ> + struct StringMaker<signed char[SZ]> { + static std::string convert(signed char const* str) { + return ::Catch::Detail::stringify(std::string{ reinterpret_cast<char const *>(str) }); + } + }; + template<int SZ> + struct StringMaker<unsigned char[SZ]> { + static std::string convert(unsigned char const* str) { + return ::Catch::Detail::stringify(std::string{ reinterpret_cast<char const *>(str) }); + } + }; + + template<> + struct StringMaker<int> { + static std::string convert(int value); + }; + template<> + struct StringMaker<long> { + static std::string convert(long value); + }; + template<> + struct StringMaker<long long> { + static std::string convert(long long value); + }; + template<> + struct StringMaker<unsigned int> { + static std::string convert(unsigned int value); + }; + template<> + struct StringMaker<unsigned long> { + static std::string convert(unsigned long value); + }; + template<> + struct StringMaker<unsigned long long> { + static std::string convert(unsigned long long value); + }; + + template<> + struct StringMaker<bool> { + static std::string convert(bool b); + }; + + template<> + struct StringMaker<char> { + static std::string convert(char c); + }; + template<> + struct StringMaker<signed char> { + static std::string convert(signed char c); + }; + template<> + struct StringMaker<unsigned char> { + static std::string convert(unsigned char c); + }; + + template<> + struct StringMaker<std::nullptr_t> { + static std::string convert(std::nullptr_t); + }; + + template<> + struct StringMaker<float> { + static std::string convert(float value); + }; + template<> + struct StringMaker<double> { + static std::string convert(double value); + }; + + template <typename T> + struct StringMaker<T*> { + template <typename U> + static std::string convert(U* p) { + if (p) { + return ::Catch::Detail::rawMemoryToString(p); + } else { + return "nullptr"; + } + } + }; + + template <typename R, typename C> + struct StringMaker<R C::*> { + static std::string convert(R C::* p) { + if (p) { + return ::Catch::Detail::rawMemoryToString(p); + } else { + return "nullptr"; + } + } + }; + +#if defined(_MANAGED) + template <typename T> + struct StringMaker<T^> { + static std::string convert( T^ ref ) { + return ::Catch::Detail::clrReferenceToString(ref); + } + }; +#endif + + namespace Detail { + template<typename InputIterator> + std::string rangeToString(InputIterator first, InputIterator last) { + ReusableStringStream rss; + rss << "{ "; + if (first != last) { + rss << ::Catch::Detail::stringify(*first); + for (++first; first != last; ++first) + rss << ", " << ::Catch::Detail::stringify(*first); + } + rss << " }"; + return rss.str(); + } + } + +#ifdef __OBJC__ + template<> + struct StringMaker<NSString*> { + static std::string convert(NSString * nsstring) { + if (!nsstring) + return "nil"; + return std::string("@") + [nsstring UTF8String]; + } + }; + template<> + struct StringMaker<NSObject*> { + static std::string convert(NSObject* nsObject) { + return ::Catch::Detail::stringify([nsObject description]); + } + + }; + namespace Detail { + inline std::string stringify( NSString* nsstring ) { + return StringMaker<NSString*>::convert( nsstring ); + } + + } // namespace Detail +#endif // __OBJC__ + +} // namespace Catch + +////////////////////////////////////////////////////// +// Separate std-lib types stringification, so it can be selectively enabled +// This means that we do not bring in + +#if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS) +# define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER +# define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER +# define CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +#endif + +// Separate std::pair specialization +#if defined(CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER) +#include <utility> +namespace Catch { + template<typename T1, typename T2> + struct StringMaker<std::pair<T1, T2> > { + static std::string convert(const std::pair<T1, T2>& pair) { + ReusableStringStream rss; + rss << "{ " + << ::Catch::Detail::stringify(pair.first) + << ", " + << ::Catch::Detail::stringify(pair.second) + << " }"; + return rss.str(); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER + +// Separate std::tuple specialization +#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER) +#include <tuple> +namespace Catch { + namespace Detail { + template< + typename Tuple, + std::size_t N = 0, + bool = (N < std::tuple_size<Tuple>::value) + > + struct TupleElementPrinter { + static void print(const Tuple& tuple, std::ostream& os) { + os << (N ? ", " : " ") + << ::Catch::Detail::stringify(std::get<N>(tuple)); + TupleElementPrinter<Tuple, N + 1>::print(tuple, os); + } + }; + + template< + typename Tuple, + std::size_t N + > + struct TupleElementPrinter<Tuple, N, false> { + static void print(const Tuple&, std::ostream&) {} + }; + + } + + template<typename ...Types> + struct StringMaker<std::tuple<Types...>> { + static std::string convert(const std::tuple<Types...>& tuple) { + ReusableStringStream rss; + rss << '{'; + Detail::TupleElementPrinter<std::tuple<Types...>>::print(tuple, rss.get()); + rss << " }"; + return rss.str(); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER + +#if defined(CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_VARIANT) +#include <variant> +namespace Catch { + template<> + struct StringMaker<std::monostate> { + static std::string convert(const std::monostate&) { + return "{ }"; + } + }; + + template<typename... Elements> + struct StringMaker<std::variant<Elements...>> { + static std::string convert(const std::variant<Elements...>& variant) { + if (variant.valueless_by_exception()) { + return "{valueless variant}"; + } else { + return std::visit( + [](const auto& value) { + return ::Catch::Detail::stringify(value); + }, + variant + ); + } + } + }; +} +#endif // CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER + +namespace Catch { + struct not_this_one {}; // Tag type for detecting which begin/ end are being selected + + // Import begin/ end from std here so they are considered alongside the fallback (...) overloads in this namespace + using std::begin; + using std::end; + + not_this_one begin( ... ); + not_this_one end( ... ); + + template <typename T> + struct is_range { + static const bool value = + !std::is_same<decltype(begin(std::declval<T>())), not_this_one>::value && + !std::is_same<decltype(end(std::declval<T>())), not_this_one>::value; + }; + +#if defined(_MANAGED) // Managed types are never ranges + template <typename T> + struct is_range<T^> { + static const bool value = false; + }; +#endif + + template<typename Range> + std::string rangeToString( Range const& range ) { + return ::Catch::Detail::rangeToString( begin( range ), end( range ) ); + } + + // Handle vector<bool> specially + template<typename Allocator> + std::string rangeToString( std::vector<bool, Allocator> const& v ) { + ReusableStringStream rss; + rss << "{ "; + bool first = true; + for( bool b : v ) { + if( first ) + first = false; + else + rss << ", "; + rss << ::Catch::Detail::stringify( b ); + } + rss << " }"; + return rss.str(); + } + + template<typename R> + struct StringMaker<R, typename std::enable_if<is_range<R>::value && !::Catch::Detail::IsStreamInsertable<R>::value>::type> { + static std::string convert( R const& range ) { + return rangeToString( range ); + } + }; + + template <typename T, int SZ> + struct StringMaker<T[SZ]> { + static std::string convert(T const(&arr)[SZ]) { + return rangeToString(arr); + } + }; + +} // namespace Catch + +// Separate std::chrono::duration specialization +#if defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +#include <ctime> +#include <ratio> +#include <chrono> + +namespace Catch { + +template <class Ratio> +struct ratio_string { + static std::string symbol(); +}; + +template <class Ratio> +std::string ratio_string<Ratio>::symbol() { + Catch::ReusableStringStream rss; + rss << '[' << Ratio::num << '/' + << Ratio::den << ']'; + return rss.str(); +} +template <> +struct ratio_string<std::atto> { + static std::string symbol(); +}; +template <> +struct ratio_string<std::femto> { + static std::string symbol(); +}; +template <> +struct ratio_string<std::pico> { + static std::string symbol(); +}; +template <> +struct ratio_string<std::nano> { + static std::string symbol(); +}; +template <> +struct ratio_string<std::micro> { + static std::string symbol(); +}; +template <> +struct ratio_string<std::milli> { + static std::string symbol(); +}; + + //////////// + // std::chrono::duration specializations + template<typename Value, typename Ratio> + struct StringMaker<std::chrono::duration<Value, Ratio>> { + static std::string convert(std::chrono::duration<Value, Ratio> const& duration) { + ReusableStringStream rss; + rss << duration.count() << ' ' << ratio_string<Ratio>::symbol() << 's'; + return rss.str(); + } + }; + template<typename Value> + struct StringMaker<std::chrono::duration<Value, std::ratio<1>>> { + static std::string convert(std::chrono::duration<Value, std::ratio<1>> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " s"; + return rss.str(); + } + }; + template<typename Value> + struct StringMaker<std::chrono::duration<Value, std::ratio<60>>> { + static std::string convert(std::chrono::duration<Value, std::ratio<60>> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " m"; + return rss.str(); + } + }; + template<typename Value> + struct StringMaker<std::chrono::duration<Value, std::ratio<3600>>> { + static std::string convert(std::chrono::duration<Value, std::ratio<3600>> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " h"; + return rss.str(); + } + }; + + //////////// + // std::chrono::time_point specialization + // Generic time_point cannot be specialized, only std::chrono::time_point<system_clock> + template<typename Clock, typename Duration> + struct StringMaker<std::chrono::time_point<Clock, Duration>> { + static std::string convert(std::chrono::time_point<Clock, Duration> const& time_point) { + return ::Catch::Detail::stringify(time_point.time_since_epoch()) + " since epoch"; + } + }; + // std::chrono::time_point<system_clock> specialization + template<typename Duration> + struct StringMaker<std::chrono::time_point<std::chrono::system_clock, Duration>> { + static std::string convert(std::chrono::time_point<std::chrono::system_clock, Duration> const& time_point) { + auto converted = std::chrono::system_clock::to_time_t(time_point); + +#ifdef _MSC_VER + std::tm timeInfo = {}; + gmtime_s(&timeInfo, &converted); +#else + std::tm* timeInfo = std::gmtime(&converted); +#endif + + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + char timeStamp[timeStampSize]; + const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; + +#ifdef _MSC_VER + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); +#else + std::strftime(timeStamp, timeStampSize, fmt, timeInfo); +#endif + return std::string(timeStamp); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// end catch_tostring.h +#include <iosfwd> + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4389) // '==' : signed/unsigned mismatch +#pragma warning(disable:4018) // more "signed/unsigned mismatch" +#pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform) +#pragma warning(disable:4180) // qualifier applied to function type has no meaning +#endif + +namespace Catch { + + struct ITransientExpression { + auto isBinaryExpression() const -> bool { return m_isBinaryExpression; } + auto getResult() const -> bool { return m_result; } + virtual void streamReconstructedExpression( std::ostream &os ) const = 0; + + ITransientExpression( bool isBinaryExpression, bool result ) + : m_isBinaryExpression( isBinaryExpression ), + m_result( result ) + {} + + // We don't actually need a virtual destructor, but many static analysers + // complain if it's not here :-( + virtual ~ITransientExpression(); + + bool m_isBinaryExpression; + bool m_result; + + }; + + void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ); + + template<typename LhsT, typename RhsT> + class BinaryExpr : public ITransientExpression { + LhsT m_lhs; + StringRef m_op; + RhsT m_rhs; + + void streamReconstructedExpression( std::ostream &os ) const override { + formatReconstructedExpression + ( os, Catch::Detail::stringify( m_lhs ), m_op, Catch::Detail::stringify( m_rhs ) ); + } + + public: + BinaryExpr( bool comparisonResult, LhsT lhs, StringRef op, RhsT rhs ) + : ITransientExpression{ true, comparisonResult }, + m_lhs( lhs ), + m_op( op ), + m_rhs( rhs ) + {} + }; + + template<typename LhsT> + class UnaryExpr : public ITransientExpression { + LhsT m_lhs; + + void streamReconstructedExpression( std::ostream &os ) const override { + os << Catch::Detail::stringify( m_lhs ); + } + + public: + explicit UnaryExpr( LhsT lhs ) + : ITransientExpression{ false, lhs ? true : false }, + m_lhs( lhs ) + {} + }; + + // Specialised comparison functions to handle equality comparisons between ints and pointers (NULL deduces as an int) + template<typename LhsT, typename RhsT> + auto compareEqual( LhsT const& lhs, RhsT const& rhs ) -> bool { return static_cast<bool>(lhs == rhs); } + template<typename T> + auto compareEqual( T* const& lhs, int rhs ) -> bool { return lhs == reinterpret_cast<void const*>( rhs ); } + template<typename T> + auto compareEqual( T* const& lhs, long rhs ) -> bool { return lhs == reinterpret_cast<void const*>( rhs ); } + template<typename T> + auto compareEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) == rhs; } + template<typename T> + auto compareEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) == rhs; } + + template<typename LhsT, typename RhsT> + auto compareNotEqual( LhsT const& lhs, RhsT&& rhs ) -> bool { return static_cast<bool>(lhs != rhs); } + template<typename T> + auto compareNotEqual( T* const& lhs, int rhs ) -> bool { return lhs != reinterpret_cast<void const*>( rhs ); } + template<typename T> + auto compareNotEqual( T* const& lhs, long rhs ) -> bool { return lhs != reinterpret_cast<void const*>( rhs ); } + template<typename T> + auto compareNotEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) != rhs; } + template<typename T> + auto compareNotEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) != rhs; } + + template<typename LhsT> + class ExprLhs { + LhsT m_lhs; + public: + explicit ExprLhs( LhsT lhs ) : m_lhs( lhs ) {} + + template<typename RhsT> + auto operator == ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { + return { compareEqual( m_lhs, rhs ), m_lhs, "==", rhs }; + } + auto operator == ( bool rhs ) -> BinaryExpr<LhsT, bool> const { + return { m_lhs == rhs, m_lhs, "==", rhs }; + } + + template<typename RhsT> + auto operator != ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { + return { compareNotEqual( m_lhs, rhs ), m_lhs, "!=", rhs }; + } + auto operator != ( bool rhs ) -> BinaryExpr<LhsT, bool> const { + return { m_lhs != rhs, m_lhs, "!=", rhs }; + } + + template<typename RhsT> + auto operator > ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { + return { static_cast<bool>(m_lhs > rhs), m_lhs, ">", rhs }; + } + template<typename RhsT> + auto operator < ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { + return { static_cast<bool>(m_lhs < rhs), m_lhs, "<", rhs }; + } + template<typename RhsT> + auto operator >= ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { + return { static_cast<bool>(m_lhs >= rhs), m_lhs, ">=", rhs }; + } + template<typename RhsT> + auto operator <= ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { + return { static_cast<bool>(m_lhs <= rhs), m_lhs, "<=", rhs }; + } + + auto makeUnaryExpr() const -> UnaryExpr<LhsT> { + return UnaryExpr<LhsT>{ m_lhs }; + } + }; + + void handleExpression( ITransientExpression const& expr ); + + template<typename T> + void handleExpression( ExprLhs<T> const& expr ) { + handleExpression( expr.makeUnaryExpr() ); + } + + struct Decomposer { + template<typename T> + auto operator <= ( T const& lhs ) -> ExprLhs<T const&> { + return ExprLhs<T const&>{ lhs }; + } + + auto operator <=( bool value ) -> ExprLhs<bool> { + return ExprLhs<bool>{ value }; + } + }; + +} // end namespace Catch + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// end catch_decomposer.h +// start catch_interfaces_capture.h + +#include <string> + +namespace Catch { + + class AssertionResult; + struct AssertionInfo; + struct SectionInfo; + struct SectionEndInfo; + struct MessageInfo; + struct Counts; + struct BenchmarkInfo; + struct BenchmarkStats; + struct AssertionReaction; + struct SourceLineInfo; + + struct ITransientExpression; + struct IGeneratorTracker; + + struct IResultCapture { + + virtual ~IResultCapture(); + + virtual bool sectionStarted( SectionInfo const& sectionInfo, + Counts& assertions ) = 0; + virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0; + virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0; + + virtual auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& = 0; + + virtual void benchmarkStarting( BenchmarkInfo const& info ) = 0; + virtual void benchmarkEnded( BenchmarkStats const& stats ) = 0; + + virtual void pushScopedMessage( MessageInfo const& message ) = 0; + virtual void popScopedMessage( MessageInfo const& message ) = 0; + + virtual void handleFatalErrorCondition( StringRef message ) = 0; + + virtual void handleExpr + ( AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction ) = 0; + virtual void handleMessage + ( AssertionInfo const& info, + ResultWas::OfType resultType, + StringRef const& message, + AssertionReaction& reaction ) = 0; + virtual void handleUnexpectedExceptionNotThrown + ( AssertionInfo const& info, + AssertionReaction& reaction ) = 0; + virtual void handleUnexpectedInflightException + ( AssertionInfo const& info, + std::string const& message, + AssertionReaction& reaction ) = 0; + virtual void handleIncomplete + ( AssertionInfo const& info ) = 0; + virtual void handleNonExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction ) = 0; + + virtual bool lastAssertionPassed() = 0; + virtual void assertionPassed() = 0; + + // Deprecated, do not use: + virtual std::string getCurrentTestName() const = 0; + virtual const AssertionResult* getLastResult() const = 0; + virtual void exceptionEarlyReported() = 0; + }; + + IResultCapture& getResultCapture(); +} + +// end catch_interfaces_capture.h +namespace Catch { + + struct TestFailureException{}; + struct AssertionResultData; + struct IResultCapture; + class RunContext; + + class LazyExpression { + friend class AssertionHandler; + friend struct AssertionStats; + friend class RunContext; + + ITransientExpression const* m_transientExpression = nullptr; + bool m_isNegated; + public: + LazyExpression( bool isNegated ); + LazyExpression( LazyExpression const& other ); + LazyExpression& operator = ( LazyExpression const& ) = delete; + + explicit operator bool() const; + + friend auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream&; + }; + + struct AssertionReaction { + bool shouldDebugBreak = false; + bool shouldThrow = false; + }; + + class AssertionHandler { + AssertionInfo m_assertionInfo; + AssertionReaction m_reaction; + bool m_completed = false; + IResultCapture& m_resultCapture; + + public: + AssertionHandler + ( StringRef const& macroName, + SourceLineInfo const& lineInfo, + StringRef capturedExpression, + ResultDisposition::Flags resultDisposition ); + ~AssertionHandler() { + if ( !m_completed ) { + m_resultCapture.handleIncomplete( m_assertionInfo ); + } + } + + template<typename T> + void handleExpr( ExprLhs<T> const& expr ) { + handleExpr( expr.makeUnaryExpr() ); + } + void handleExpr( ITransientExpression const& expr ); + + void handleMessage(ResultWas::OfType resultType, StringRef const& message); + + void handleExceptionThrownAsExpected(); + void handleUnexpectedExceptionNotThrown(); + void handleExceptionNotThrownAsExpected(); + void handleThrowingCallSkipped(); + void handleUnexpectedInflightException(); + + void complete(); + void setCompleted(); + + // query + auto allowThrows() const -> bool; + }; + + void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef const& matcherString ); + +} // namespace Catch + +// end catch_assertionhandler.h +// start catch_message.h + +#include <string> +#include <vector> + +namespace Catch { + + struct MessageInfo { + MessageInfo( StringRef const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ); + + StringRef macroName; + std::string message; + SourceLineInfo lineInfo; + ResultWas::OfType type; + unsigned int sequence; + + bool operator == ( MessageInfo const& other ) const; + bool operator < ( MessageInfo const& other ) const; + private: + static unsigned int globalCount; + }; + + struct MessageStream { + + template<typename T> + MessageStream& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + ReusableStringStream m_stream; + }; + + struct MessageBuilder : MessageStream { + MessageBuilder( StringRef const& macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ); + + template<typename T> + MessageBuilder& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + MessageInfo m_info; + }; + + class ScopedMessage { + public: + explicit ScopedMessage( MessageBuilder const& builder ); + ~ScopedMessage(); + + MessageInfo m_info; + }; + + class Capturer { + std::vector<MessageInfo> m_messages; + IResultCapture& m_resultCapture = getResultCapture(); + size_t m_captured = 0; + public: + Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ); + ~Capturer(); + + void captureValue( size_t index, std::string const& value ); + + template<typename T> + void captureValues( size_t index, T const& value ) { + captureValue( index, Catch::Detail::stringify( value ) ); + } + + template<typename T, typename... Ts> + void captureValues( size_t index, T const& value, Ts const&... values ) { + captureValue( index, Catch::Detail::stringify(value) ); + captureValues( index+1, values... ); + } + }; + +} // end namespace Catch + +// end catch_message.h +#if !defined(CATCH_CONFIG_DISABLE) + +#if !defined(CATCH_CONFIG_DISABLE_STRINGIFICATION) + #define CATCH_INTERNAL_STRINGIFY(...) #__VA_ARGS__ +#else + #define CATCH_INTERNAL_STRINGIFY(...) "Disabled by CATCH_CONFIG_DISABLE_STRINGIFICATION" +#endif + +#if defined(CATCH_CONFIG_FAST_COMPILE) || defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + +/////////////////////////////////////////////////////////////////////////////// +// Another way to speed-up compilation is to omit local try-catch for REQUIRE* +// macros. +#define INTERNAL_CATCH_TRY +#define INTERNAL_CATCH_CATCH( capturer ) + +#else // CATCH_CONFIG_FAST_COMPILE + +#define INTERNAL_CATCH_TRY try +#define INTERNAL_CATCH_CATCH( handler ) catch(...) { handler.handleUnexpectedInflightException(); } + +#endif + +#define INTERNAL_CATCH_REACT( handler ) handler.complete(); + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TEST( macroName, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ + INTERNAL_CATCH_TRY { \ + CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + catchAssertionHandler.handleExpr( Catch::Decomposer() <= __VA_ARGS__ ); \ + CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( (void)0, false && static_cast<bool>( !!(__VA_ARGS__) ) ) // the expression here is never evaluated at runtime but it forces the compiler to give it a look + // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_IF( macroName, resultDisposition, ... ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ + if( Catch::getResultCapture().lastAssertionPassed() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_ELSE( macroName, resultDisposition, ... ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ + if( !Catch::getResultCapture().lastAssertionPassed() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ + try { \ + static_cast<void>(__VA_ARGS__); \ + catchAssertionHandler.handleExceptionNotThrownAsExpected(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + static_cast<void>(__VA_ARGS__); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleExceptionThrownAsExpected(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr) ", " CATCH_INTERNAL_STRINGIFY(exceptionType), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + static_cast<void>(expr); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( exceptionType const& ) { \ + catchAssertionHandler.handleExceptionThrownAsExpected(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::StringRef(), resultDisposition ); \ + catchAssertionHandler.handleMessage( messageType, ( Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop() ).m_stream.str() ); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_CAPTURE( varName, macroName, ... ) \ + auto varName = Catch::Capturer( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info, #__VA_ARGS__ ); \ + varName.captureValues( 0, __VA_ARGS__ ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_INFO( macroName, log ) \ + Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage )( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ); + +/////////////////////////////////////////////////////////////////////////////// +// Although this is matcher-based, it can be used with just a string +#define INTERNAL_CATCH_THROWS_STR_MATCHES( macroName, resultDisposition, matcher, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + static_cast<void>(__VA_ARGS__); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( ... ) { \ + Catch::handleExceptionMatchExpr( catchAssertionHandler, matcher, #matcher##_catch_sr ); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +#endif // CATCH_CONFIG_DISABLE + +// end catch_capture.hpp +// start catch_section.h + +// start catch_section_info.h + +// start catch_totals.h + +#include <cstddef> + +namespace Catch { + + struct Counts { + Counts operator - ( Counts const& other ) const; + Counts& operator += ( Counts const& other ); + + std::size_t total() const; + bool allPassed() const; + bool allOk() const; + + std::size_t passed = 0; + std::size_t failed = 0; + std::size_t failedButOk = 0; + }; + + struct Totals { + + Totals operator - ( Totals const& other ) const; + Totals& operator += ( Totals const& other ); + + Totals delta( Totals const& prevTotals ) const; + + int error = 0; + Counts assertions; + Counts testCases; + }; +} + +// end catch_totals.h +#include <string> + +namespace Catch { + + struct SectionInfo { + SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name ); + + // Deprecated + SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& ) : SectionInfo( _lineInfo, _name ) {} + + std::string name; + std::string description; // !Deprecated: this will always be empty + SourceLineInfo lineInfo; + }; + + struct SectionEndInfo { + SectionInfo sectionInfo; + Counts prevAssertions; + double durationInSeconds; + }; + +} // end namespace Catch + +// end catch_section_info.h +// start catch_timer.h + +#include <cstdint> + +namespace Catch { + + auto getCurrentNanosecondsSinceEpoch() -> uint64_t; + auto getEstimatedClockResolution() -> uint64_t; + + class Timer { + uint64_t m_nanoseconds = 0; + public: + void start(); + auto getElapsedNanoseconds() const -> uint64_t; + auto getElapsedMicroseconds() const -> uint64_t; + auto getElapsedMilliseconds() const -> unsigned int; + auto getElapsedSeconds() const -> double; + }; + +} // namespace Catch + +// end catch_timer.h +#include <string> + +namespace Catch { + + class Section : NonCopyable { + public: + Section( SectionInfo const& info ); + ~Section(); + + // This indicates whether the section should be executed or not + explicit operator bool() const; + + private: + SectionInfo m_info; + + std::string m_name; + Counts m_assertions; + bool m_sectionIncluded; + Timer m_timer; + }; + +} // end namespace Catch + +#define INTERNAL_CATCH_SECTION( ... ) \ + CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) \ + CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS + +#define INTERNAL_CATCH_DYNAMIC_SECTION( ... ) \ + CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, (Catch::ReusableStringStream() << __VA_ARGS__).str() ) ) \ + CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS + +// end catch_section.h +// start catch_benchmark.h + +#include <cstdint> +#include <string> + +namespace Catch { + + class BenchmarkLooper { + + std::string m_name; + std::size_t m_count = 0; + std::size_t m_iterationsToRun = 1; + uint64_t m_resolution; + Timer m_timer; + + static auto getResolution() -> uint64_t; + public: + // Keep most of this inline as it's on the code path that is being timed + BenchmarkLooper( StringRef name ) + : m_name( name ), + m_resolution( getResolution() ) + { + reportStart(); + m_timer.start(); + } + + explicit operator bool() { + if( m_count < m_iterationsToRun ) + return true; + return needsMoreIterations(); + } + + void increment() { + ++m_count; + } + + void reportStart(); + auto needsMoreIterations() -> bool; + }; + +} // end namespace Catch + +#define BENCHMARK( name ) \ + for( Catch::BenchmarkLooper looper( name ); looper; looper.increment() ) + +// end catch_benchmark.h +// start catch_interfaces_exception.h + +// start catch_interfaces_registry_hub.h + +#include <string> +#include <memory> + +namespace Catch { + + class TestCase; + struct ITestCaseRegistry; + struct IExceptionTranslatorRegistry; + struct IExceptionTranslator; + struct IReporterRegistry; + struct IReporterFactory; + struct ITagAliasRegistry; + class StartupExceptionRegistry; + + using IReporterFactoryPtr = std::shared_ptr<IReporterFactory>; + + struct IRegistryHub { + virtual ~IRegistryHub(); + + virtual IReporterRegistry const& getReporterRegistry() const = 0; + virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; + virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0; + + virtual IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const = 0; + + virtual StartupExceptionRegistry const& getStartupExceptionRegistry() const = 0; + }; + + struct IMutableRegistryHub { + virtual ~IMutableRegistryHub(); + virtual void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) = 0; + virtual void registerListener( IReporterFactoryPtr const& factory ) = 0; + virtual void registerTest( TestCase const& testInfo ) = 0; + virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; + virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0; + virtual void registerStartupException() noexcept = 0; + }; + + IRegistryHub const& getRegistryHub(); + IMutableRegistryHub& getMutableRegistryHub(); + void cleanUp(); + std::string translateActiveException(); + +} + +// end catch_interfaces_registry_hub.h +#if defined(CATCH_CONFIG_DISABLE) + #define INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( translatorName, signature) \ + static std::string translatorName( signature ) +#endif + +#include <exception> +#include <string> +#include <vector> + +namespace Catch { + using exceptionTranslateFunction = std::string(*)(); + + struct IExceptionTranslator; + using ExceptionTranslators = std::vector<std::unique_ptr<IExceptionTranslator const>>; + + struct IExceptionTranslator { + virtual ~IExceptionTranslator(); + virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0; + }; + + struct IExceptionTranslatorRegistry { + virtual ~IExceptionTranslatorRegistry(); + + virtual std::string translateActiveException() const = 0; + }; + + class ExceptionTranslatorRegistrar { + template<typename T> + class ExceptionTranslator : public IExceptionTranslator { + public: + + ExceptionTranslator( std::string(*translateFunction)( T& ) ) + : m_translateFunction( translateFunction ) + {} + + std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const override { + try { + if( it == itEnd ) + std::rethrow_exception(std::current_exception()); + else + return (*it)->translate( it+1, itEnd ); + } + catch( T& ex ) { + return m_translateFunction( ex ); + } + } + + protected: + std::string(*m_translateFunction)( T& ); + }; + + public: + template<typename T> + ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) { + getMutableRegistryHub().registerTranslator + ( new ExceptionTranslator<T>( translateFunction ) ); + } + }; +} + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \ + static std::string translatorName( signature ); \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + static std::string translatorName( signature ) + +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) + +// end catch_interfaces_exception.h +// start catch_approx.h + +#include <type_traits> + +namespace Catch { +namespace Detail { + + class Approx { + private: + bool equalityComparisonImpl(double other) const; + // Validates the new margin (margin >= 0) + // out-of-line to avoid including stdexcept in the header + void setMargin(double margin); + // Validates the new epsilon (0 < epsilon < 1) + // out-of-line to avoid including stdexcept in the header + void setEpsilon(double epsilon); + + public: + explicit Approx ( double value ); + + static Approx custom(); + + Approx operator-() const; + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + Approx operator()( T const& value ) { + Approx approx( static_cast<double>(value) ); + approx.m_epsilon = m_epsilon; + approx.m_margin = m_margin; + approx.m_scale = m_scale; + return approx; + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + explicit Approx( T const& value ): Approx(static_cast<double>(value)) + {} + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator == ( const T& lhs, Approx const& rhs ) { + auto lhs_v = static_cast<double>(lhs); + return rhs.equalityComparisonImpl(lhs_v); + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator == ( Approx const& lhs, const T& rhs ) { + return operator==( rhs, lhs ); + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator != ( T const& lhs, Approx const& rhs ) { + return !operator==( lhs, rhs ); + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator != ( Approx const& lhs, T const& rhs ) { + return !operator==( rhs, lhs ); + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator <= ( T const& lhs, Approx const& rhs ) { + return static_cast<double>(lhs) < rhs.m_value || lhs == rhs; + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator <= ( Approx const& lhs, T const& rhs ) { + return lhs.m_value < static_cast<double>(rhs) || lhs == rhs; + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator >= ( T const& lhs, Approx const& rhs ) { + return static_cast<double>(lhs) > rhs.m_value || lhs == rhs; + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator >= ( Approx const& lhs, T const& rhs ) { + return lhs.m_value > static_cast<double>(rhs) || lhs == rhs; + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + Approx& epsilon( T const& newEpsilon ) { + double epsilonAsDouble = static_cast<double>(newEpsilon); + setEpsilon(epsilonAsDouble); + return *this; + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + Approx& margin( T const& newMargin ) { + double marginAsDouble = static_cast<double>(newMargin); + setMargin(marginAsDouble); + return *this; + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + Approx& scale( T const& newScale ) { + m_scale = static_cast<double>(newScale); + return *this; + } + + std::string toString() const; + + private: + double m_epsilon; + double m_margin; + double m_scale; + double m_value; + }; +} // end namespace Detail + +namespace literals { + Detail::Approx operator "" _a(long double val); + Detail::Approx operator "" _a(unsigned long long val); +} // end namespace literals + +template<> +struct StringMaker<Catch::Detail::Approx> { + static std::string convert(Catch::Detail::Approx const& value); +}; + +} // end namespace Catch + +// end catch_approx.h +// start catch_string_manip.h + +#include <string> +#include <iosfwd> + +namespace Catch { + + bool startsWith( std::string const& s, std::string const& prefix ); + bool startsWith( std::string const& s, char prefix ); + bool endsWith( std::string const& s, std::string const& suffix ); + bool endsWith( std::string const& s, char suffix ); + bool contains( std::string const& s, std::string const& infix ); + void toLowerInPlace( std::string& s ); + std::string toLower( std::string const& s ); + std::string trim( std::string const& str ); + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); + + struct pluralise { + pluralise( std::size_t count, std::string const& label ); + + friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); + + std::size_t m_count; + std::string m_label; + }; +} + +// end catch_string_manip.h +#ifndef CATCH_CONFIG_DISABLE_MATCHERS +// start catch_capture_matchers.h + +// start catch_matchers.h + +#include <string> +#include <vector> + +namespace Catch { +namespace Matchers { + namespace Impl { + + template<typename ArgT> struct MatchAllOf; + template<typename ArgT> struct MatchAnyOf; + template<typename ArgT> struct MatchNotOf; + + class MatcherUntypedBase { + public: + MatcherUntypedBase() = default; + MatcherUntypedBase ( MatcherUntypedBase const& ) = default; + MatcherUntypedBase& operator = ( MatcherUntypedBase const& ) = delete; + std::string toString() const; + + protected: + virtual ~MatcherUntypedBase(); + virtual std::string describe() const = 0; + mutable std::string m_cachedToString; + }; + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wnon-virtual-dtor" +#endif + + template<typename ObjectT> + struct MatcherMethod { + virtual bool match( ObjectT const& arg ) const = 0; + }; + +#ifdef __clang__ +# pragma clang diagnostic pop +#endif + + template<typename T> + struct MatcherBase : MatcherUntypedBase, MatcherMethod<T> { + + MatchAllOf<T> operator && ( MatcherBase const& other ) const; + MatchAnyOf<T> operator || ( MatcherBase const& other ) const; + MatchNotOf<T> operator ! () const; + }; + + template<typename ArgT> + struct MatchAllOf : MatcherBase<ArgT> { + bool match( ArgT const& arg ) const override { + for( auto matcher : m_matchers ) { + if (!matcher->match(arg)) + return false; + } + return true; + } + std::string describe() const override { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; + bool first = true; + for( auto matcher : m_matchers ) { + if( first ) + first = false; + else + description += " and "; + description += matcher->toString(); + } + description += " )"; + return description; + } + + MatchAllOf<ArgT>& operator && ( MatcherBase<ArgT> const& other ) { + m_matchers.push_back( &other ); + return *this; + } + + std::vector<MatcherBase<ArgT> const*> m_matchers; + }; + template<typename ArgT> + struct MatchAnyOf : MatcherBase<ArgT> { + + bool match( ArgT const& arg ) const override { + for( auto matcher : m_matchers ) { + if (matcher->match(arg)) + return true; + } + return false; + } + std::string describe() const override { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; + bool first = true; + for( auto matcher : m_matchers ) { + if( first ) + first = false; + else + description += " or "; + description += matcher->toString(); + } + description += " )"; + return description; + } + + MatchAnyOf<ArgT>& operator || ( MatcherBase<ArgT> const& other ) { + m_matchers.push_back( &other ); + return *this; + } + + std::vector<MatcherBase<ArgT> const*> m_matchers; + }; + + template<typename ArgT> + struct MatchNotOf : MatcherBase<ArgT> { + + MatchNotOf( MatcherBase<ArgT> const& underlyingMatcher ) : m_underlyingMatcher( underlyingMatcher ) {} + + bool match( ArgT const& arg ) const override { + return !m_underlyingMatcher.match( arg ); + } + + std::string describe() const override { + return "not " + m_underlyingMatcher.toString(); + } + MatcherBase<ArgT> const& m_underlyingMatcher; + }; + + template<typename T> + MatchAllOf<T> MatcherBase<T>::operator && ( MatcherBase const& other ) const { + return MatchAllOf<T>() && *this && other; + } + template<typename T> + MatchAnyOf<T> MatcherBase<T>::operator || ( MatcherBase const& other ) const { + return MatchAnyOf<T>() || *this || other; + } + template<typename T> + MatchNotOf<T> MatcherBase<T>::operator ! () const { + return MatchNotOf<T>( *this ); + } + + } // namespace Impl + +} // namespace Matchers + +using namespace Matchers; +using Matchers::Impl::MatcherBase; + +} // namespace Catch + +// end catch_matchers.h +// start catch_matchers_floating.h + +#include <type_traits> +#include <cmath> + +namespace Catch { +namespace Matchers { + + namespace Floating { + + enum class FloatingPointKind : uint8_t; + + struct WithinAbsMatcher : MatcherBase<double> { + WithinAbsMatcher(double target, double margin); + bool match(double const& matchee) const override; + std::string describe() const override; + private: + double m_target; + double m_margin; + }; + + struct WithinUlpsMatcher : MatcherBase<double> { + WithinUlpsMatcher(double target, int ulps, FloatingPointKind baseType); + bool match(double const& matchee) const override; + std::string describe() const override; + private: + double m_target; + int m_ulps; + FloatingPointKind m_type; + }; + + } // namespace Floating + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + Floating::WithinUlpsMatcher WithinULP(double target, int maxUlpDiff); + Floating::WithinUlpsMatcher WithinULP(float target, int maxUlpDiff); + Floating::WithinAbsMatcher WithinAbs(double target, double margin); + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_floating.h +// start catch_matchers_generic.hpp + +#include <functional> +#include <string> + +namespace Catch { +namespace Matchers { +namespace Generic { + +namespace Detail { + std::string finalizeDescription(const std::string& desc); +} + +template <typename T> +class PredicateMatcher : public MatcherBase<T> { + std::function<bool(T const&)> m_predicate; + std::string m_description; +public: + + PredicateMatcher(std::function<bool(T const&)> const& elem, std::string const& descr) + :m_predicate(std::move(elem)), + m_description(Detail::finalizeDescription(descr)) + {} + + bool match( T const& item ) const override { + return m_predicate(item); + } + + std::string describe() const override { + return m_description; + } +}; + +} // namespace Generic + + // The following functions create the actual matcher objects. + // The user has to explicitly specify type to the function, because + // infering std::function<bool(T const&)> is hard (but possible) and + // requires a lot of TMP. + template<typename T> + Generic::PredicateMatcher<T> Predicate(std::function<bool(T const&)> const& predicate, std::string const& description = "") { + return Generic::PredicateMatcher<T>(predicate, description); + } + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_generic.hpp +// start catch_matchers_string.h + +#include <string> + +namespace Catch { +namespace Matchers { + + namespace StdString { + + struct CasedString + { + CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ); + std::string adjustString( std::string const& str ) const; + std::string caseSensitivitySuffix() const; + + CaseSensitive::Choice m_caseSensitivity; + std::string m_str; + }; + + struct StringMatcherBase : MatcherBase<std::string> { + StringMatcherBase( std::string const& operation, CasedString const& comparator ); + std::string describe() const override; + + CasedString m_comparator; + std::string m_operation; + }; + + struct EqualsMatcher : StringMatcherBase { + EqualsMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + struct ContainsMatcher : StringMatcherBase { + ContainsMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + struct StartsWithMatcher : StringMatcherBase { + StartsWithMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + struct EndsWithMatcher : StringMatcherBase { + EndsWithMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + + struct RegexMatcher : MatcherBase<std::string> { + RegexMatcher( std::string regex, CaseSensitive::Choice caseSensitivity ); + bool match( std::string const& matchee ) const override; + std::string describe() const override; + + private: + std::string m_regex; + CaseSensitive::Choice m_caseSensitivity; + }; + + } // namespace StdString + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + + StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::RegexMatcher Matches( std::string const& regex, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_string.h +// start catch_matchers_vector.h + +#include <algorithm> + +namespace Catch { +namespace Matchers { + + namespace Vector { + namespace Detail { + template <typename InputIterator, typename T> + size_t count(InputIterator first, InputIterator last, T const& item) { + size_t cnt = 0; + for (; first != last; ++first) { + if (*first == item) { + ++cnt; + } + } + return cnt; + } + template <typename InputIterator, typename T> + bool contains(InputIterator first, InputIterator last, T const& item) { + for (; first != last; ++first) { + if (*first == item) { + return true; + } + } + return false; + } + } + + template<typename T> + struct ContainsElementMatcher : MatcherBase<std::vector<T>> { + + ContainsElementMatcher(T const &comparator) : m_comparator( comparator) {} + + bool match(std::vector<T> const &v) const override { + for (auto const& el : v) { + if (el == m_comparator) { + return true; + } + } + return false; + } + + std::string describe() const override { + return "Contains: " + ::Catch::Detail::stringify( m_comparator ); + } + + T const& m_comparator; + }; + + template<typename T> + struct ContainsMatcher : MatcherBase<std::vector<T>> { + + ContainsMatcher(std::vector<T> const &comparator) : m_comparator( comparator ) {} + + bool match(std::vector<T> const &v) const override { + // !TBD: see note in EqualsMatcher + if (m_comparator.size() > v.size()) + return false; + for (auto const& comparator : m_comparator) { + auto present = false; + for (const auto& el : v) { + if (el == comparator) { + present = true; + break; + } + } + if (!present) { + return false; + } + } + return true; + } + std::string describe() const override { + return "Contains: " + ::Catch::Detail::stringify( m_comparator ); + } + + std::vector<T> const& m_comparator; + }; + + template<typename T> + struct EqualsMatcher : MatcherBase<std::vector<T>> { + + EqualsMatcher(std::vector<T> const &comparator) : m_comparator( comparator ) {} + + bool match(std::vector<T> const &v) const override { + // !TBD: This currently works if all elements can be compared using != + // - a more general approach would be via a compare template that defaults + // to using !=. but could be specialised for, e.g. std::vector<T> etc + // - then just call that directly + if (m_comparator.size() != v.size()) + return false; + for (std::size_t i = 0; i < v.size(); ++i) + if (m_comparator[i] != v[i]) + return false; + return true; + } + std::string describe() const override { + return "Equals: " + ::Catch::Detail::stringify( m_comparator ); + } + std::vector<T> const& m_comparator; + }; + + template<typename T> + struct UnorderedEqualsMatcher : MatcherBase<std::vector<T>> { + UnorderedEqualsMatcher(std::vector<T> const& target) : m_target(target) {} + bool match(std::vector<T> const& vec) const override { + // Note: This is a reimplementation of std::is_permutation, + // because I don't want to include <algorithm> inside the common path + if (m_target.size() != vec.size()) { + return false; + } + auto lfirst = m_target.begin(), llast = m_target.end(); + auto rfirst = vec.begin(), rlast = vec.end(); + // Cut common prefix to optimize checking of permuted parts + while (lfirst != llast && *lfirst == *rfirst) { + ++lfirst; ++rfirst; + } + if (lfirst == llast) { + return true; + } + + for (auto mid = lfirst; mid != llast; ++mid) { + // Skip already counted items + if (Detail::contains(lfirst, mid, *mid)) { + continue; + } + size_t num_vec = Detail::count(rfirst, rlast, *mid); + if (num_vec == 0 || Detail::count(lfirst, llast, *mid) != num_vec) { + return false; + } + } + + return true; + } + + std::string describe() const override { + return "UnorderedEquals: " + ::Catch::Detail::stringify(m_target); + } + private: + std::vector<T> const& m_target; + }; + + } // namespace Vector + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + + template<typename T> + Vector::ContainsMatcher<T> Contains( std::vector<T> const& comparator ) { + return Vector::ContainsMatcher<T>( comparator ); + } + + template<typename T> + Vector::ContainsElementMatcher<T> VectorContains( T const& comparator ) { + return Vector::ContainsElementMatcher<T>( comparator ); + } + + template<typename T> + Vector::EqualsMatcher<T> Equals( std::vector<T> const& comparator ) { + return Vector::EqualsMatcher<T>( comparator ); + } + + template<typename T> + Vector::UnorderedEqualsMatcher<T> UnorderedEquals(std::vector<T> const& target) { + return Vector::UnorderedEqualsMatcher<T>(target); + } + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_vector.h +namespace Catch { + + template<typename ArgT, typename MatcherT> + class MatchExpr : public ITransientExpression { + ArgT const& m_arg; + MatcherT m_matcher; + StringRef m_matcherString; + public: + MatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef const& matcherString ) + : ITransientExpression{ true, matcher.match( arg ) }, + m_arg( arg ), + m_matcher( matcher ), + m_matcherString( matcherString ) + {} + + void streamReconstructedExpression( std::ostream &os ) const override { + auto matcherAsString = m_matcher.toString(); + os << Catch::Detail::stringify( m_arg ) << ' '; + if( matcherAsString == Detail::unprintableString ) + os << m_matcherString; + else + os << matcherAsString; + } + }; + + using StringMatcher = Matchers::Impl::MatcherBase<std::string>; + + void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef const& matcherString ); + + template<typename ArgT, typename MatcherT> + auto makeMatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef const& matcherString ) -> MatchExpr<ArgT, MatcherT> { + return MatchExpr<ArgT, MatcherT>( arg, matcher, matcherString ); + } + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CHECK_THAT( macroName, matcher, resultDisposition, arg ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + INTERNAL_CATCH_TRY { \ + catchAssertionHandler.handleExpr( Catch::makeMatchExpr( arg, matcher, #matcher##_catch_sr ) ); \ + } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_MATCHES( macroName, exceptionType, resultDisposition, matcher, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(exceptionType) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + static_cast<void>(__VA_ARGS__ ); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( exceptionType const& ex ) { \ + catchAssertionHandler.handleExpr( Catch::makeMatchExpr( ex, matcher, #matcher##_catch_sr ) ); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +// end catch_capture_matchers.h +#endif +// start catch_generators.hpp + +// start catch_interfaces_generatortracker.h + + +#include <memory> + +namespace Catch { + + namespace Generators { + class GeneratorBase { + protected: + size_t m_size = 0; + + public: + GeneratorBase( size_t size ) : m_size( size ) {} + virtual ~GeneratorBase(); + auto size() const -> size_t { return m_size; } + }; + using GeneratorBasePtr = std::unique_ptr<GeneratorBase>; + + } // namespace Generators + + struct IGeneratorTracker { + virtual ~IGeneratorTracker(); + virtual auto hasGenerator() const -> bool = 0; + virtual auto getGenerator() const -> Generators::GeneratorBasePtr const& = 0; + virtual void setGenerator( Generators::GeneratorBasePtr&& generator ) = 0; + virtual auto getIndex() const -> std::size_t = 0; + }; + +} // namespace Catch + +// end catch_interfaces_generatortracker.h +// start catch_enforce.h + +#include <stdexcept> + +namespace Catch { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + template <typename Ex> + [[noreturn]] + void throw_exception(Ex const& e) { + throw e; + } +#else // ^^ Exceptions are enabled // Exceptions are disabled vv + [[noreturn]] + void throw_exception(std::exception const& e); +#endif +} // namespace Catch; + +#define CATCH_PREPARE_EXCEPTION( type, msg ) \ + type( ( Catch::ReusableStringStream() << msg ).str() ) +#define CATCH_INTERNAL_ERROR( msg ) \ + Catch::throw_exception(CATCH_PREPARE_EXCEPTION( std::logic_error, CATCH_INTERNAL_LINEINFO << ": Internal Catch error: " << msg)) +#define CATCH_ERROR( msg ) \ + Catch::throw_exception(CATCH_PREPARE_EXCEPTION( std::domain_error, msg )) +#define CATCH_RUNTIME_ERROR( msg ) \ + Catch::throw_exception(CATCH_PREPARE_EXCEPTION( std::runtime_error, msg )) +#define CATCH_ENFORCE( condition, msg ) \ + do{ if( !(condition) ) CATCH_ERROR( msg ); } while(false) + +// end catch_enforce.h +#include <memory> +#include <vector> +#include <cassert> + +#include <utility> + +namespace Catch { +namespace Generators { + + // !TBD move this into its own location? + namespace pf{ + template<typename T, typename... Args> + std::unique_ptr<T> make_unique( Args&&... args ) { + return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); + } + } + + template<typename T> + struct IGenerator { + virtual ~IGenerator() {} + virtual auto get( size_t index ) const -> T = 0; + }; + + template<typename T> + class SingleValueGenerator : public IGenerator<T> { + T m_value; + public: + SingleValueGenerator( T const& value ) : m_value( value ) {} + + auto get( size_t ) const -> T override { + return m_value; + } + }; + + template<typename T> + class FixedValuesGenerator : public IGenerator<T> { + std::vector<T> m_values; + + public: + FixedValuesGenerator( std::initializer_list<T> values ) : m_values( values ) {} + + auto get( size_t index ) const -> T override { + return m_values[index]; + } + }; + + template<typename T> + class RangeGenerator : public IGenerator<T> { + T const m_first; + T const m_last; + + public: + RangeGenerator( T const& first, T const& last ) : m_first( first ), m_last( last ) { + assert( m_last > m_first ); + } + + auto get( size_t index ) const -> T override { + // ToDo:: introduce a safe cast to catch potential overflows + return static_cast<T>(m_first+index); + } + }; + + template<typename T> + struct NullGenerator : IGenerator<T> { + auto get( size_t ) const -> T override { + CATCH_INTERNAL_ERROR("A Null Generator is always empty"); + } + }; + + template<typename T> + class Generator { + std::unique_ptr<IGenerator<T>> m_generator; + size_t m_size; + + public: + Generator( size_t size, std::unique_ptr<IGenerator<T>> generator ) + : m_generator( std::move( generator ) ), + m_size( size ) + {} + + auto size() const -> size_t { return m_size; } + auto operator[]( size_t index ) const -> T { + assert( index < m_size ); + return m_generator->get( index ); + } + }; + + std::vector<size_t> randomiseIndices( size_t selectionSize, size_t sourceSize ); + + template<typename T> + class GeneratorRandomiser : public IGenerator<T> { + Generator<T> m_baseGenerator; + + std::vector<size_t> m_indices; + public: + GeneratorRandomiser( Generator<T>&& baseGenerator, size_t numberOfItems ) + : m_baseGenerator( std::move( baseGenerator ) ), + m_indices( randomiseIndices( numberOfItems, m_baseGenerator.size() ) ) + {} + + auto get( size_t index ) const -> T override { + return m_baseGenerator[m_indices[index]]; + } + }; + + template<typename T> + struct RequiresASpecialisationFor; + + template<typename T> + auto all() -> Generator<T> { return RequiresASpecialisationFor<T>(); } + + template<> + auto all<int>() -> Generator<int>; + + template<typename T> + auto range( T const& first, T const& last ) -> Generator<T> { + return Generator<T>( (last-first), pf::make_unique<RangeGenerator<T>>( first, last ) ); + } + + template<typename T> + auto random( T const& first, T const& last ) -> Generator<T> { + auto gen = range( first, last ); + auto size = gen.size(); + + return Generator<T>( size, pf::make_unique<GeneratorRandomiser<T>>( std::move( gen ), size ) ); + } + template<typename T> + auto random( size_t size ) -> Generator<T> { + return Generator<T>( size, pf::make_unique<GeneratorRandomiser<T>>( all<T>(), size ) ); + } + + template<typename T> + auto values( std::initializer_list<T> values ) -> Generator<T> { + return Generator<T>( values.size(), pf::make_unique<FixedValuesGenerator<T>>( values ) ); + } + template<typename T> + auto value( T const& val ) -> Generator<T> { + return Generator<T>( 1, pf::make_unique<SingleValueGenerator<T>>( val ) ); + } + + template<typename T> + auto as() -> Generator<T> { + return Generator<T>( 0, pf::make_unique<NullGenerator<T>>() ); + } + + template<typename... Ts> + auto table( std::initializer_list<std::tuple<Ts...>>&& tuples ) -> Generator<std::tuple<Ts...>> { + return values<std::tuple<Ts...>>( std::forward<std::initializer_list<std::tuple<Ts...>>>( tuples ) ); + } + + template<typename T> + struct Generators : GeneratorBase { + std::vector<Generator<T>> m_generators; + + using type = T; + + Generators() : GeneratorBase( 0 ) {} + + void populate( T&& val ) { + m_size += 1; + m_generators.emplace_back( value( std::move( val ) ) ); + } + template<typename U> + void populate( U&& val ) { + populate( T( std::move( val ) ) ); + } + void populate( Generator<T>&& generator ) { + m_size += generator.size(); + m_generators.emplace_back( std::move( generator ) ); + } + + template<typename U, typename... Gs> + void populate( U&& valueOrGenerator, Gs... moreGenerators ) { + populate( std::forward<U>( valueOrGenerator ) ); + populate( std::forward<Gs>( moreGenerators )... ); + } + + auto operator[]( size_t index ) const -> T { + size_t sizes = 0; + for( auto const& gen : m_generators ) { + auto localIndex = index-sizes; + sizes += gen.size(); + if( index < sizes ) + return gen[localIndex]; + } + CATCH_INTERNAL_ERROR("Index '" << index << "' is out of range (" << sizes << ')'); + } + }; + + template<typename T, typename... Gs> + auto makeGenerators( Generator<T>&& generator, Gs... moreGenerators ) -> Generators<T> { + Generators<T> generators; + generators.m_generators.reserve( 1+sizeof...(Gs) ); + generators.populate( std::move( generator ), std::forward<Gs>( moreGenerators )... ); + return generators; + } + template<typename T> + auto makeGenerators( Generator<T>&& generator ) -> Generators<T> { + Generators<T> generators; + generators.populate( std::move( generator ) ); + return generators; + } + template<typename T, typename... Gs> + auto makeGenerators( T&& val, Gs... moreGenerators ) -> Generators<T> { + return makeGenerators( value( std::forward<T>( val ) ), std::forward<Gs>( moreGenerators )... ); + } + template<typename T, typename U, typename... Gs> + auto makeGenerators( U&& val, Gs... moreGenerators ) -> Generators<T> { + return makeGenerators( value( T( std::forward<U>( val ) ) ), std::forward<Gs>( moreGenerators )... ); + } + + auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker&; + + template<typename L> + // Note: The type after -> is weird, because VS2015 cannot parse + // the expression used in the typedef inside, when it is in + // return type. Yeah, ¯\_(ツ)_/¯ + auto generate( SourceLineInfo const& lineInfo, L const& generatorExpression ) -> decltype(std::declval<decltype(generatorExpression())>()[0]) { + using UnderlyingType = typename decltype(generatorExpression())::type; + + IGeneratorTracker& tracker = acquireGeneratorTracker( lineInfo ); + if( !tracker.hasGenerator() ) + tracker.setGenerator( pf::make_unique<Generators<UnderlyingType>>( generatorExpression() ) ); + + auto const& generator = static_cast<Generators<UnderlyingType> const&>( *tracker.getGenerator() ); + return generator[tracker.getIndex()]; + } + +} // namespace Generators +} // namespace Catch + +#define GENERATE( ... ) \ + Catch::Generators::generate( CATCH_INTERNAL_LINEINFO, []{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) + +// end catch_generators.hpp + +// These files are included here so the single_include script doesn't put them +// in the conditionally compiled sections +// start catch_test_case_info.h + +#include <string> +#include <vector> +#include <memory> + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + struct ITestInvoker; + + struct TestCaseInfo { + enum SpecialProperties{ + None = 0, + IsHidden = 1 << 1, + ShouldFail = 1 << 2, + MayFail = 1 << 3, + Throws = 1 << 4, + NonPortable = 1 << 5, + Benchmark = 1 << 6 + }; + + TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::vector<std::string> const& _tags, + SourceLineInfo const& _lineInfo ); + + friend void setTags( TestCaseInfo& testCaseInfo, std::vector<std::string> tags ); + + bool isHidden() const; + bool throws() const; + bool okToFail() const; + bool expectedToFail() const; + + std::string tagsAsString() const; + + std::string name; + std::string className; + std::string description; + std::vector<std::string> tags; + std::vector<std::string> lcaseTags; + SourceLineInfo lineInfo; + SpecialProperties properties; + }; + + class TestCase : public TestCaseInfo { + public: + + TestCase( ITestInvoker* testCase, TestCaseInfo&& info ); + + TestCase withName( std::string const& _newName ) const; + + void invoke() const; + + TestCaseInfo const& getTestCaseInfo() const; + + bool operator == ( TestCase const& other ) const; + bool operator < ( TestCase const& other ) const; + + private: + std::shared_ptr<ITestInvoker> test; + }; + + TestCase makeTestCase( ITestInvoker* testCase, + std::string const& className, + NameAndTags const& nameAndTags, + SourceLineInfo const& lineInfo ); +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// end catch_test_case_info.h +// start catch_interfaces_runner.h + +namespace Catch { + + struct IRunner { + virtual ~IRunner(); + virtual bool aborting() const = 0; + }; +} + +// end catch_interfaces_runner.h + +#ifdef __OBJC__ +// start catch_objc.hpp + +#import <objc/runtime.h> + +#include <string> + +// NB. Any general catch headers included here must be included +// in catch.hpp first to make sure they are included by the single +// header for non obj-usage + +/////////////////////////////////////////////////////////////////////////////// +// This protocol is really only here for (self) documenting purposes, since +// all its methods are optional. +@protocol OcFixture + +@optional + +-(void) setUp; +-(void) tearDown; + +@end + +namespace Catch { + + class OcMethod : public ITestInvoker { + + public: + OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {} + + virtual void invoke() const { + id obj = [[m_cls alloc] init]; + + performOptionalSelector( obj, @selector(setUp) ); + performOptionalSelector( obj, m_sel ); + performOptionalSelector( obj, @selector(tearDown) ); + + arcSafeRelease( obj ); + } + private: + virtual ~OcMethod() {} + + Class m_cls; + SEL m_sel; + }; + + namespace Detail{ + + inline std::string getAnnotation( Class cls, + std::string const& annotationName, + std::string const& testCaseName ) { + NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()]; + SEL sel = NSSelectorFromString( selStr ); + arcSafeRelease( selStr ); + id value = performOptionalSelector( cls, sel ); + if( value ) + return [(NSString*)value UTF8String]; + return ""; + } + } + + inline std::size_t registerTestMethods() { + std::size_t noTestMethods = 0; + int noClasses = objc_getClassList( nullptr, 0 ); + + Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); + objc_getClassList( classes, noClasses ); + + for( int c = 0; c < noClasses; c++ ) { + Class cls = classes[c]; + { + u_int count; + Method* methods = class_copyMethodList( cls, &count ); + for( u_int m = 0; m < count ; m++ ) { + SEL selector = method_getName(methods[m]); + std::string methodName = sel_getName(selector); + if( startsWith( methodName, "Catch_TestCase_" ) ) { + std::string testCaseName = methodName.substr( 15 ); + std::string name = Detail::getAnnotation( cls, "Name", testCaseName ); + std::string desc = Detail::getAnnotation( cls, "Description", testCaseName ); + const char* className = class_getName( cls ); + + getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, NameAndTags( name.c_str(), desc.c_str() ), SourceLineInfo("",0) ) ); + noTestMethods++; + } + } + free(methods); + } + } + return noTestMethods; + } + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) + + namespace Matchers { + namespace Impl { + namespace NSStringMatchers { + + struct StringHolder : MatcherBase<NSString*>{ + StringHolder( NSString* substr ) : m_substr( [substr copy] ){} + StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){} + StringHolder() { + arcSafeRelease( m_substr ); + } + + bool match( NSString* arg ) const override { + return false; + } + + NSString* CATCH_ARC_STRONG m_substr; + }; + + struct Equals : StringHolder { + Equals( NSString* substr ) : StringHolder( substr ){} + + bool match( NSString* str ) const override { + return (str != nil || m_substr == nil ) && + [str isEqualToString:m_substr]; + } + + std::string describe() const override { + return "equals string: " + Catch::Detail::stringify( m_substr ); + } + }; + + struct Contains : StringHolder { + Contains( NSString* substr ) : StringHolder( substr ){} + + bool match( NSString* str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location != NSNotFound; + } + + std::string describe() const override { + return "contains string: " + Catch::Detail::stringify( m_substr ); + } + }; + + struct StartsWith : StringHolder { + StartsWith( NSString* substr ) : StringHolder( substr ){} + + bool match( NSString* str ) const override { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == 0; + } + + std::string describe() const override { + return "starts with: " + Catch::Detail::stringify( m_substr ); + } + }; + struct EndsWith : StringHolder { + EndsWith( NSString* substr ) : StringHolder( substr ){} + + bool match( NSString* str ) const override { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == [str length] - [m_substr length]; + } + + std::string describe() const override { + return "ends with: " + Catch::Detail::stringify( m_substr ); + } + }; + + } // namespace NSStringMatchers + } // namespace Impl + + inline Impl::NSStringMatchers::Equals + Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); } + + inline Impl::NSStringMatchers::Contains + Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); } + + inline Impl::NSStringMatchers::StartsWith + StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); } + + inline Impl::NSStringMatchers::EndsWith + EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); } + + } // namespace Matchers + + using namespace Matchers; + +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define OC_MAKE_UNIQUE_NAME( root, uniqueSuffix ) root##uniqueSuffix +#define OC_TEST_CASE2( name, desc, uniqueSuffix ) \ ++(NSString*) OC_MAKE_UNIQUE_NAME( Catch_Name_test_, uniqueSuffix ) \ +{ \ +return @ name; \ +} \ ++(NSString*) OC_MAKE_UNIQUE_NAME( Catch_Description_test_, uniqueSuffix ) \ +{ \ +return @ desc; \ +} \ +-(void) OC_MAKE_UNIQUE_NAME( Catch_TestCase_test_, uniqueSuffix ) + +#define OC_TEST_CASE( name, desc ) OC_TEST_CASE2( name, desc, __LINE__ ) + +// end catch_objc.hpp +#endif + +#ifdef CATCH_CONFIG_EXTERNAL_INTERFACES +// start catch_external_interfaces.h + +// start catch_reporter_bases.hpp + +// start catch_interfaces_reporter.h + +// start catch_config.hpp + +// start catch_test_spec_parser.h + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// start catch_test_spec.h + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// start catch_wildcard_pattern.h + +namespace Catch +{ + class WildcardPattern { + enum WildcardPosition { + NoWildcard = 0, + WildcardAtStart = 1, + WildcardAtEnd = 2, + WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd + }; + + public: + + WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity ); + virtual ~WildcardPattern() = default; + virtual bool matches( std::string const& str ) const; + + private: + std::string adjustCase( std::string const& str ) const; + CaseSensitive::Choice m_caseSensitivity; + WildcardPosition m_wildcard = NoWildcard; + std::string m_pattern; + }; +} + +// end catch_wildcard_pattern.h +#include <string> +#include <vector> +#include <memory> + +namespace Catch { + + class TestSpec { + struct Pattern { + virtual ~Pattern(); + virtual bool matches( TestCaseInfo const& testCase ) const = 0; + }; + using PatternPtr = std::shared_ptr<Pattern>; + + class NamePattern : public Pattern { + public: + NamePattern( std::string const& name ); + virtual ~NamePattern(); + virtual bool matches( TestCaseInfo const& testCase ) const override; + private: + WildcardPattern m_wildcardPattern; + }; + + class TagPattern : public Pattern { + public: + TagPattern( std::string const& tag ); + virtual ~TagPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const override; + private: + std::string m_tag; + }; + + class ExcludedPattern : public Pattern { + public: + ExcludedPattern( PatternPtr const& underlyingPattern ); + virtual ~ExcludedPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const override; + private: + PatternPtr m_underlyingPattern; + }; + + struct Filter { + std::vector<PatternPtr> m_patterns; + + bool matches( TestCaseInfo const& testCase ) const; + }; + + public: + bool hasFilters() const; + bool matches( TestCaseInfo const& testCase ) const; + + private: + std::vector<Filter> m_filters; + + friend class TestSpecParser; + }; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// end catch_test_spec.h +// start catch_interfaces_tag_alias_registry.h + +#include <string> + +namespace Catch { + + struct TagAlias; + + struct ITagAliasRegistry { + virtual ~ITagAliasRegistry(); + // Nullptr if not present + virtual TagAlias const* find( std::string const& alias ) const = 0; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; + + static ITagAliasRegistry const& get(); + }; + +} // end namespace Catch + +// end catch_interfaces_tag_alias_registry.h +namespace Catch { + + class TestSpecParser { + enum Mode{ None, Name, QuotedName, Tag, EscapedName }; + Mode m_mode = None; + bool m_exclusion = false; + std::size_t m_start = std::string::npos, m_pos = 0; + std::string m_arg; + std::vector<std::size_t> m_escapeChars; + TestSpec::Filter m_currentFilter; + TestSpec m_testSpec; + ITagAliasRegistry const* m_tagAliases = nullptr; + + public: + TestSpecParser( ITagAliasRegistry const& tagAliases ); + + TestSpecParser& parse( std::string const& arg ); + TestSpec testSpec(); + + private: + void visitChar( char c ); + void startNewMode( Mode mode, std::size_t start ); + void escape(); + std::string subString() const; + + template<typename T> + void addPattern() { + std::string token = subString(); + for( std::size_t i = 0; i < m_escapeChars.size(); ++i ) + token = token.substr( 0, m_escapeChars[i]-m_start-i ) + token.substr( m_escapeChars[i]-m_start-i+1 ); + m_escapeChars.clear(); + if( startsWith( token, "exclude:" ) ) { + m_exclusion = true; + token = token.substr( 8 ); + } + if( !token.empty() ) { + TestSpec::PatternPtr pattern = std::make_shared<T>( token ); + if( m_exclusion ) + pattern = std::make_shared<TestSpec::ExcludedPattern>( pattern ); + m_currentFilter.m_patterns.push_back( pattern ); + } + m_exclusion = false; + m_mode = None; + } + + void addFilter(); + }; + TestSpec parseTestSpec( std::string const& arg ); + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// end catch_test_spec_parser.h +// start catch_interfaces_config.h + +#include <iosfwd> +#include <string> +#include <vector> +#include <memory> + +namespace Catch { + + enum class Verbosity { + Quiet = 0, + Normal, + High + }; + + struct WarnAbout { enum What { + Nothing = 0x00, + NoAssertions = 0x01, + NoTests = 0x02 + }; }; + + struct ShowDurations { enum OrNot { + DefaultForReporter, + Always, + Never + }; }; + struct RunTests { enum InWhatOrder { + InDeclarationOrder, + InLexicographicalOrder, + InRandomOrder + }; }; + struct UseColour { enum YesOrNo { + Auto, + Yes, + No + }; }; + struct WaitForKeypress { enum When { + Never, + BeforeStart = 1, + BeforeExit = 2, + BeforeStartAndExit = BeforeStart | BeforeExit + }; }; + + class TestSpec; + + struct IConfig : NonCopyable { + + virtual ~IConfig(); + + virtual bool allowThrows() const = 0; + virtual std::ostream& stream() const = 0; + virtual std::string name() const = 0; + virtual bool includeSuccessfulResults() const = 0; + virtual bool shouldDebugBreak() const = 0; + virtual bool warnAboutMissingAssertions() const = 0; + virtual bool warnAboutNoTests() const = 0; + virtual int abortAfter() const = 0; + virtual bool showInvisibles() const = 0; + virtual ShowDurations::OrNot showDurations() const = 0; + virtual TestSpec const& testSpec() const = 0; + virtual bool hasTestFilters() const = 0; + virtual RunTests::InWhatOrder runOrder() const = 0; + virtual unsigned int rngSeed() const = 0; + virtual int benchmarkResolutionMultiple() const = 0; + virtual UseColour::YesOrNo useColour() const = 0; + virtual std::vector<std::string> const& getSectionsToRun() const = 0; + virtual Verbosity verbosity() const = 0; + }; + + using IConfigPtr = std::shared_ptr<IConfig const>; +} + +// end catch_interfaces_config.h +// Libstdc++ doesn't like incomplete classes for unique_ptr + +#include <memory> +#include <vector> +#include <string> + +#ifndef CATCH_CONFIG_CONSOLE_WIDTH +#define CATCH_CONFIG_CONSOLE_WIDTH 80 +#endif + +namespace Catch { + + struct IStream; + + struct ConfigData { + bool listTests = false; + bool listTags = false; + bool listReporters = false; + bool listTestNamesOnly = false; + + bool showSuccessfulTests = false; + bool shouldDebugBreak = false; + bool noThrow = false; + bool showHelp = false; + bool showInvisibles = false; + bool filenamesAsTags = false; + bool libIdentify = false; + + int abortAfter = -1; + unsigned int rngSeed = 0; + int benchmarkResolutionMultiple = 100; + + Verbosity verbosity = Verbosity::Normal; + WarnAbout::What warnings = WarnAbout::Nothing; + ShowDurations::OrNot showDurations = ShowDurations::DefaultForReporter; + RunTests::InWhatOrder runOrder = RunTests::InDeclarationOrder; + UseColour::YesOrNo useColour = UseColour::Auto; + WaitForKeypress::When waitForKeypress = WaitForKeypress::Never; + + std::string outputFilename; + std::string name; + std::string processName; +#ifndef CATCH_CONFIG_DEFAULT_REPORTER +#define CATCH_CONFIG_DEFAULT_REPORTER "console" +#endif + std::string reporterName = CATCH_CONFIG_DEFAULT_REPORTER; +#undef CATCH_CONFIG_DEFAULT_REPORTER + + std::vector<std::string> testsOrTags; + std::vector<std::string> sectionsToRun; + }; + + class Config : public IConfig { + public: + + Config() = default; + Config( ConfigData const& data ); + virtual ~Config() = default; + + std::string const& getFilename() const; + + bool listTests() const; + bool listTestNamesOnly() const; + bool listTags() const; + bool listReporters() const; + + std::string getProcessName() const; + std::string const& getReporterName() const; + + std::vector<std::string> const& getTestsOrTags() const; + std::vector<std::string> const& getSectionsToRun() const override; + + virtual TestSpec const& testSpec() const override; + bool hasTestFilters() const override; + + bool showHelp() const; + + // IConfig interface + bool allowThrows() const override; + std::ostream& stream() const override; + std::string name() const override; + bool includeSuccessfulResults() const override; + bool warnAboutMissingAssertions() const override; + bool warnAboutNoTests() const override; + ShowDurations::OrNot showDurations() const override; + RunTests::InWhatOrder runOrder() const override; + unsigned int rngSeed() const override; + int benchmarkResolutionMultiple() const override; + UseColour::YesOrNo useColour() const override; + bool shouldDebugBreak() const override; + int abortAfter() const override; + bool showInvisibles() const override; + Verbosity verbosity() const override; + + private: + + IStream const* openStream(); + ConfigData m_data; + + std::unique_ptr<IStream const> m_stream; + TestSpec m_testSpec; + bool m_hasTestFilters = false; + }; + +} // end namespace Catch + +// end catch_config.hpp +// start catch_assertionresult.h + +#include <string> + +namespace Catch { + + struct AssertionResultData + { + AssertionResultData() = delete; + + AssertionResultData( ResultWas::OfType _resultType, LazyExpression const& _lazyExpression ); + + std::string message; + mutable std::string reconstructedExpression; + LazyExpression lazyExpression; + ResultWas::OfType resultType; + + std::string reconstructExpression() const; + }; + + class AssertionResult { + public: + AssertionResult() = delete; + AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); + + bool isOk() const; + bool succeeded() const; + ResultWas::OfType getResultType() const; + bool hasExpression() const; + bool hasMessage() const; + std::string getExpression() const; + std::string getExpressionInMacro() const; + bool hasExpandedExpression() const; + std::string getExpandedExpression() const; + std::string getMessage() const; + SourceLineInfo getSourceInfo() const; + StringRef getTestMacroName() const; + + //protected: + AssertionInfo m_info; + AssertionResultData m_resultData; + }; + +} // end namespace Catch + +// end catch_assertionresult.h +// start catch_option.hpp + +namespace Catch { + + // An optional type + template<typename T> + class Option { + public: + Option() : nullableValue( nullptr ) {} + Option( T const& _value ) + : nullableValue( new( storage ) T( _value ) ) + {} + Option( Option const& _other ) + : nullableValue( _other ? new( storage ) T( *_other ) : nullptr ) + {} + + ~Option() { + reset(); + } + + Option& operator= ( Option const& _other ) { + if( &_other != this ) { + reset(); + if( _other ) + nullableValue = new( storage ) T( *_other ); + } + return *this; + } + Option& operator = ( T const& _value ) { + reset(); + nullableValue = new( storage ) T( _value ); + return *this; + } + + void reset() { + if( nullableValue ) + nullableValue->~T(); + nullableValue = nullptr; + } + + T& operator*() { return *nullableValue; } + T const& operator*() const { return *nullableValue; } + T* operator->() { return nullableValue; } + const T* operator->() const { return nullableValue; } + + T valueOr( T const& defaultValue ) const { + return nullableValue ? *nullableValue : defaultValue; + } + + bool some() const { return nullableValue != nullptr; } + bool none() const { return nullableValue == nullptr; } + + bool operator !() const { return nullableValue == nullptr; } + explicit operator bool() const { + return some(); + } + + private: + T *nullableValue; + alignas(alignof(T)) char storage[sizeof(T)]; + }; + +} // end namespace Catch + +// end catch_option.hpp +#include <string> +#include <iosfwd> +#include <map> +#include <set> +#include <memory> + +namespace Catch { + + struct ReporterConfig { + explicit ReporterConfig( IConfigPtr const& _fullConfig ); + + ReporterConfig( IConfigPtr const& _fullConfig, std::ostream& _stream ); + + std::ostream& stream() const; + IConfigPtr fullConfig() const; + + private: + std::ostream* m_stream; + IConfigPtr m_fullConfig; + }; + + struct ReporterPreferences { + bool shouldRedirectStdOut = false; + bool shouldReportAllAssertions = false; + }; + + template<typename T> + struct LazyStat : Option<T> { + LazyStat& operator=( T const& _value ) { + Option<T>::operator=( _value ); + used = false; + return *this; + } + void reset() { + Option<T>::reset(); + used = false; + } + bool used = false; + }; + + struct TestRunInfo { + TestRunInfo( std::string const& _name ); + std::string name; + }; + struct GroupInfo { + GroupInfo( std::string const& _name, + std::size_t _groupIndex, + std::size_t _groupsCount ); + + std::string name; + std::size_t groupIndex; + std::size_t groupsCounts; + }; + + struct AssertionStats { + AssertionStats( AssertionResult const& _assertionResult, + std::vector<MessageInfo> const& _infoMessages, + Totals const& _totals ); + + AssertionStats( AssertionStats const& ) = default; + AssertionStats( AssertionStats && ) = default; + AssertionStats& operator = ( AssertionStats const& ) = default; + AssertionStats& operator = ( AssertionStats && ) = default; + virtual ~AssertionStats(); + + AssertionResult assertionResult; + std::vector<MessageInfo> infoMessages; + Totals totals; + }; + + struct SectionStats { + SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ); + SectionStats( SectionStats const& ) = default; + SectionStats( SectionStats && ) = default; + SectionStats& operator = ( SectionStats const& ) = default; + SectionStats& operator = ( SectionStats && ) = default; + virtual ~SectionStats(); + + SectionInfo sectionInfo; + Counts assertions; + double durationInSeconds; + bool missingAssertions; + }; + + struct TestCaseStats { + TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ); + + TestCaseStats( TestCaseStats const& ) = default; + TestCaseStats( TestCaseStats && ) = default; + TestCaseStats& operator = ( TestCaseStats const& ) = default; + TestCaseStats& operator = ( TestCaseStats && ) = default; + virtual ~TestCaseStats(); + + TestCaseInfo testInfo; + Totals totals; + std::string stdOut; + std::string stdErr; + bool aborting; + }; + + struct TestGroupStats { + TestGroupStats( GroupInfo const& _groupInfo, + Totals const& _totals, + bool _aborting ); + TestGroupStats( GroupInfo const& _groupInfo ); + + TestGroupStats( TestGroupStats const& ) = default; + TestGroupStats( TestGroupStats && ) = default; + TestGroupStats& operator = ( TestGroupStats const& ) = default; + TestGroupStats& operator = ( TestGroupStats && ) = default; + virtual ~TestGroupStats(); + + GroupInfo groupInfo; + Totals totals; + bool aborting; + }; + + struct TestRunStats { + TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ); + + TestRunStats( TestRunStats const& ) = default; + TestRunStats( TestRunStats && ) = default; + TestRunStats& operator = ( TestRunStats const& ) = default; + TestRunStats& operator = ( TestRunStats && ) = default; + virtual ~TestRunStats(); + + TestRunInfo runInfo; + Totals totals; + bool aborting; + }; + + struct BenchmarkInfo { + std::string name; + }; + struct BenchmarkStats { + BenchmarkInfo info; + std::size_t iterations; + uint64_t elapsedTimeInNanoseconds; + }; + + struct IStreamingReporter { + virtual ~IStreamingReporter() = default; + + // Implementing class must also provide the following static methods: + // static std::string getDescription(); + // static std::set<Verbosity> getSupportedVerbosities() + + virtual ReporterPreferences getPreferences() const = 0; + + virtual void noMatchingTestCases( std::string const& spec ) = 0; + + virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; + virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; + virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; + + // *** experimental *** + virtual void benchmarkStarting( BenchmarkInfo const& ) {} + + virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; + + // The return value indicates if the messages buffer should be cleared: + virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; + + // *** experimental *** + virtual void benchmarkEnded( BenchmarkStats const& ) {} + + virtual void sectionEnded( SectionStats const& sectionStats ) = 0; + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; + virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; + + virtual void skipTest( TestCaseInfo const& testInfo ) = 0; + + // Default empty implementation provided + virtual void fatalErrorEncountered( StringRef name ); + + virtual bool isMulti() const; + }; + using IStreamingReporterPtr = std::unique_ptr<IStreamingReporter>; + + struct IReporterFactory { + virtual ~IReporterFactory(); + virtual IStreamingReporterPtr create( ReporterConfig const& config ) const = 0; + virtual std::string getDescription() const = 0; + }; + using IReporterFactoryPtr = std::shared_ptr<IReporterFactory>; + + struct IReporterRegistry { + using FactoryMap = std::map<std::string, IReporterFactoryPtr>; + using Listeners = std::vector<IReporterFactoryPtr>; + + virtual ~IReporterRegistry(); + virtual IStreamingReporterPtr create( std::string const& name, IConfigPtr const& config ) const = 0; + virtual FactoryMap const& getFactories() const = 0; + virtual Listeners const& getListeners() const = 0; + }; + +} // end namespace Catch + +// end catch_interfaces_reporter.h +#include <algorithm> +#include <cstring> +#include <cfloat> +#include <cstdio> +#include <cassert> +#include <memory> +#include <ostream> + +namespace Catch { + void prepareExpandedExpression(AssertionResult& result); + + // Returns double formatted as %.3f (format expected on output) + std::string getFormattedDuration( double duration ); + + template<typename DerivedT> + struct StreamingReporterBase : IStreamingReporter { + + StreamingReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = false; + if( !DerivedT::getSupportedVerbosities().count( m_config->verbosity() ) ) + CATCH_ERROR( "Verbosity level not supported by this reporter" ); + } + + ReporterPreferences getPreferences() const override { + return m_reporterPrefs; + } + + static std::set<Verbosity> getSupportedVerbosities() { + return { Verbosity::Normal }; + } + + ~StreamingReporterBase() override = default; + + void noMatchingTestCases(std::string const&) override {} + + void testRunStarting(TestRunInfo const& _testRunInfo) override { + currentTestRunInfo = _testRunInfo; + } + void testGroupStarting(GroupInfo const& _groupInfo) override { + currentGroupInfo = _groupInfo; + } + + void testCaseStarting(TestCaseInfo const& _testInfo) override { + currentTestCaseInfo = _testInfo; + } + void sectionStarting(SectionInfo const& _sectionInfo) override { + m_sectionStack.push_back(_sectionInfo); + } + + void sectionEnded(SectionStats const& /* _sectionStats */) override { + m_sectionStack.pop_back(); + } + void testCaseEnded(TestCaseStats const& /* _testCaseStats */) override { + currentTestCaseInfo.reset(); + } + void testGroupEnded(TestGroupStats const& /* _testGroupStats */) override { + currentGroupInfo.reset(); + } + void testRunEnded(TestRunStats const& /* _testRunStats */) override { + currentTestCaseInfo.reset(); + currentGroupInfo.reset(); + currentTestRunInfo.reset(); + } + + void skipTest(TestCaseInfo const&) override { + // Don't do anything with this by default. + // It can optionally be overridden in the derived class. + } + + IConfigPtr m_config; + std::ostream& stream; + + LazyStat<TestRunInfo> currentTestRunInfo; + LazyStat<GroupInfo> currentGroupInfo; + LazyStat<TestCaseInfo> currentTestCaseInfo; + + std::vector<SectionInfo> m_sectionStack; + ReporterPreferences m_reporterPrefs; + }; + + template<typename DerivedT> + struct CumulativeReporterBase : IStreamingReporter { + template<typename T, typename ChildNodeT> + struct Node { + explicit Node( T const& _value ) : value( _value ) {} + virtual ~Node() {} + + using ChildNodes = std::vector<std::shared_ptr<ChildNodeT>>; + T value; + ChildNodes children; + }; + struct SectionNode { + explicit SectionNode(SectionStats const& _stats) : stats(_stats) {} + virtual ~SectionNode() = default; + + bool operator == (SectionNode const& other) const { + return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; + } + bool operator == (std::shared_ptr<SectionNode> const& other) const { + return operator==(*other); + } + + SectionStats stats; + using ChildSections = std::vector<std::shared_ptr<SectionNode>>; + using Assertions = std::vector<AssertionStats>; + ChildSections childSections; + Assertions assertions; + std::string stdOut; + std::string stdErr; + }; + + struct BySectionInfo { + BySectionInfo( SectionInfo const& other ) : m_other( other ) {} + BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} + bool operator() (std::shared_ptr<SectionNode> const& node) const { + return ((node->stats.sectionInfo.name == m_other.name) && + (node->stats.sectionInfo.lineInfo == m_other.lineInfo)); + } + void operator=(BySectionInfo const&) = delete; + + private: + SectionInfo const& m_other; + }; + + using TestCaseNode = Node<TestCaseStats, SectionNode>; + using TestGroupNode = Node<TestGroupStats, TestCaseNode>; + using TestRunNode = Node<TestRunStats, TestGroupNode>; + + CumulativeReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = false; + if( !DerivedT::getSupportedVerbosities().count( m_config->verbosity() ) ) + CATCH_ERROR( "Verbosity level not supported by this reporter" ); + } + ~CumulativeReporterBase() override = default; + + ReporterPreferences getPreferences() const override { + return m_reporterPrefs; + } + + static std::set<Verbosity> getSupportedVerbosities() { + return { Verbosity::Normal }; + } + + void testRunStarting( TestRunInfo const& ) override {} + void testGroupStarting( GroupInfo const& ) override {} + + void testCaseStarting( TestCaseInfo const& ) override {} + + void sectionStarting( SectionInfo const& sectionInfo ) override { + SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); + std::shared_ptr<SectionNode> node; + if( m_sectionStack.empty() ) { + if( !m_rootSection ) + m_rootSection = std::make_shared<SectionNode>( incompleteStats ); + node = m_rootSection; + } + else { + SectionNode& parentNode = *m_sectionStack.back(); + auto it = + std::find_if( parentNode.childSections.begin(), + parentNode.childSections.end(), + BySectionInfo( sectionInfo ) ); + if( it == parentNode.childSections.end() ) { + node = std::make_shared<SectionNode>( incompleteStats ); + parentNode.childSections.push_back( node ); + } + else + node = *it; + } + m_sectionStack.push_back( node ); + m_deepestSection = std::move(node); + } + + void assertionStarting(AssertionInfo const&) override {} + + bool assertionEnded(AssertionStats const& assertionStats) override { + assert(!m_sectionStack.empty()); + // AssertionResult holds a pointer to a temporary DecomposedExpression, + // which getExpandedExpression() calls to build the expression string. + // Our section stack copy of the assertionResult will likely outlive the + // temporary, so it must be expanded or discarded now to avoid calling + // a destroyed object later. + prepareExpandedExpression(const_cast<AssertionResult&>( assertionStats.assertionResult ) ); + SectionNode& sectionNode = *m_sectionStack.back(); + sectionNode.assertions.push_back(assertionStats); + return true; + } + void sectionEnded(SectionStats const& sectionStats) override { + assert(!m_sectionStack.empty()); + SectionNode& node = *m_sectionStack.back(); + node.stats = sectionStats; + m_sectionStack.pop_back(); + } + void testCaseEnded(TestCaseStats const& testCaseStats) override { + auto node = std::make_shared<TestCaseNode>(testCaseStats); + assert(m_sectionStack.size() == 0); + node->children.push_back(m_rootSection); + m_testCases.push_back(node); + m_rootSection.reset(); + + assert(m_deepestSection); + m_deepestSection->stdOut = testCaseStats.stdOut; + m_deepestSection->stdErr = testCaseStats.stdErr; + } + void testGroupEnded(TestGroupStats const& testGroupStats) override { + auto node = std::make_shared<TestGroupNode>(testGroupStats); + node->children.swap(m_testCases); + m_testGroups.push_back(node); + } + void testRunEnded(TestRunStats const& testRunStats) override { + auto node = std::make_shared<TestRunNode>(testRunStats); + node->children.swap(m_testGroups); + m_testRuns.push_back(node); + testRunEndedCumulative(); + } + virtual void testRunEndedCumulative() = 0; + + void skipTest(TestCaseInfo const&) override {} + + IConfigPtr m_config; + std::ostream& stream; + std::vector<AssertionStats> m_assertions; + std::vector<std::vector<std::shared_ptr<SectionNode>>> m_sections; + std::vector<std::shared_ptr<TestCaseNode>> m_testCases; + std::vector<std::shared_ptr<TestGroupNode>> m_testGroups; + + std::vector<std::shared_ptr<TestRunNode>> m_testRuns; + + std::shared_ptr<SectionNode> m_rootSection; + std::shared_ptr<SectionNode> m_deepestSection; + std::vector<std::shared_ptr<SectionNode>> m_sectionStack; + ReporterPreferences m_reporterPrefs; + }; + + template<char C> + char const* getLineOfChars() { + static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; + if( !*line ) { + std::memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); + line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; + } + return line; + } + + struct TestEventListenerBase : StreamingReporterBase<TestEventListenerBase> { + TestEventListenerBase( ReporterConfig const& _config ); + + static std::set<Verbosity> getSupportedVerbosities(); + + void assertionStarting(AssertionInfo const&) override; + bool assertionEnded(AssertionStats const&) override; + }; + +} // end namespace Catch + +// end catch_reporter_bases.hpp +// start catch_console_colour.h + +namespace Catch { + + struct Colour { + enum Code { + None = 0, + + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White, + BrightYellow = Bright | Yellow, + + // By intention + FileName = LightGrey, + Warning = BrightYellow, + ResultError = BrightRed, + ResultSuccess = BrightGreen, + ResultExpectedFailure = Warning, + + Error = BrightRed, + Success = Green, + + OriginalExpression = Cyan, + ReconstructedExpression = BrightYellow, + + SecondaryText = LightGrey, + Headers = White + }; + + // Use constructed object for RAII guard + Colour( Code _colourCode ); + Colour( Colour&& other ) noexcept; + Colour& operator=( Colour&& other ) noexcept; + ~Colour(); + + // Use static method for one-shot changes + static void use( Code _colourCode ); + + private: + bool m_moved = false; + }; + + std::ostream& operator << ( std::ostream& os, Colour const& ); + +} // end namespace Catch + +// end catch_console_colour.h +// start catch_reporter_registrars.hpp + + +namespace Catch { + + template<typename T> + class ReporterRegistrar { + + class ReporterFactory : public IReporterFactory { + + virtual IStreamingReporterPtr create( ReporterConfig const& config ) const override { + return std::unique_ptr<T>( new T( config ) ); + } + + virtual std::string getDescription() const override { + return T::getDescription(); + } + }; + + public: + + explicit ReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, std::make_shared<ReporterFactory>() ); + } + }; + + template<typename T> + class ListenerRegistrar { + + class ListenerFactory : public IReporterFactory { + + virtual IStreamingReporterPtr create( ReporterConfig const& config ) const override { + return std::unique_ptr<T>( new T( config ) ); + } + virtual std::string getDescription() const override { + return std::string(); + } + }; + + public: + + ListenerRegistrar() { + getMutableRegistryHub().registerListener( std::make_shared<ListenerFactory>() ); + } + }; +} + +#if !defined(CATCH_CONFIG_DISABLE) + +#define CATCH_REGISTER_REPORTER( name, reporterType ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::ReporterRegistrar<reporterType> catch_internal_RegistrarFor##reporterType( name ); } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + +#define CATCH_REGISTER_LISTENER( listenerType ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::ListenerRegistrar<listenerType> catch_internal_RegistrarFor##listenerType; } \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +#else // CATCH_CONFIG_DISABLE + +#define CATCH_REGISTER_REPORTER(name, reporterType) +#define CATCH_REGISTER_LISTENER(listenerType) + +#endif // CATCH_CONFIG_DISABLE + +// end catch_reporter_registrars.hpp +// Allow users to base their work off existing reporters +// start catch_reporter_compact.h + +namespace Catch { + + struct CompactReporter : StreamingReporterBase<CompactReporter> { + + using StreamingReporterBase::StreamingReporterBase; + + ~CompactReporter() override; + + static std::string getDescription(); + + ReporterPreferences getPreferences() const override; + + void noMatchingTestCases(std::string const& spec) override; + + void assertionStarting(AssertionInfo const&) override; + + bool assertionEnded(AssertionStats const& _assertionStats) override; + + void sectionEnded(SectionStats const& _sectionStats) override; + + void testRunEnded(TestRunStats const& _testRunStats) override; + + }; + +} // end namespace Catch + +// end catch_reporter_compact.h +// start catch_reporter_console.h + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled + // and default is missing) is enabled +#endif + +namespace Catch { + // Fwd decls + struct SummaryColumn; + class TablePrinter; + + struct ConsoleReporter : StreamingReporterBase<ConsoleReporter> { + std::unique_ptr<TablePrinter> m_tablePrinter; + + ConsoleReporter(ReporterConfig const& config); + ~ConsoleReporter() override; + static std::string getDescription(); + + void noMatchingTestCases(std::string const& spec) override; + + void assertionStarting(AssertionInfo const&) override; + + bool assertionEnded(AssertionStats const& _assertionStats) override; + + void sectionStarting(SectionInfo const& _sectionInfo) override; + void sectionEnded(SectionStats const& _sectionStats) override; + + void benchmarkStarting(BenchmarkInfo const& info) override; + void benchmarkEnded(BenchmarkStats const& stats) override; + + void testCaseEnded(TestCaseStats const& _testCaseStats) override; + void testGroupEnded(TestGroupStats const& _testGroupStats) override; + void testRunEnded(TestRunStats const& _testRunStats) override; + + private: + + void lazyPrint(); + + void lazyPrintWithoutClosingBenchmarkTable(); + void lazyPrintRunInfo(); + void lazyPrintGroupInfo(); + void printTestCaseAndSectionHeader(); + + void printClosedHeader(std::string const& _name); + void printOpenHeader(std::string const& _name); + + // if string has a : in first line will set indent to follow it on + // subsequent lines + void printHeaderString(std::string const& _string, std::size_t indent = 0); + + void printTotals(Totals const& totals); + void printSummaryRow(std::string const& label, std::vector<SummaryColumn> const& cols, std::size_t row); + + void printTotalsDivider(Totals const& totals); + void printSummaryDivider(); + + private: + bool m_headerPrinted = false; + }; + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +// end catch_reporter_console.h +// start catch_reporter_junit.h + +// start catch_xmlwriter.h + +#include <vector> + +namespace Catch { + + class XmlEncode { + public: + enum ForWhat { ForTextNodes, ForAttributes }; + + XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ); + + void encodeTo( std::ostream& os ) const; + + friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ); + + private: + std::string m_str; + ForWhat m_forWhat; + }; + + class XmlWriter { + public: + + class ScopedElement { + public: + ScopedElement( XmlWriter* writer ); + + ScopedElement( ScopedElement&& other ) noexcept; + ScopedElement& operator=( ScopedElement&& other ) noexcept; + + ~ScopedElement(); + + ScopedElement& writeText( std::string const& text, bool indent = true ); + + template<typename T> + ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + private: + mutable XmlWriter* m_writer = nullptr; + }; + + XmlWriter( std::ostream& os = Catch::cout() ); + ~XmlWriter(); + + XmlWriter( XmlWriter const& ) = delete; + XmlWriter& operator=( XmlWriter const& ) = delete; + + XmlWriter& startElement( std::string const& name ); + + ScopedElement scopedElement( std::string const& name ); + + XmlWriter& endElement(); + + XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ); + + XmlWriter& writeAttribute( std::string const& name, bool attribute ); + + template<typename T> + XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { + ReusableStringStream rss; + rss << attribute; + return writeAttribute( name, rss.str() ); + } + + XmlWriter& writeText( std::string const& text, bool indent = true ); + + XmlWriter& writeComment( std::string const& text ); + + void writeStylesheetRef( std::string const& url ); + + XmlWriter& writeBlankLine(); + + void ensureTagClosed(); + + private: + + void writeDeclaration(); + + void newlineIfNecessary(); + + bool m_tagIsOpen = false; + bool m_needsNewline = false; + std::vector<std::string> m_tags; + std::string m_indent; + std::ostream& m_os; + }; + +} + +// end catch_xmlwriter.h +namespace Catch { + + class JunitReporter : public CumulativeReporterBase<JunitReporter> { + public: + JunitReporter(ReporterConfig const& _config); + + ~JunitReporter() override; + + static std::string getDescription(); + + void noMatchingTestCases(std::string const& /*spec*/) override; + + void testRunStarting(TestRunInfo const& runInfo) override; + + void testGroupStarting(GroupInfo const& groupInfo) override; + + void testCaseStarting(TestCaseInfo const& testCaseInfo) override; + bool assertionEnded(AssertionStats const& assertionStats) override; + + void testCaseEnded(TestCaseStats const& testCaseStats) override; + + void testGroupEnded(TestGroupStats const& testGroupStats) override; + + void testRunEndedCumulative() override; + + void writeGroup(TestGroupNode const& groupNode, double suiteTime); + + void writeTestCase(TestCaseNode const& testCaseNode); + + void writeSection(std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode); + + void writeAssertions(SectionNode const& sectionNode); + void writeAssertion(AssertionStats const& stats); + + XmlWriter xml; + Timer suiteTimer; + std::string stdOutForSuite; + std::string stdErrForSuite; + unsigned int unexpectedExceptions = 0; + bool m_okToFail = false; + }; + +} // end namespace Catch + +// end catch_reporter_junit.h +// start catch_reporter_xml.h + +namespace Catch { + class XmlReporter : public StreamingReporterBase<XmlReporter> { + public: + XmlReporter(ReporterConfig const& _config); + + ~XmlReporter() override; + + static std::string getDescription(); + + virtual std::string getStylesheetRef() const; + + void writeSourceInfo(SourceLineInfo const& sourceInfo); + + public: // StreamingReporterBase + + void noMatchingTestCases(std::string const& s) override; + + void testRunStarting(TestRunInfo const& testInfo) override; + + void testGroupStarting(GroupInfo const& groupInfo) override; + + void testCaseStarting(TestCaseInfo const& testInfo) override; + + void sectionStarting(SectionInfo const& sectionInfo) override; + + void assertionStarting(AssertionInfo const&) override; + + bool assertionEnded(AssertionStats const& assertionStats) override; + + void sectionEnded(SectionStats const& sectionStats) override; + + void testCaseEnded(TestCaseStats const& testCaseStats) override; + + void testGroupEnded(TestGroupStats const& testGroupStats) override; + + void testRunEnded(TestRunStats const& testRunStats) override; + + private: + Timer m_testCaseTimer; + XmlWriter m_xml; + int m_sectionDepth = 0; + }; + +} // end namespace Catch + +// end catch_reporter_xml.h + +// end catch_external_interfaces.h +#endif + +#endif // ! CATCH_CONFIG_IMPL_ONLY + +#ifdef CATCH_IMPL +// start catch_impl.hpp + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +// Keep these here for external reporters +// start catch_test_case_tracker.h + +#include <string> +#include <vector> +#include <memory> + +namespace Catch { +namespace TestCaseTracking { + + struct NameAndLocation { + std::string name; + SourceLineInfo location; + + NameAndLocation( std::string const& _name, SourceLineInfo const& _location ); + }; + + struct ITracker; + + using ITrackerPtr = std::shared_ptr<ITracker>; + + struct ITracker { + virtual ~ITracker(); + + // static queries + virtual NameAndLocation const& nameAndLocation() const = 0; + + // dynamic queries + virtual bool isComplete() const = 0; // Successfully completed or failed + virtual bool isSuccessfullyCompleted() const = 0; + virtual bool isOpen() const = 0; // Started but not complete + virtual bool hasChildren() const = 0; + + virtual ITracker& parent() = 0; + + // actions + virtual void close() = 0; // Successfully complete + virtual void fail() = 0; + virtual void markAsNeedingAnotherRun() = 0; + + virtual void addChild( ITrackerPtr const& child ) = 0; + virtual ITrackerPtr findChild( NameAndLocation const& nameAndLocation ) = 0; + virtual void openChild() = 0; + + // Debug/ checking + virtual bool isSectionTracker() const = 0; + virtual bool isIndexTracker() const = 0; + }; + + class TrackerContext { + + enum RunState { + NotStarted, + Executing, + CompletedCycle + }; + + ITrackerPtr m_rootTracker; + ITracker* m_currentTracker = nullptr; + RunState m_runState = NotStarted; + + public: + + static TrackerContext& instance(); + + ITracker& startRun(); + void endRun(); + + void startCycle(); + void completeCycle(); + + bool completedCycle() const; + ITracker& currentTracker(); + void setCurrentTracker( ITracker* tracker ); + }; + + class TrackerBase : public ITracker { + protected: + enum CycleState { + NotStarted, + Executing, + ExecutingChildren, + NeedsAnotherRun, + CompletedSuccessfully, + Failed + }; + + using Children = std::vector<ITrackerPtr>; + NameAndLocation m_nameAndLocation; + TrackerContext& m_ctx; + ITracker* m_parent; + Children m_children; + CycleState m_runState = NotStarted; + + public: + TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ); + + NameAndLocation const& nameAndLocation() const override; + bool isComplete() const override; + bool isSuccessfullyCompleted() const override; + bool isOpen() const override; + bool hasChildren() const override; + + void addChild( ITrackerPtr const& child ) override; + + ITrackerPtr findChild( NameAndLocation const& nameAndLocation ) override; + ITracker& parent() override; + + void openChild() override; + + bool isSectionTracker() const override; + bool isIndexTracker() const override; + + void open(); + + void close() override; + void fail() override; + void markAsNeedingAnotherRun() override; + + private: + void moveToParent(); + void moveToThis(); + }; + + class SectionTracker : public TrackerBase { + std::vector<std::string> m_filters; + public: + SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ); + + bool isSectionTracker() const override; + + static SectionTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ); + + void tryOpen(); + + void addInitialFilters( std::vector<std::string> const& filters ); + void addNextFilters( std::vector<std::string> const& filters ); + }; + + class IndexTracker : public TrackerBase { + int m_size; + int m_index = -1; + public: + IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size ); + + bool isIndexTracker() const override; + void close() override; + + static IndexTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ); + + int index() const; + + void moveNext(); + }; + +} // namespace TestCaseTracking + +using TestCaseTracking::ITracker; +using TestCaseTracking::TrackerContext; +using TestCaseTracking::SectionTracker; +using TestCaseTracking::IndexTracker; + +} // namespace Catch + +// end catch_test_case_tracker.h + +// start catch_leak_detector.h + +namespace Catch { + + struct LeakDetector { + LeakDetector(); + ~LeakDetector(); + }; + +} +// end catch_leak_detector.h +// Cpp files will be included in the single-header file here +// start catch_approx.cpp + +#include <cmath> +#include <limits> + +namespace { + +// Performs equivalent check of std::fabs(lhs - rhs) <= margin +// But without the subtraction to allow for INFINITY in comparison +bool marginComparison(double lhs, double rhs, double margin) { + return (lhs + margin >= rhs) && (rhs + margin >= lhs); +} + +} + +namespace Catch { +namespace Detail { + + Approx::Approx ( double value ) + : m_epsilon( std::numeric_limits<float>::epsilon()*100 ), + m_margin( 0.0 ), + m_scale( 0.0 ), + m_value( value ) + {} + + Approx Approx::custom() { + return Approx( 0 ); + } + + Approx Approx::operator-() const { + auto temp(*this); + temp.m_value = -temp.m_value; + return temp; + } + + std::string Approx::toString() const { + ReusableStringStream rss; + rss << "Approx( " << ::Catch::Detail::stringify( m_value ) << " )"; + return rss.str(); + } + + bool Approx::equalityComparisonImpl(const double other) const { + // First try with fixed margin, then compute margin based on epsilon, scale and Approx's value + // Thanks to Richard Harris for his help refining the scaled margin value + return marginComparison(m_value, other, m_margin) || marginComparison(m_value, other, m_epsilon * (m_scale + std::fabs(m_value))); + } + + void Approx::setMargin(double margin) { + CATCH_ENFORCE(margin >= 0, + "Invalid Approx::margin: " << margin << '.' + << " Approx::Margin has to be non-negative."); + m_margin = margin; + } + + void Approx::setEpsilon(double epsilon) { + CATCH_ENFORCE(epsilon >= 0 && epsilon <= 1.0, + "Invalid Approx::epsilon: " << epsilon << '.' + << " Approx::epsilon has to be in [0, 1]"); + m_epsilon = epsilon; + } + +} // end namespace Detail + +namespace literals { + Detail::Approx operator "" _a(long double val) { + return Detail::Approx(val); + } + Detail::Approx operator "" _a(unsigned long long val) { + return Detail::Approx(val); + } +} // end namespace literals + +std::string StringMaker<Catch::Detail::Approx>::convert(Catch::Detail::Approx const& value) { + return value.toString(); +} + +} // end namespace Catch +// end catch_approx.cpp +// start catch_assertionhandler.cpp + +// start catch_context.h + +#include <memory> + +namespace Catch { + + struct IResultCapture; + struct IRunner; + struct IConfig; + struct IMutableContext; + + using IConfigPtr = std::shared_ptr<IConfig const>; + + struct IContext + { + virtual ~IContext(); + + virtual IResultCapture* getResultCapture() = 0; + virtual IRunner* getRunner() = 0; + virtual IConfigPtr const& getConfig() const = 0; + }; + + struct IMutableContext : IContext + { + virtual ~IMutableContext(); + virtual void setResultCapture( IResultCapture* resultCapture ) = 0; + virtual void setRunner( IRunner* runner ) = 0; + virtual void setConfig( IConfigPtr const& config ) = 0; + + private: + static IMutableContext *currentContext; + friend IMutableContext& getCurrentMutableContext(); + friend void cleanUpContext(); + static void createContext(); + }; + + inline IMutableContext& getCurrentMutableContext() + { + if( !IMutableContext::currentContext ) + IMutableContext::createContext(); + return *IMutableContext::currentContext; + } + + inline IContext& getCurrentContext() + { + return getCurrentMutableContext(); + } + + void cleanUpContext(); +} + +// end catch_context.h +// start catch_debugger.h + +namespace Catch { + bool isDebuggerActive(); +} + +#ifdef CATCH_PLATFORM_MAC + + #define CATCH_TRAP() __asm__("int $3\n" : : ) /* NOLINT */ + +#elif defined(CATCH_PLATFORM_LINUX) + // If we can use inline assembler, do it because this allows us to break + // directly at the location of the failing check instead of breaking inside + // raise() called from it, i.e. one stack frame below. + #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) + #define CATCH_TRAP() asm volatile ("int $3") /* NOLINT */ + #else // Fall back to the generic way. + #include <signal.h> + + #define CATCH_TRAP() raise(SIGTRAP) + #endif +#elif defined(_MSC_VER) + #define CATCH_TRAP() __debugbreak() +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) void __stdcall DebugBreak(); + #define CATCH_TRAP() DebugBreak() +#endif + +#ifdef CATCH_TRAP + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } +#else + namespace Catch { + inline void doNothing() {} + } + #define CATCH_BREAK_INTO_DEBUGGER() Catch::doNothing() +#endif + +// end catch_debugger.h +// start catch_run_context.h + +// start catch_fatal_condition.h + +// start catch_windows_h_proxy.h + + +#if defined(CATCH_PLATFORM_WINDOWS) + +#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) +# define CATCH_DEFINED_NOMINMAX +# define NOMINMAX +#endif +#if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) +# define CATCH_DEFINED_WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif + +#ifdef __AFXDLL +#include <AfxWin.h> +#else +#include <windows.h> +#endif + +#ifdef CATCH_DEFINED_NOMINMAX +# undef NOMINMAX +#endif +#ifdef CATCH_DEFINED_WIN32_LEAN_AND_MEAN +# undef WIN32_LEAN_AND_MEAN +#endif + +#endif // defined(CATCH_PLATFORM_WINDOWS) + +// end catch_windows_h_proxy.h +#if defined( CATCH_CONFIG_WINDOWS_SEH ) + +namespace Catch { + + struct FatalConditionHandler { + + static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo); + FatalConditionHandler(); + static void reset(); + ~FatalConditionHandler(); + + private: + static bool isSet; + static ULONG guaranteeSize; + static PVOID exceptionHandlerHandle; + }; + +} // namespace Catch + +#elif defined ( CATCH_CONFIG_POSIX_SIGNALS ) + +#include <signal.h> + +namespace Catch { + + struct FatalConditionHandler { + + static bool isSet; + static struct sigaction oldSigActions[]; + static stack_t oldSigStack; + static char altStackMem[]; + + static void handleSignal( int sig ); + + FatalConditionHandler(); + ~FatalConditionHandler(); + static void reset(); + }; + +} // namespace Catch + +#else + +namespace Catch { + struct FatalConditionHandler { + void reset(); + }; +} + +#endif + +// end catch_fatal_condition.h +#include <string> + +namespace Catch { + + struct IMutableContext; + + /////////////////////////////////////////////////////////////////////////// + + class RunContext : public IResultCapture, public IRunner { + + public: + RunContext( RunContext const& ) = delete; + RunContext& operator =( RunContext const& ) = delete; + + explicit RunContext( IConfigPtr const& _config, IStreamingReporterPtr&& reporter ); + + ~RunContext() override; + + void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ); + void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ); + + Totals runTest(TestCase const& testCase); + + IConfigPtr config() const; + IStreamingReporter& reporter() const; + + public: // IResultCapture + + // Assertion handlers + void handleExpr + ( AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction ) override; + void handleMessage + ( AssertionInfo const& info, + ResultWas::OfType resultType, + StringRef const& message, + AssertionReaction& reaction ) override; + void handleUnexpectedExceptionNotThrown + ( AssertionInfo const& info, + AssertionReaction& reaction ) override; + void handleUnexpectedInflightException + ( AssertionInfo const& info, + std::string const& message, + AssertionReaction& reaction ) override; + void handleIncomplete + ( AssertionInfo const& info ) override; + void handleNonExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction ) override; + + bool sectionStarted( SectionInfo const& sectionInfo, Counts& assertions ) override; + + void sectionEnded( SectionEndInfo const& endInfo ) override; + void sectionEndedEarly( SectionEndInfo const& endInfo ) override; + + auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& override; + + void benchmarkStarting( BenchmarkInfo const& info ) override; + void benchmarkEnded( BenchmarkStats const& stats ) override; + + void pushScopedMessage( MessageInfo const& message ) override; + void popScopedMessage( MessageInfo const& message ) override; + + std::string getCurrentTestName() const override; + + const AssertionResult* getLastResult() const override; + + void exceptionEarlyReported() override; + + void handleFatalErrorCondition( StringRef message ) override; + + bool lastAssertionPassed() override; + + void assertionPassed() override; + + public: + // !TBD We need to do this another way! + bool aborting() const final; + + private: + + void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ); + void invokeActiveTestCase(); + + void resetAssertionInfo(); + bool testForMissingAssertions( Counts& assertions ); + + void assertionEnded( AssertionResult const& result ); + void reportExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + ITransientExpression const *expr, + bool negated ); + + void populateReaction( AssertionReaction& reaction ); + + private: + + void handleUnfinishedSections(); + + TestRunInfo m_runInfo; + IMutableContext& m_context; + TestCase const* m_activeTestCase = nullptr; + ITracker* m_testCaseTracker; + Option<AssertionResult> m_lastResult; + + IConfigPtr m_config; + Totals m_totals; + IStreamingReporterPtr m_reporter; + std::vector<MessageInfo> m_messages; + AssertionInfo m_lastAssertionInfo; + std::vector<SectionEndInfo> m_unfinishedSections; + std::vector<ITracker*> m_activeSections; + TrackerContext m_trackerContext; + bool m_lastAssertionPassed = false; + bool m_shouldReportUnexpected = true; + bool m_includeSuccessfulResults; + }; + +} // end namespace Catch + +// end catch_run_context.h +namespace Catch { + + namespace { + auto operator <<( std::ostream& os, ITransientExpression const& expr ) -> std::ostream& { + expr.streamReconstructedExpression( os ); + return os; + } + } + + LazyExpression::LazyExpression( bool isNegated ) + : m_isNegated( isNegated ) + {} + + LazyExpression::LazyExpression( LazyExpression const& other ) : m_isNegated( other.m_isNegated ) {} + + LazyExpression::operator bool() const { + return m_transientExpression != nullptr; + } + + auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream& { + if( lazyExpr.m_isNegated ) + os << "!"; + + if( lazyExpr ) { + if( lazyExpr.m_isNegated && lazyExpr.m_transientExpression->isBinaryExpression() ) + os << "(" << *lazyExpr.m_transientExpression << ")"; + else + os << *lazyExpr.m_transientExpression; + } + else { + os << "{** error - unchecked empty expression requested **}"; + } + return os; + } + + AssertionHandler::AssertionHandler + ( StringRef const& macroName, + SourceLineInfo const& lineInfo, + StringRef capturedExpression, + ResultDisposition::Flags resultDisposition ) + : m_assertionInfo{ macroName, lineInfo, capturedExpression, resultDisposition }, + m_resultCapture( getResultCapture() ) + {} + + void AssertionHandler::handleExpr( ITransientExpression const& expr ) { + m_resultCapture.handleExpr( m_assertionInfo, expr, m_reaction ); + } + void AssertionHandler::handleMessage(ResultWas::OfType resultType, StringRef const& message) { + m_resultCapture.handleMessage( m_assertionInfo, resultType, message, m_reaction ); + } + + auto AssertionHandler::allowThrows() const -> bool { + return getCurrentContext().getConfig()->allowThrows(); + } + + void AssertionHandler::complete() { + setCompleted(); + if( m_reaction.shouldDebugBreak ) { + + // If you find your debugger stopping you here then go one level up on the + // call-stack for the code that caused it (typically a failed assertion) + + // (To go back to the test and change execution, jump over the throw, next) + CATCH_BREAK_INTO_DEBUGGER(); + } + if (m_reaction.shouldThrow) { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + throw Catch::TestFailureException(); +#else + CATCH_ERROR( "Test failure requires aborting test!" ); +#endif + } + } + void AssertionHandler::setCompleted() { + m_completed = true; + } + + void AssertionHandler::handleUnexpectedInflightException() { + m_resultCapture.handleUnexpectedInflightException( m_assertionInfo, Catch::translateActiveException(), m_reaction ); + } + + void AssertionHandler::handleExceptionThrownAsExpected() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + void AssertionHandler::handleExceptionNotThrownAsExpected() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + + void AssertionHandler::handleUnexpectedExceptionNotThrown() { + m_resultCapture.handleUnexpectedExceptionNotThrown( m_assertionInfo, m_reaction ); + } + + void AssertionHandler::handleThrowingCallSkipped() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + + // This is the overload that takes a string and infers the Equals matcher from it + // The more general overload, that takes any string matcher, is in catch_capture_matchers.cpp + void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef const& matcherString ) { + handleExceptionMatchExpr( handler, Matchers::Equals( str ), matcherString ); + } + +} // namespace Catch +// end catch_assertionhandler.cpp +// start catch_assertionresult.cpp + +namespace Catch { + AssertionResultData::AssertionResultData(ResultWas::OfType _resultType, LazyExpression const & _lazyExpression): + lazyExpression(_lazyExpression), + resultType(_resultType) {} + + std::string AssertionResultData::reconstructExpression() const { + + if( reconstructedExpression.empty() ) { + if( lazyExpression ) { + ReusableStringStream rss; + rss << lazyExpression; + reconstructedExpression = rss.str(); + } + } + return reconstructedExpression; + } + + AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) + : m_info( info ), + m_resultData( data ) + {} + + // Result was a success + bool AssertionResult::succeeded() const { + return Catch::isOk( m_resultData.resultType ); + } + + // Result was a success, or failure is suppressed + bool AssertionResult::isOk() const { + return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); + } + + ResultWas::OfType AssertionResult::getResultType() const { + return m_resultData.resultType; + } + + bool AssertionResult::hasExpression() const { + return m_info.capturedExpression[0] != 0; + } + + bool AssertionResult::hasMessage() const { + return !m_resultData.message.empty(); + } + + std::string AssertionResult::getExpression() const { + if( isFalseTest( m_info.resultDisposition ) ) + return "!(" + m_info.capturedExpression + ")"; + else + return m_info.capturedExpression; + } + + std::string AssertionResult::getExpressionInMacro() const { + std::string expr; + if( m_info.macroName[0] == 0 ) + expr = m_info.capturedExpression; + else { + expr.reserve( m_info.macroName.size() + m_info.capturedExpression.size() + 4 ); + expr += m_info.macroName; + expr += "( "; + expr += m_info.capturedExpression; + expr += " )"; + } + return expr; + } + + bool AssertionResult::hasExpandedExpression() const { + return hasExpression() && getExpandedExpression() != getExpression(); + } + + std::string AssertionResult::getExpandedExpression() const { + std::string expr = m_resultData.reconstructExpression(); + return expr.empty() + ? getExpression() + : expr; + } + + std::string AssertionResult::getMessage() const { + return m_resultData.message; + } + SourceLineInfo AssertionResult::getSourceInfo() const { + return m_info.lineInfo; + } + + StringRef AssertionResult::getTestMacroName() const { + return m_info.macroName; + } + +} // end namespace Catch +// end catch_assertionresult.cpp +// start catch_benchmark.cpp + +namespace Catch { + + auto BenchmarkLooper::getResolution() -> uint64_t { + return getEstimatedClockResolution() * getCurrentContext().getConfig()->benchmarkResolutionMultiple(); + } + + void BenchmarkLooper::reportStart() { + getResultCapture().benchmarkStarting( { m_name } ); + } + auto BenchmarkLooper::needsMoreIterations() -> bool { + auto elapsed = m_timer.getElapsedNanoseconds(); + + // Exponentially increasing iterations until we're confident in our timer resolution + if( elapsed < m_resolution ) { + m_iterationsToRun *= 10; + return true; + } + + getResultCapture().benchmarkEnded( { { m_name }, m_count, elapsed } ); + return false; + } + +} // end namespace Catch +// end catch_benchmark.cpp +// start catch_capture_matchers.cpp + +namespace Catch { + + using StringMatcher = Matchers::Impl::MatcherBase<std::string>; + + // This is the general overload that takes a any string matcher + // There is another overload, in catch_assertionhandler.h/.cpp, that only takes a string and infers + // the Equals matcher (so the header does not mention matchers) + void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef const& matcherString ) { + std::string exceptionMessage = Catch::translateActiveException(); + MatchExpr<std::string, StringMatcher const&> expr( exceptionMessage, matcher, matcherString ); + handler.handleExpr( expr ); + } + +} // namespace Catch +// end catch_capture_matchers.cpp +// start catch_commandline.cpp + +// start catch_commandline.h + +// start catch_clara.h + +// Use Catch's value for console width (store Clara's off to the side, if present) +#ifdef CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#undef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#endif +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH-1 + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#pragma clang diagnostic ignored "-Wshadow" +#endif + +// start clara.hpp +// Copyright 2017 Two Blue Cubes Ltd. All rights reserved. +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See https://github.com/philsquared/Clara for more details + +// Clara v1.1.5 + + +#ifndef CATCH_CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_CONFIG_CONSOLE_WIDTH 80 +#endif + +#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CLARA_CONFIG_CONSOLE_WIDTH +#endif + +#ifndef CLARA_CONFIG_OPTIONAL_TYPE +#ifdef __has_include +#if __has_include(<optional>) && __cplusplus >= 201703L +#include <optional> +#define CLARA_CONFIG_OPTIONAL_TYPE std::optional +#endif +#endif +#endif + +// ----------- #included from clara_textflow.hpp ----------- + +// TextFlowCpp +// +// A single-header library for wrapping and laying out basic text, by Phil Nash +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// This project is hosted at https://github.com/philsquared/textflowcpp + + +#include <cassert> +#include <ostream> +#include <sstream> +#include <vector> + +#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80 +#endif + +namespace Catch { +namespace clara { +namespace TextFlow { + +inline auto isWhitespace(char c) -> bool { + static std::string chars = " \t\n\r"; + return chars.find(c) != std::string::npos; +} +inline auto isBreakableBefore(char c) -> bool { + static std::string chars = "[({<|"; + return chars.find(c) != std::string::npos; +} +inline auto isBreakableAfter(char c) -> bool { + static std::string chars = "])}>.,:;*+-=&/\\"; + return chars.find(c) != std::string::npos; +} + +class Columns; + +class Column { + std::vector<std::string> m_strings; + size_t m_width = CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH; + size_t m_indent = 0; + size_t m_initialIndent = std::string::npos; + +public: + class iterator { + friend Column; + + Column const& m_column; + size_t m_stringIndex = 0; + size_t m_pos = 0; + + size_t m_len = 0; + size_t m_end = 0; + bool m_suffix = false; + + iterator(Column const& column, size_t stringIndex) + : m_column(column), + m_stringIndex(stringIndex) {} + + auto line() const -> std::string const& { return m_column.m_strings[m_stringIndex]; } + + auto isBoundary(size_t at) const -> bool { + assert(at > 0); + assert(at <= line().size()); + + return at == line().size() || + (isWhitespace(line()[at]) && !isWhitespace(line()[at - 1])) || + isBreakableBefore(line()[at]) || + isBreakableAfter(line()[at - 1]); + } + + void calcLength() { + assert(m_stringIndex < m_column.m_strings.size()); + + m_suffix = false; + auto width = m_column.m_width - indent(); + m_end = m_pos; + while (m_end < line().size() && line()[m_end] != '\n') + ++m_end; + + if (m_end < m_pos + width) { + m_len = m_end - m_pos; + } else { + size_t len = width; + while (len > 0 && !isBoundary(m_pos + len)) + --len; + while (len > 0 && isWhitespace(line()[m_pos + len - 1])) + --len; + + if (len > 0) { + m_len = len; + } else { + m_suffix = true; + m_len = width - 1; + } + } + } + + auto indent() const -> size_t { + auto initial = m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos; + return initial == std::string::npos ? m_column.m_indent : initial; + } + + auto addIndentAndSuffix(std::string const &plain) const -> std::string { + return std::string(indent(), ' ') + (m_suffix ? plain + "-" : plain); + } + + public: + using difference_type = std::ptrdiff_t; + using value_type = std::string; + using pointer = value_type * ; + using reference = value_type & ; + using iterator_category = std::forward_iterator_tag; + + explicit iterator(Column const& column) : m_column(column) { + assert(m_column.m_width > m_column.m_indent); + assert(m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent); + calcLength(); + if (m_len == 0) + m_stringIndex++; // Empty string + } + + auto operator *() const -> std::string { + assert(m_stringIndex < m_column.m_strings.size()); + assert(m_pos <= m_end); + return addIndentAndSuffix(line().substr(m_pos, m_len)); + } + + auto operator ++() -> iterator& { + m_pos += m_len; + if (m_pos < line().size() && line()[m_pos] == '\n') + m_pos += 1; + else + while (m_pos < line().size() && isWhitespace(line()[m_pos])) + ++m_pos; + + if (m_pos == line().size()) { + m_pos = 0; + ++m_stringIndex; + } + if (m_stringIndex < m_column.m_strings.size()) + calcLength(); + return *this; + } + auto operator ++(int) -> iterator { + iterator prev(*this); + operator++(); + return prev; + } + + auto operator ==(iterator const& other) const -> bool { + return + m_pos == other.m_pos && + m_stringIndex == other.m_stringIndex && + &m_column == &other.m_column; + } + auto operator !=(iterator const& other) const -> bool { + return !operator==(other); + } + }; + using const_iterator = iterator; + + explicit Column(std::string const& text) { m_strings.push_back(text); } + + auto width(size_t newWidth) -> Column& { + assert(newWidth > 0); + m_width = newWidth; + return *this; + } + auto indent(size_t newIndent) -> Column& { + m_indent = newIndent; + return *this; + } + auto initialIndent(size_t newIndent) -> Column& { + m_initialIndent = newIndent; + return *this; + } + + auto width() const -> size_t { return m_width; } + auto begin() const -> iterator { return iterator(*this); } + auto end() const -> iterator { return { *this, m_strings.size() }; } + + inline friend std::ostream& operator << (std::ostream& os, Column const& col) { + bool first = true; + for (auto line : col) { + if (first) + first = false; + else + os << "\n"; + os << line; + } + return os; + } + + auto operator + (Column const& other)->Columns; + + auto toString() const -> std::string { + std::ostringstream oss; + oss << *this; + return oss.str(); + } +}; + +class Spacer : public Column { + +public: + explicit Spacer(size_t spaceWidth) : Column("") { + width(spaceWidth); + } +}; + +class Columns { + std::vector<Column> m_columns; + +public: + + class iterator { + friend Columns; + struct EndTag {}; + + std::vector<Column> const& m_columns; + std::vector<Column::iterator> m_iterators; + size_t m_activeIterators; + + iterator(Columns const& columns, EndTag) + : m_columns(columns.m_columns), + m_activeIterators(0) { + m_iterators.reserve(m_columns.size()); + + for (auto const& col : m_columns) + m_iterators.push_back(col.end()); + } + + public: + using difference_type = std::ptrdiff_t; + using value_type = std::string; + using pointer = value_type * ; + using reference = value_type & ; + using iterator_category = std::forward_iterator_tag; + + explicit iterator(Columns const& columns) + : m_columns(columns.m_columns), + m_activeIterators(m_columns.size()) { + m_iterators.reserve(m_columns.size()); + + for (auto const& col : m_columns) + m_iterators.push_back(col.begin()); + } + + auto operator ==(iterator const& other) const -> bool { + return m_iterators == other.m_iterators; + } + auto operator !=(iterator const& other) const -> bool { + return m_iterators != other.m_iterators; + } + auto operator *() const -> std::string { + std::string row, padding; + + for (size_t i = 0; i < m_columns.size(); ++i) { + auto width = m_columns[i].width(); + if (m_iterators[i] != m_columns[i].end()) { + std::string col = *m_iterators[i]; + row += padding + col; + if (col.size() < width) + padding = std::string(width - col.size(), ' '); + else + padding = ""; + } else { + padding += std::string(width, ' '); + } + } + return row; + } + auto operator ++() -> iterator& { + for (size_t i = 0; i < m_columns.size(); ++i) { + if (m_iterators[i] != m_columns[i].end()) + ++m_iterators[i]; + } + return *this; + } + auto operator ++(int) -> iterator { + iterator prev(*this); + operator++(); + return prev; + } + }; + using const_iterator = iterator; + + auto begin() const -> iterator { return iterator(*this); } + auto end() const -> iterator { return { *this, iterator::EndTag() }; } + + auto operator += (Column const& col) -> Columns& { + m_columns.push_back(col); + return *this; + } + auto operator + (Column const& col) -> Columns { + Columns combined = *this; + combined += col; + return combined; + } + + inline friend std::ostream& operator << (std::ostream& os, Columns const& cols) { + + bool first = true; + for (auto line : cols) { + if (first) + first = false; + else + os << "\n"; + os << line; + } + return os; + } + + auto toString() const -> std::string { + std::ostringstream oss; + oss << *this; + return oss.str(); + } +}; + +inline auto Column::operator + (Column const& other) -> Columns { + Columns cols; + cols += *this; + cols += other; + return cols; +} +} + +} +} + +// ----------- end of #include from clara_textflow.hpp ----------- +// ........... back in clara.hpp + +#include <string> +#include <memory> +#include <set> +#include <algorithm> + +#if !defined(CATCH_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) ) +#define CATCH_PLATFORM_WINDOWS +#endif + +namespace Catch { namespace clara { +namespace detail { + + // Traits for extracting arg and return type of lambdas (for single argument lambdas) + template<typename L> + struct UnaryLambdaTraits : UnaryLambdaTraits<decltype( &L::operator() )> {}; + + template<typename ClassT, typename ReturnT, typename... Args> + struct UnaryLambdaTraits<ReturnT( ClassT::* )( Args... ) const> { + static const bool isValid = false; + }; + + template<typename ClassT, typename ReturnT, typename ArgT> + struct UnaryLambdaTraits<ReturnT( ClassT::* )( ArgT ) const> { + static const bool isValid = true; + using ArgType = typename std::remove_const<typename std::remove_reference<ArgT>::type>::type; + using ReturnType = ReturnT; + }; + + class TokenStream; + + // Transport for raw args (copied from main args, or supplied via init list for testing) + class Args { + friend TokenStream; + std::string m_exeName; + std::vector<std::string> m_args; + + public: + Args( int argc, char const* const* argv ) + : m_exeName(argv[0]), + m_args(argv + 1, argv + argc) {} + + Args( std::initializer_list<std::string> args ) + : m_exeName( *args.begin() ), + m_args( args.begin()+1, args.end() ) + {} + + auto exeName() const -> std::string { + return m_exeName; + } + }; + + // Wraps a token coming from a token stream. These may not directly correspond to strings as a single string + // may encode an option + its argument if the : or = form is used + enum class TokenType { + Option, Argument + }; + struct Token { + TokenType type; + std::string token; + }; + + inline auto isOptPrefix( char c ) -> bool { + return c == '-' +#ifdef CATCH_PLATFORM_WINDOWS + || c == '/' +#endif + ; + } + + // Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled + class TokenStream { + using Iterator = std::vector<std::string>::const_iterator; + Iterator it; + Iterator itEnd; + std::vector<Token> m_tokenBuffer; + + void loadBuffer() { + m_tokenBuffer.resize( 0 ); + + // Skip any empty strings + while( it != itEnd && it->empty() ) + ++it; + + if( it != itEnd ) { + auto const &next = *it; + if( isOptPrefix( next[0] ) ) { + auto delimiterPos = next.find_first_of( " :=" ); + if( delimiterPos != std::string::npos ) { + m_tokenBuffer.push_back( { TokenType::Option, next.substr( 0, delimiterPos ) } ); + m_tokenBuffer.push_back( { TokenType::Argument, next.substr( delimiterPos + 1 ) } ); + } else { + if( next[1] != '-' && next.size() > 2 ) { + std::string opt = "- "; + for( size_t i = 1; i < next.size(); ++i ) { + opt[1] = next[i]; + m_tokenBuffer.push_back( { TokenType::Option, opt } ); + } + } else { + m_tokenBuffer.push_back( { TokenType::Option, next } ); + } + } + } else { + m_tokenBuffer.push_back( { TokenType::Argument, next } ); + } + } + } + + public: + explicit TokenStream( Args const &args ) : TokenStream( args.m_args.begin(), args.m_args.end() ) {} + + TokenStream( Iterator it, Iterator itEnd ) : it( it ), itEnd( itEnd ) { + loadBuffer(); + } + + explicit operator bool() const { + return !m_tokenBuffer.empty() || it != itEnd; + } + + auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); } + + auto operator*() const -> Token { + assert( !m_tokenBuffer.empty() ); + return m_tokenBuffer.front(); + } + + auto operator->() const -> Token const * { + assert( !m_tokenBuffer.empty() ); + return &m_tokenBuffer.front(); + } + + auto operator++() -> TokenStream & { + if( m_tokenBuffer.size() >= 2 ) { + m_tokenBuffer.erase( m_tokenBuffer.begin() ); + } else { + if( it != itEnd ) + ++it; + loadBuffer(); + } + return *this; + } + }; + + class ResultBase { + public: + enum Type { + Ok, LogicError, RuntimeError + }; + + protected: + ResultBase( Type type ) : m_type( type ) {} + virtual ~ResultBase() = default; + + virtual void enforceOk() const = 0; + + Type m_type; + }; + + template<typename T> + class ResultValueBase : public ResultBase { + public: + auto value() const -> T const & { + enforceOk(); + return m_value; + } + + protected: + ResultValueBase( Type type ) : ResultBase( type ) {} + + ResultValueBase( ResultValueBase const &other ) : ResultBase( other ) { + if( m_type == ResultBase::Ok ) + new( &m_value ) T( other.m_value ); + } + + ResultValueBase( Type, T const &value ) : ResultBase( Ok ) { + new( &m_value ) T( value ); + } + + auto operator=( ResultValueBase const &other ) -> ResultValueBase & { + if( m_type == ResultBase::Ok ) + m_value.~T(); + ResultBase::operator=(other); + if( m_type == ResultBase::Ok ) + new( &m_value ) T( other.m_value ); + return *this; + } + + ~ResultValueBase() override { + if( m_type == Ok ) + m_value.~T(); + } + + union { + T m_value; + }; + }; + + template<> + class ResultValueBase<void> : public ResultBase { + protected: + using ResultBase::ResultBase; + }; + + template<typename T = void> + class BasicResult : public ResultValueBase<T> { + public: + template<typename U> + explicit BasicResult( BasicResult<U> const &other ) + : ResultValueBase<T>( other.type() ), + m_errorMessage( other.errorMessage() ) + { + assert( type() != ResultBase::Ok ); + } + + template<typename U> + static auto ok( U const &value ) -> BasicResult { return { ResultBase::Ok, value }; } + static auto ok() -> BasicResult { return { ResultBase::Ok }; } + static auto logicError( std::string const &message ) -> BasicResult { return { ResultBase::LogicError, message }; } + static auto runtimeError( std::string const &message ) -> BasicResult { return { ResultBase::RuntimeError, message }; } + + explicit operator bool() const { return m_type == ResultBase::Ok; } + auto type() const -> ResultBase::Type { return m_type; } + auto errorMessage() const -> std::string { return m_errorMessage; } + + protected: + void enforceOk() const override { + + // Errors shouldn't reach this point, but if they do + // the actual error message will be in m_errorMessage + assert( m_type != ResultBase::LogicError ); + assert( m_type != ResultBase::RuntimeError ); + if( m_type != ResultBase::Ok ) + std::abort(); + } + + std::string m_errorMessage; // Only populated if resultType is an error + + BasicResult( ResultBase::Type type, std::string const &message ) + : ResultValueBase<T>(type), + m_errorMessage(message) + { + assert( m_type != ResultBase::Ok ); + } + + using ResultValueBase<T>::ResultValueBase; + using ResultBase::m_type; + }; + + enum class ParseResultType { + Matched, NoMatch, ShortCircuitAll, ShortCircuitSame + }; + + class ParseState { + public: + + ParseState( ParseResultType type, TokenStream const &remainingTokens ) + : m_type(type), + m_remainingTokens( remainingTokens ) + {} + + auto type() const -> ParseResultType { return m_type; } + auto remainingTokens() const -> TokenStream { return m_remainingTokens; } + + private: + ParseResultType m_type; + TokenStream m_remainingTokens; + }; + + using Result = BasicResult<void>; + using ParserResult = BasicResult<ParseResultType>; + using InternalParseResult = BasicResult<ParseState>; + + struct HelpColumns { + std::string left; + std::string right; + }; + + template<typename T> + inline auto convertInto( std::string const &source, T& target ) -> ParserResult { + std::stringstream ss; + ss << source; + ss >> target; + if( ss.fail() ) + return ParserResult::runtimeError( "Unable to convert '" + source + "' to destination type" ); + else + return ParserResult::ok( ParseResultType::Matched ); + } + inline auto convertInto( std::string const &source, std::string& target ) -> ParserResult { + target = source; + return ParserResult::ok( ParseResultType::Matched ); + } + inline auto convertInto( std::string const &source, bool &target ) -> ParserResult { + std::string srcLC = source; + std::transform( srcLC.begin(), srcLC.end(), srcLC.begin(), []( char c ) { return static_cast<char>( ::tolower(c) ); } ); + if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" || srcLC == "on") + target = true; + else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" || srcLC == "off") + target = false; + else + return ParserResult::runtimeError( "Expected a boolean value but did not recognise: '" + source + "'" ); + return ParserResult::ok( ParseResultType::Matched ); + } +#ifdef CLARA_CONFIG_OPTIONAL_TYPE + template<typename T> + inline auto convertInto( std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE<T>& target ) -> ParserResult { + T temp; + auto result = convertInto( source, temp ); + if( result ) + target = std::move(temp); + return result; + } +#endif // CLARA_CONFIG_OPTIONAL_TYPE + + struct NonCopyable { + NonCopyable() = default; + NonCopyable( NonCopyable const & ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable &operator=( NonCopyable const & ) = delete; + NonCopyable &operator=( NonCopyable && ) = delete; + }; + + struct BoundRef : NonCopyable { + virtual ~BoundRef() = default; + virtual auto isContainer() const -> bool { return false; } + virtual auto isFlag() const -> bool { return false; } + }; + struct BoundValueRefBase : BoundRef { + virtual auto setValue( std::string const &arg ) -> ParserResult = 0; + }; + struct BoundFlagRefBase : BoundRef { + virtual auto setFlag( bool flag ) -> ParserResult = 0; + virtual auto isFlag() const -> bool { return true; } + }; + + template<typename T> + struct BoundValueRef : BoundValueRefBase { + T &m_ref; + + explicit BoundValueRef( T &ref ) : m_ref( ref ) {} + + auto setValue( std::string const &arg ) -> ParserResult override { + return convertInto( arg, m_ref ); + } + }; + + template<typename T> + struct BoundValueRef<std::vector<T>> : BoundValueRefBase { + std::vector<T> &m_ref; + + explicit BoundValueRef( std::vector<T> &ref ) : m_ref( ref ) {} + + auto isContainer() const -> bool override { return true; } + + auto setValue( std::string const &arg ) -> ParserResult override { + T temp; + auto result = convertInto( arg, temp ); + if( result ) + m_ref.push_back( temp ); + return result; + } + }; + + struct BoundFlagRef : BoundFlagRefBase { + bool &m_ref; + + explicit BoundFlagRef( bool &ref ) : m_ref( ref ) {} + + auto setFlag( bool flag ) -> ParserResult override { + m_ref = flag; + return ParserResult::ok( ParseResultType::Matched ); + } + }; + + template<typename ReturnType> + struct LambdaInvoker { + static_assert( std::is_same<ReturnType, ParserResult>::value, "Lambda must return void or clara::ParserResult" ); + + template<typename L, typename ArgType> + static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { + return lambda( arg ); + } + }; + + template<> + struct LambdaInvoker<void> { + template<typename L, typename ArgType> + static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { + lambda( arg ); + return ParserResult::ok( ParseResultType::Matched ); + } + }; + + template<typename ArgType, typename L> + inline auto invokeLambda( L const &lambda, std::string const &arg ) -> ParserResult { + ArgType temp{}; + auto result = convertInto( arg, temp ); + return !result + ? result + : LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke( lambda, temp ); + } + + template<typename L> + struct BoundLambda : BoundValueRefBase { + L m_lambda; + + static_assert( UnaryLambdaTraits<L>::isValid, "Supplied lambda must take exactly one argument" ); + explicit BoundLambda( L const &lambda ) : m_lambda( lambda ) {} + + auto setValue( std::string const &arg ) -> ParserResult override { + return invokeLambda<typename UnaryLambdaTraits<L>::ArgType>( m_lambda, arg ); + } + }; + + template<typename L> + struct BoundFlagLambda : BoundFlagRefBase { + L m_lambda; + + static_assert( UnaryLambdaTraits<L>::isValid, "Supplied lambda must take exactly one argument" ); + static_assert( std::is_same<typename UnaryLambdaTraits<L>::ArgType, bool>::value, "flags must be boolean" ); + + explicit BoundFlagLambda( L const &lambda ) : m_lambda( lambda ) {} + + auto setFlag( bool flag ) -> ParserResult override { + return LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke( m_lambda, flag ); + } + }; + + enum class Optionality { Optional, Required }; + + struct Parser; + + class ParserBase { + public: + virtual ~ParserBase() = default; + virtual auto validate() const -> Result { return Result::ok(); } + virtual auto parse( std::string const& exeName, TokenStream const &tokens) const -> InternalParseResult = 0; + virtual auto cardinality() const -> size_t { return 1; } + + auto parse( Args const &args ) const -> InternalParseResult { + return parse( args.exeName(), TokenStream( args ) ); + } + }; + + template<typename DerivedT> + class ComposableParserImpl : public ParserBase { + public: + template<typename T> + auto operator|( T const &other ) const -> Parser; + + template<typename T> + auto operator+( T const &other ) const -> Parser; + }; + + // Common code and state for Args and Opts + template<typename DerivedT> + class ParserRefImpl : public ComposableParserImpl<DerivedT> { + protected: + Optionality m_optionality = Optionality::Optional; + std::shared_ptr<BoundRef> m_ref; + std::string m_hint; + std::string m_description; + + explicit ParserRefImpl( std::shared_ptr<BoundRef> const &ref ) : m_ref( ref ) {} + + public: + template<typename T> + ParserRefImpl( T &ref, std::string const &hint ) + : m_ref( std::make_shared<BoundValueRef<T>>( ref ) ), + m_hint( hint ) + {} + + template<typename LambdaT> + ParserRefImpl( LambdaT const &ref, std::string const &hint ) + : m_ref( std::make_shared<BoundLambda<LambdaT>>( ref ) ), + m_hint(hint) + {} + + auto operator()( std::string const &description ) -> DerivedT & { + m_description = description; + return static_cast<DerivedT &>( *this ); + } + + auto optional() -> DerivedT & { + m_optionality = Optionality::Optional; + return static_cast<DerivedT &>( *this ); + }; + + auto required() -> DerivedT & { + m_optionality = Optionality::Required; + return static_cast<DerivedT &>( *this ); + }; + + auto isOptional() const -> bool { + return m_optionality == Optionality::Optional; + } + + auto cardinality() const -> size_t override { + if( m_ref->isContainer() ) + return 0; + else + return 1; + } + + auto hint() const -> std::string { return m_hint; } + }; + + class ExeName : public ComposableParserImpl<ExeName> { + std::shared_ptr<std::string> m_name; + std::shared_ptr<BoundValueRefBase> m_ref; + + template<typename LambdaT> + static auto makeRef(LambdaT const &lambda) -> std::shared_ptr<BoundValueRefBase> { + return std::make_shared<BoundLambda<LambdaT>>( lambda) ; + } + + public: + ExeName() : m_name( std::make_shared<std::string>( "<executable>" ) ) {} + + explicit ExeName( std::string &ref ) : ExeName() { + m_ref = std::make_shared<BoundValueRef<std::string>>( ref ); + } + + template<typename LambdaT> + explicit ExeName( LambdaT const& lambda ) : ExeName() { + m_ref = std::make_shared<BoundLambda<LambdaT>>( lambda ); + } + + // The exe name is not parsed out of the normal tokens, but is handled specially + auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { + return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); + } + + auto name() const -> std::string { return *m_name; } + auto set( std::string const& newName ) -> ParserResult { + + auto lastSlash = newName.find_last_of( "\\/" ); + auto filename = ( lastSlash == std::string::npos ) + ? newName + : newName.substr( lastSlash+1 ); + + *m_name = filename; + if( m_ref ) + return m_ref->setValue( filename ); + else + return ParserResult::ok( ParseResultType::Matched ); + } + }; + + class Arg : public ParserRefImpl<Arg> { + public: + using ParserRefImpl::ParserRefImpl; + + auto parse( std::string const &, TokenStream const &tokens ) const -> InternalParseResult override { + auto validationResult = validate(); + if( !validationResult ) + return InternalParseResult( validationResult ); + + auto remainingTokens = tokens; + auto const &token = *remainingTokens; + if( token.type != TokenType::Argument ) + return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); + + assert( !m_ref->isFlag() ); + auto valueRef = static_cast<detail::BoundValueRefBase*>( m_ref.get() ); + + auto result = valueRef->setValue( remainingTokens->token ); + if( !result ) + return InternalParseResult( result ); + else + return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); + } + }; + + inline auto normaliseOpt( std::string const &optName ) -> std::string { +#ifdef CATCH_PLATFORM_WINDOWS + if( optName[0] == '/' ) + return "-" + optName.substr( 1 ); + else +#endif + return optName; + } + + class Opt : public ParserRefImpl<Opt> { + protected: + std::vector<std::string> m_optNames; + + public: + template<typename LambdaT> + explicit Opt( LambdaT const &ref ) : ParserRefImpl( std::make_shared<BoundFlagLambda<LambdaT>>( ref ) ) {} + + explicit Opt( bool &ref ) : ParserRefImpl( std::make_shared<BoundFlagRef>( ref ) ) {} + + template<typename LambdaT> + Opt( LambdaT const &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} + + template<typename T> + Opt( T &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} + + auto operator[]( std::string const &optName ) -> Opt & { + m_optNames.push_back( optName ); + return *this; + } + + auto getHelpColumns() const -> std::vector<HelpColumns> { + std::ostringstream oss; + bool first = true; + for( auto const &opt : m_optNames ) { + if (first) + first = false; + else + oss << ", "; + oss << opt; + } + if( !m_hint.empty() ) + oss << " <" << m_hint << ">"; + return { { oss.str(), m_description } }; + } + + auto isMatch( std::string const &optToken ) const -> bool { + auto normalisedToken = normaliseOpt( optToken ); + for( auto const &name : m_optNames ) { + if( normaliseOpt( name ) == normalisedToken ) + return true; + } + return false; + } + + using ParserBase::parse; + + auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { + auto validationResult = validate(); + if( !validationResult ) + return InternalParseResult( validationResult ); + + auto remainingTokens = tokens; + if( remainingTokens && remainingTokens->type == TokenType::Option ) { + auto const &token = *remainingTokens; + if( isMatch(token.token ) ) { + if( m_ref->isFlag() ) { + auto flagRef = static_cast<detail::BoundFlagRefBase*>( m_ref.get() ); + auto result = flagRef->setFlag( true ); + if( !result ) + return InternalParseResult( result ); + if( result.value() == ParseResultType::ShortCircuitAll ) + return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); + } else { + auto valueRef = static_cast<detail::BoundValueRefBase*>( m_ref.get() ); + ++remainingTokens; + if( !remainingTokens ) + return InternalParseResult::runtimeError( "Expected argument following " + token.token ); + auto const &argToken = *remainingTokens; + if( argToken.type != TokenType::Argument ) + return InternalParseResult::runtimeError( "Expected argument following " + token.token ); + auto result = valueRef->setValue( argToken.token ); + if( !result ) + return InternalParseResult( result ); + if( result.value() == ParseResultType::ShortCircuitAll ) + return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); + } + return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); + } + } + return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); + } + + auto validate() const -> Result override { + if( m_optNames.empty() ) + return Result::logicError( "No options supplied to Opt" ); + for( auto const &name : m_optNames ) { + if( name.empty() ) + return Result::logicError( "Option name cannot be empty" ); +#ifdef CATCH_PLATFORM_WINDOWS + if( name[0] != '-' && name[0] != '/' ) + return Result::logicError( "Option name must begin with '-' or '/'" ); +#else + if( name[0] != '-' ) + return Result::logicError( "Option name must begin with '-'" ); +#endif + } + return ParserRefImpl::validate(); + } + }; + + struct Help : Opt { + Help( bool &showHelpFlag ) + : Opt([&]( bool flag ) { + showHelpFlag = flag; + return ParserResult::ok( ParseResultType::ShortCircuitAll ); + }) + { + static_cast<Opt &>( *this ) + ("display usage information") + ["-?"]["-h"]["--help"] + .optional(); + } + }; + + struct Parser : ParserBase { + + mutable ExeName m_exeName; + std::vector<Opt> m_options; + std::vector<Arg> m_args; + + auto operator|=( ExeName const &exeName ) -> Parser & { + m_exeName = exeName; + return *this; + } + + auto operator|=( Arg const &arg ) -> Parser & { + m_args.push_back(arg); + return *this; + } + + auto operator|=( Opt const &opt ) -> Parser & { + m_options.push_back(opt); + return *this; + } + + auto operator|=( Parser const &other ) -> Parser & { + m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end()); + m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end()); + return *this; + } + + template<typename T> + auto operator|( T const &other ) const -> Parser { + return Parser( *this ) |= other; + } + + // Forward deprecated interface with '+' instead of '|' + template<typename T> + auto operator+=( T const &other ) -> Parser & { return operator|=( other ); } + template<typename T> + auto operator+( T const &other ) const -> Parser { return operator|( other ); } + + auto getHelpColumns() const -> std::vector<HelpColumns> { + std::vector<HelpColumns> cols; + for (auto const &o : m_options) { + auto childCols = o.getHelpColumns(); + cols.insert( cols.end(), childCols.begin(), childCols.end() ); + } + return cols; + } + + void writeToStream( std::ostream &os ) const { + if (!m_exeName.name().empty()) { + os << "usage:\n" << " " << m_exeName.name() << " "; + bool required = true, first = true; + for( auto const &arg : m_args ) { + if (first) + first = false; + else + os << " "; + if( arg.isOptional() && required ) { + os << "["; + required = false; + } + os << "<" << arg.hint() << ">"; + if( arg.cardinality() == 0 ) + os << " ... "; + } + if( !required ) + os << "]"; + if( !m_options.empty() ) + os << " options"; + os << "\n\nwhere options are:" << std::endl; + } + + auto rows = getHelpColumns(); + size_t consoleWidth = CATCH_CLARA_CONFIG_CONSOLE_WIDTH; + size_t optWidth = 0; + for( auto const &cols : rows ) + optWidth = (std::max)(optWidth, cols.left.size() + 2); + + optWidth = (std::min)(optWidth, consoleWidth/2); + + for( auto const &cols : rows ) { + auto row = + TextFlow::Column( cols.left ).width( optWidth ).indent( 2 ) + + TextFlow::Spacer(4) + + TextFlow::Column( cols.right ).width( consoleWidth - 7 - optWidth ); + os << row << std::endl; + } + } + + friend auto operator<<( std::ostream &os, Parser const &parser ) -> std::ostream& { + parser.writeToStream( os ); + return os; + } + + auto validate() const -> Result override { + for( auto const &opt : m_options ) { + auto result = opt.validate(); + if( !result ) + return result; + } + for( auto const &arg : m_args ) { + auto result = arg.validate(); + if( !result ) + return result; + } + return Result::ok(); + } + + using ParserBase::parse; + + auto parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override { + + struct ParserInfo { + ParserBase const* parser = nullptr; + size_t count = 0; + }; + const size_t totalParsers = m_options.size() + m_args.size(); + assert( totalParsers < 512 ); + // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do + ParserInfo parseInfos[512]; + + { + size_t i = 0; + for (auto const &opt : m_options) parseInfos[i++].parser = &opt; + for (auto const &arg : m_args) parseInfos[i++].parser = &arg; + } + + m_exeName.set( exeName ); + + auto result = InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); + while( result.value().remainingTokens() ) { + bool tokenParsed = false; + + for( size_t i = 0; i < totalParsers; ++i ) { + auto& parseInfo = parseInfos[i]; + if( parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality() ) { + result = parseInfo.parser->parse(exeName, result.value().remainingTokens()); + if (!result) + return result; + if (result.value().type() != ParseResultType::NoMatch) { + tokenParsed = true; + ++parseInfo.count; + break; + } + } + } + + if( result.value().type() == ParseResultType::ShortCircuitAll ) + return result; + if( !tokenParsed ) + return InternalParseResult::runtimeError( "Unrecognised token: " + result.value().remainingTokens()->token ); + } + // !TBD Check missing required options + return result; + } + }; + + template<typename DerivedT> + template<typename T> + auto ComposableParserImpl<DerivedT>::operator|( T const &other ) const -> Parser { + return Parser() | static_cast<DerivedT const &>( *this ) | other; + } +} // namespace detail + +// A Combined parser +using detail::Parser; + +// A parser for options +using detail::Opt; + +// A parser for arguments +using detail::Arg; + +// Wrapper for argc, argv from main() +using detail::Args; + +// Specifies the name of the executable +using detail::ExeName; + +// Convenience wrapper for option parser that specifies the help option +using detail::Help; + +// enum of result types from a parse +using detail::ParseResultType; + +// Result type for parser operation +using detail::ParserResult; + +}} // namespace Catch::clara + +// end clara.hpp +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// Restore Clara's value for console width, if present +#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#endif + +// end catch_clara.h +namespace Catch { + + clara::Parser makeCommandLineParser( ConfigData& config ); + +} // end namespace Catch + +// end catch_commandline.h +#include <fstream> +#include <ctime> + +namespace Catch { + + clara::Parser makeCommandLineParser( ConfigData& config ) { + + using namespace clara; + + auto const setWarning = [&]( std::string const& warning ) { + auto warningSet = [&]() { + if( warning == "NoAssertions" ) + return WarnAbout::NoAssertions; + + if ( warning == "NoTests" ) + return WarnAbout::NoTests; + + return WarnAbout::Nothing; + }(); + + if (warningSet == WarnAbout::Nothing) + return ParserResult::runtimeError( "Unrecognised warning: '" + warning + "'" ); + config.warnings = static_cast<WarnAbout::What>( config.warnings | warningSet ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const loadTestNamesFromFile = [&]( std::string const& filename ) { + std::ifstream f( filename.c_str() ); + if( !f.is_open() ) + return ParserResult::runtimeError( "Unable to load input file: '" + filename + "'" ); + + std::string line; + while( std::getline( f, line ) ) { + line = trim(line); + if( !line.empty() && !startsWith( line, '#' ) ) { + if( !startsWith( line, '"' ) ) + line = '"' + line + '"'; + config.testsOrTags.push_back( line + ',' ); + } + } + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setTestOrder = [&]( std::string const& order ) { + if( startsWith( "declared", order ) ) + config.runOrder = RunTests::InDeclarationOrder; + else if( startsWith( "lexical", order ) ) + config.runOrder = RunTests::InLexicographicalOrder; + else if( startsWith( "random", order ) ) + config.runOrder = RunTests::InRandomOrder; + else + return clara::ParserResult::runtimeError( "Unrecognised ordering: '" + order + "'" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setRngSeed = [&]( std::string const& seed ) { + if( seed != "time" ) + return clara::detail::convertInto( seed, config.rngSeed ); + config.rngSeed = static_cast<unsigned int>( std::time(nullptr) ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setColourUsage = [&]( std::string const& useColour ) { + auto mode = toLower( useColour ); + + if( mode == "yes" ) + config.useColour = UseColour::Yes; + else if( mode == "no" ) + config.useColour = UseColour::No; + else if( mode == "auto" ) + config.useColour = UseColour::Auto; + else + return ParserResult::runtimeError( "colour mode must be one of: auto, yes or no. '" + useColour + "' not recognised" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setWaitForKeypress = [&]( std::string const& keypress ) { + auto keypressLc = toLower( keypress ); + if( keypressLc == "start" ) + config.waitForKeypress = WaitForKeypress::BeforeStart; + else if( keypressLc == "exit" ) + config.waitForKeypress = WaitForKeypress::BeforeExit; + else if( keypressLc == "both" ) + config.waitForKeypress = WaitForKeypress::BeforeStartAndExit; + else + return ParserResult::runtimeError( "keypress argument must be one of: start, exit or both. '" + keypress + "' not recognised" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setVerbosity = [&]( std::string const& verbosity ) { + auto lcVerbosity = toLower( verbosity ); + if( lcVerbosity == "quiet" ) + config.verbosity = Verbosity::Quiet; + else if( lcVerbosity == "normal" ) + config.verbosity = Verbosity::Normal; + else if( lcVerbosity == "high" ) + config.verbosity = Verbosity::High; + else + return ParserResult::runtimeError( "Unrecognised verbosity, '" + verbosity + "'" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setReporter = [&]( std::string const& reporter ) { + IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); + + auto lcReporter = toLower( reporter ); + auto result = factories.find( lcReporter ); + + if( factories.end() != result ) + config.reporterName = lcReporter; + else + return ParserResult::runtimeError( "Unrecognized reporter, '" + reporter + "'. Check available with --list-reporters" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + + auto cli + = ExeName( config.processName ) + | Help( config.showHelp ) + | Opt( config.listTests ) + ["-l"]["--list-tests"] + ( "list all/matching test cases" ) + | Opt( config.listTags ) + ["-t"]["--list-tags"] + ( "list all/matching tags" ) + | Opt( config.showSuccessfulTests ) + ["-s"]["--success"] + ( "include successful tests in output" ) + | Opt( config.shouldDebugBreak ) + ["-b"]["--break"] + ( "break into debugger on failure" ) + | Opt( config.noThrow ) + ["-e"]["--nothrow"] + ( "skip exception tests" ) + | Opt( config.showInvisibles ) + ["-i"]["--invisibles"] + ( "show invisibles (tabs, newlines)" ) + | Opt( config.outputFilename, "filename" ) + ["-o"]["--out"] + ( "output filename" ) + | Opt( setReporter, "name" ) + ["-r"]["--reporter"] + ( "reporter to use (defaults to console)" ) + | Opt( config.name, "name" ) + ["-n"]["--name"] + ( "suite name" ) + | Opt( [&]( bool ){ config.abortAfter = 1; } ) + ["-a"]["--abort"] + ( "abort at first failure" ) + | Opt( [&]( int x ){ config.abortAfter = x; }, "no. failures" ) + ["-x"]["--abortx"] + ( "abort after x failures" ) + | Opt( setWarning, "warning name" ) + ["-w"]["--warn"] + ( "enable warnings" ) + | Opt( [&]( bool flag ) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, "yes|no" ) + ["-d"]["--durations"] + ( "show test durations" ) + | Opt( loadTestNamesFromFile, "filename" ) + ["-f"]["--input-file"] + ( "load test names to run from a file" ) + | Opt( config.filenamesAsTags ) + ["-#"]["--filenames-as-tags"] + ( "adds a tag for the filename" ) + | Opt( config.sectionsToRun, "section name" ) + ["-c"]["--section"] + ( "specify section to run" ) + | Opt( setVerbosity, "quiet|normal|high" ) + ["-v"]["--verbosity"] + ( "set output verbosity" ) + | Opt( config.listTestNamesOnly ) + ["--list-test-names-only"] + ( "list all/matching test cases names only" ) + | Opt( config.listReporters ) + ["--list-reporters"] + ( "list all reporters" ) + | Opt( setTestOrder, "decl|lex|rand" ) + ["--order"] + ( "test case order (defaults to decl)" ) + | Opt( setRngSeed, "'time'|number" ) + ["--rng-seed"] + ( "set a specific seed for random numbers" ) + | Opt( setColourUsage, "yes|no" ) + ["--use-colour"] + ( "should output be colourised" ) + | Opt( config.libIdentify ) + ["--libidentify"] + ( "report name and version according to libidentify standard" ) + | Opt( setWaitForKeypress, "start|exit|both" ) + ["--wait-for-keypress"] + ( "waits for a keypress before exiting" ) + | Opt( config.benchmarkResolutionMultiple, "multiplier" ) + ["--benchmark-resolution-multiple"] + ( "multiple of clock resolution to run benchmarks" ) + + | Arg( config.testsOrTags, "test name|pattern|tags" ) + ( "which test or tests to use" ); + + return cli; + } + +} // end namespace Catch +// end catch_commandline.cpp +// start catch_common.cpp + +#include <cstring> +#include <ostream> + +namespace Catch { + + bool SourceLineInfo::empty() const noexcept { + return file[0] == '\0'; + } + bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const noexcept { + return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0); + } + bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const noexcept { + // We can assume that the same file will usually have the same pointer. + // Thus, if the pointers are the same, there is no point in calling the strcmp + return line < other.line || ( line == other.line && file != other.file && (std::strcmp(file, other.file) < 0)); + } + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { +#ifndef __GNUG__ + os << info.file << '(' << info.line << ')'; +#else + os << info.file << ':' << info.line; +#endif + return os; + } + + std::string StreamEndStop::operator+() const { + return std::string(); + } + + NonCopyable::NonCopyable() = default; + NonCopyable::~NonCopyable() = default; + +} +// end catch_common.cpp +// start catch_config.cpp + +namespace Catch { + + Config::Config( ConfigData const& data ) + : m_data( data ), + m_stream( openStream() ) + { + TestSpecParser parser(ITagAliasRegistry::get()); + if (data.testsOrTags.empty()) { + parser.parse("~[.]"); // All not hidden tests + } + else { + m_hasTestFilters = true; + for( auto const& testOrTags : data.testsOrTags ) + parser.parse( testOrTags ); + } + m_testSpec = parser.testSpec(); + } + + std::string const& Config::getFilename() const { + return m_data.outputFilename ; + } + + bool Config::listTests() const { return m_data.listTests; } + bool Config::listTestNamesOnly() const { return m_data.listTestNamesOnly; } + bool Config::listTags() const { return m_data.listTags; } + bool Config::listReporters() const { return m_data.listReporters; } + + std::string Config::getProcessName() const { return m_data.processName; } + std::string const& Config::getReporterName() const { return m_data.reporterName; } + + std::vector<std::string> const& Config::getTestsOrTags() const { return m_data.testsOrTags; } + std::vector<std::string> const& Config::getSectionsToRun() const { return m_data.sectionsToRun; } + + TestSpec const& Config::testSpec() const { return m_testSpec; } + bool Config::hasTestFilters() const { return m_hasTestFilters; } + + bool Config::showHelp() const { return m_data.showHelp; } + + // IConfig interface + bool Config::allowThrows() const { return !m_data.noThrow; } + std::ostream& Config::stream() const { return m_stream->stream(); } + std::string Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } + bool Config::includeSuccessfulResults() const { return m_data.showSuccessfulTests; } + bool Config::warnAboutMissingAssertions() const { return !!(m_data.warnings & WarnAbout::NoAssertions); } + bool Config::warnAboutNoTests() const { return !!(m_data.warnings & WarnAbout::NoTests); } + ShowDurations::OrNot Config::showDurations() const { return m_data.showDurations; } + RunTests::InWhatOrder Config::runOrder() const { return m_data.runOrder; } + unsigned int Config::rngSeed() const { return m_data.rngSeed; } + int Config::benchmarkResolutionMultiple() const { return m_data.benchmarkResolutionMultiple; } + UseColour::YesOrNo Config::useColour() const { return m_data.useColour; } + bool Config::shouldDebugBreak() const { return m_data.shouldDebugBreak; } + int Config::abortAfter() const { return m_data.abortAfter; } + bool Config::showInvisibles() const { return m_data.showInvisibles; } + Verbosity Config::verbosity() const { return m_data.verbosity; } + + IStream const* Config::openStream() { + return Catch::makeStream(m_data.outputFilename); + } + +} // end namespace Catch +// end catch_config.cpp +// start catch_console_colour.cpp + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +// start catch_errno_guard.h + +namespace Catch { + + class ErrnoGuard { + public: + ErrnoGuard(); + ~ErrnoGuard(); + private: + int m_oldErrno; + }; + +} + +// end catch_errno_guard.h +#include <sstream> + +namespace Catch { + namespace { + + struct IColourImpl { + virtual ~IColourImpl() = default; + virtual void use( Colour::Code _colourCode ) = 0; + }; + + struct NoColourImpl : IColourImpl { + void use( Colour::Code ) {} + + static IColourImpl* instance() { + static NoColourImpl s_instance; + return &s_instance; + } + }; + + } // anon namespace +} // namespace Catch + +#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) +# ifdef CATCH_PLATFORM_WINDOWS +# define CATCH_CONFIG_COLOUR_WINDOWS +# else +# define CATCH_CONFIG_COLOUR_ANSI +# endif +#endif + +#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// + +namespace Catch { +namespace { + + class Win32ColourImpl : public IColourImpl { + public: + Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) + { + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); + originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY ); + originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY ); + } + + virtual void use( Colour::Code _colourCode ) override { + switch( _colourCode ) { + case Colour::None: return setTextAttribute( originalForegroundAttributes ); + case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::Red: return setTextAttribute( FOREGROUND_RED ); + case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); + case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); + case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); + case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); + case Colour::Grey: return setTextAttribute( 0 ); + + case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); + case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); + case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); + case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::BrightYellow: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN ); + + case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); + + default: + CATCH_ERROR( "Unknown colour requested" ); + } + } + + private: + void setTextAttribute( WORD _textAttribute ) { + SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes ); + } + HANDLE stdoutHandle; + WORD originalForegroundAttributes; + WORD originalBackgroundAttributes; + }; + + IColourImpl* platformColourInstance() { + static Win32ColourImpl s_instance; + + IConfigPtr config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = UseColour::Yes; + return colourMode == UseColour::Yes + ? &s_instance + : NoColourImpl::instance(); + } + +} // end anon namespace +} // end namespace Catch + +#elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// + +#include <unistd.h> + +namespace Catch { +namespace { + + // use POSIX/ ANSI console terminal codes + // Thanks to Adam Strzelecki for original contribution + // (http://github.com/nanoant) + // https://github.com/philsquared/Catch/pull/131 + class PosixColourImpl : public IColourImpl { + public: + virtual void use( Colour::Code _colourCode ) override { + switch( _colourCode ) { + case Colour::None: + case Colour::White: return setColour( "[0m" ); + case Colour::Red: return setColour( "[0;31m" ); + case Colour::Green: return setColour( "[0;32m" ); + case Colour::Blue: return setColour( "[0;34m" ); + case Colour::Cyan: return setColour( "[0;36m" ); + case Colour::Yellow: return setColour( "[0;33m" ); + case Colour::Grey: return setColour( "[1;30m" ); + + case Colour::LightGrey: return setColour( "[0;37m" ); + case Colour::BrightRed: return setColour( "[1;31m" ); + case Colour::BrightGreen: return setColour( "[1;32m" ); + case Colour::BrightWhite: return setColour( "[1;37m" ); + case Colour::BrightYellow: return setColour( "[1;33m" ); + + case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); + default: CATCH_INTERNAL_ERROR( "Unknown colour requested" ); + } + } + static IColourImpl* instance() { + static PosixColourImpl s_instance; + return &s_instance; + } + + private: + void setColour( const char* _escapeCode ) { + Catch::cout() << '\033' << _escapeCode; + } + }; + + bool useColourOnPlatform() { + return +#ifdef CATCH_PLATFORM_MAC + !isDebuggerActive() && +#endif +#if !(defined(__DJGPP__) && defined(__STRICT_ANSI__)) + isatty(STDOUT_FILENO) +#else + false +#endif + ; + } + IColourImpl* platformColourInstance() { + ErrnoGuard guard; + IConfigPtr config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = useColourOnPlatform() + ? UseColour::Yes + : UseColour::No; + return colourMode == UseColour::Yes + ? PosixColourImpl::instance() + : NoColourImpl::instance(); + } + +} // end anon namespace +} // end namespace Catch + +#else // not Windows or ANSI /////////////////////////////////////////////// + +namespace Catch { + + static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } + +} // end namespace Catch + +#endif // Windows/ ANSI/ None + +namespace Catch { + + Colour::Colour( Code _colourCode ) { use( _colourCode ); } + Colour::Colour( Colour&& rhs ) noexcept { + m_moved = rhs.m_moved; + rhs.m_moved = true; + } + Colour& Colour::operator=( Colour&& rhs ) noexcept { + m_moved = rhs.m_moved; + rhs.m_moved = true; + return *this; + } + + Colour::~Colour(){ if( !m_moved ) use( None ); } + + void Colour::use( Code _colourCode ) { + static IColourImpl* impl = platformColourInstance(); + impl->use( _colourCode ); + } + + std::ostream& operator << ( std::ostream& os, Colour const& ) { + return os; + } + +} // end namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +// end catch_console_colour.cpp +// start catch_context.cpp + +namespace Catch { + + class Context : public IMutableContext, NonCopyable { + + public: // IContext + virtual IResultCapture* getResultCapture() override { + return m_resultCapture; + } + virtual IRunner* getRunner() override { + return m_runner; + } + + virtual IConfigPtr const& getConfig() const override { + return m_config; + } + + virtual ~Context() override; + + public: // IMutableContext + virtual void setResultCapture( IResultCapture* resultCapture ) override { + m_resultCapture = resultCapture; + } + virtual void setRunner( IRunner* runner ) override { + m_runner = runner; + } + virtual void setConfig( IConfigPtr const& config ) override { + m_config = config; + } + + friend IMutableContext& getCurrentMutableContext(); + + private: + IConfigPtr m_config; + IRunner* m_runner = nullptr; + IResultCapture* m_resultCapture = nullptr; + }; + + IMutableContext *IMutableContext::currentContext = nullptr; + + void IMutableContext::createContext() + { + currentContext = new Context(); + } + + void cleanUpContext() { + delete IMutableContext::currentContext; + IMutableContext::currentContext = nullptr; + } + IContext::~IContext() = default; + IMutableContext::~IMutableContext() = default; + Context::~Context() = default; +} +// end catch_context.cpp +// start catch_debug_console.cpp + +// start catch_debug_console.h + +#include <string> + +namespace Catch { + void writeToDebugConsole( std::string const& text ); +} + +// end catch_debug_console.h +#ifdef CATCH_PLATFORM_WINDOWS + + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + ::OutputDebugStringA( text.c_str() ); + } + } + +#else + + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + // !TBD: Need a version for Mac/ XCode and other IDEs + Catch::cout() << text; + } + } + +#endif // Platform +// end catch_debug_console.cpp +// start catch_debugger.cpp + +#ifdef CATCH_PLATFORM_MAC + +# include <assert.h> +# include <stdbool.h> +# include <sys/types.h> +# include <unistd.h> +# include <sys/sysctl.h> +# include <cstddef> +# include <ostream> + +namespace Catch { + + // The following function is taken directly from the following technical note: + // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html + + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + bool isDebuggerActive(){ + + int mib[4]; + struct kinfo_proc info; + std::size_t size; + + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + + info.kp_proc.p_flag = 0; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // Call sysctl. + + size = sizeof(info); + if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0) != 0 ) { + Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; + return false; + } + + // We're being debugged if the P_TRACED flag is set. + + return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); + } + } // namespace Catch + +#elif defined(CATCH_PLATFORM_LINUX) + #include <fstream> + #include <string> + + namespace Catch{ + // The standard POSIX way of detecting a debugger is to attempt to + // ptrace() the process, but this needs to be done from a child and not + // this process itself to still allow attaching to this process later + // if wanted, so is rather heavy. Under Linux we have the PID of the + // "debugger" (which doesn't need to be gdb, of course, it could also + // be strace, for example) in /proc/$PID/status, so just get it from + // there instead. + bool isDebuggerActive(){ + // Libstdc++ has a bug, where std::ifstream sets errno to 0 + // This way our users can properly assert over errno values + ErrnoGuard guard; + std::ifstream in("/proc/self/status"); + for( std::string line; std::getline(in, line); ) { + static const int PREFIX_LEN = 11; + if( line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0 ) { + // We're traced if the PID is not 0 and no other PID starts + // with 0 digit, so it's enough to check for just a single + // character. + return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; + } + } + + return false; + } + } // namespace Catch +#elif defined(_MSC_VER) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#else + namespace Catch { + bool isDebuggerActive() { return false; } + } +#endif // Platform +// end catch_debugger.cpp +// start catch_decomposer.cpp + +namespace Catch { + + ITransientExpression::~ITransientExpression() = default; + + void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ) { + if( lhs.size() + rhs.size() < 40 && + lhs.find('\n') == std::string::npos && + rhs.find('\n') == std::string::npos ) + os << lhs << " " << op << " " << rhs; + else + os << lhs << "\n" << op << "\n" << rhs; + } +} +// end catch_decomposer.cpp +// start catch_enforce.cpp + +namespace Catch { +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER) + [[noreturn]] + void throw_exception(std::exception const& e) { + Catch::cerr() << "Catch will terminate because it needed to throw an exception.\n" + << "The message was: " << e.what() << '\n'; + std::terminate(); + } +#endif +} // namespace Catch; +// end catch_enforce.cpp +// start catch_errno_guard.cpp + +#include <cerrno> + +namespace Catch { + ErrnoGuard::ErrnoGuard():m_oldErrno(errno){} + ErrnoGuard::~ErrnoGuard() { errno = m_oldErrno; } +} +// end catch_errno_guard.cpp +// start catch_exception_translator_registry.cpp + +// start catch_exception_translator_registry.h + +#include <vector> +#include <string> +#include <memory> + +namespace Catch { + + class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { + public: + ~ExceptionTranslatorRegistry(); + virtual void registerTranslator( const IExceptionTranslator* translator ); + virtual std::string translateActiveException() const override; + std::string tryTranslators() const; + + private: + std::vector<std::unique_ptr<IExceptionTranslator const>> m_translators; + }; +} + +// end catch_exception_translator_registry.h +#ifdef __OBJC__ +#import "Foundation/Foundation.h" +#endif + +namespace Catch { + + ExceptionTranslatorRegistry::~ExceptionTranslatorRegistry() { + } + + void ExceptionTranslatorRegistry::registerTranslator( const IExceptionTranslator* translator ) { + m_translators.push_back( std::unique_ptr<const IExceptionTranslator>( translator ) ); + } + +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + std::string ExceptionTranslatorRegistry::translateActiveException() const { + try { +#ifdef __OBJC__ + // In Objective-C try objective-c exceptions first + @try { + return tryTranslators(); + } + @catch (NSException *exception) { + return Catch::Detail::stringify( [exception description] ); + } +#else + // Compiling a mixed mode project with MSVC means that CLR + // exceptions will be caught in (...) as well. However, these + // do not fill-in std::current_exception and thus lead to crash + // when attempting rethrow. + // /EHa switch also causes structured exceptions to be caught + // here, but they fill-in current_exception properly, so + // at worst the output should be a little weird, instead of + // causing a crash. + if (std::current_exception() == nullptr) { + return "Non C++ exception. Possibly a CLR exception."; + } + return tryTranslators(); +#endif + } + catch( TestFailureException& ) { + std::rethrow_exception(std::current_exception()); + } + catch( std::exception& ex ) { + return ex.what(); + } + catch( std::string& msg ) { + return msg; + } + catch( const char* msg ) { + return msg; + } + catch(...) { + return "Unknown exception"; + } + } + +#else // ^^ Exceptions are enabled // Exceptions are disabled vv + std::string ExceptionTranslatorRegistry::translateActiveException() const { + CATCH_INTERNAL_ERROR("Attempted to translate active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!"); + } +#endif + + std::string ExceptionTranslatorRegistry::tryTranslators() const { + if( m_translators.empty() ) + std::rethrow_exception(std::current_exception()); + else + return m_translators[0]->translate( m_translators.begin()+1, m_translators.end() ); + } +} +// end catch_exception_translator_registry.cpp +// start catch_fatal_condition.cpp + +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + +#if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS ) + +namespace { + // Report the error condition + void reportFatal( char const * const message ) { + Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message ); + } +} + +#endif // signals/SEH handling + +#if defined( CATCH_CONFIG_WINDOWS_SEH ) + +namespace Catch { + struct SignalDefs { DWORD id; const char* name; }; + + // There is no 1-1 mapping between signals and windows exceptions. + // Windows can easily distinguish between SO and SigSegV, + // but SigInt, SigTerm, etc are handled differently. + static SignalDefs signalDefs[] = { + { EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal" }, + { EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" }, + { EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" }, + { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" }, + }; + + LONG CALLBACK FatalConditionHandler::handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { + for (auto const& def : signalDefs) { + if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { + reportFatal(def.name); + } + } + // If its not an exception we care about, pass it along. + // This stops us from eating debugger breaks etc. + return EXCEPTION_CONTINUE_SEARCH; + } + + FatalConditionHandler::FatalConditionHandler() { + isSet = true; + // 32k seems enough for Catch to handle stack overflow, + // but the value was found experimentally, so there is no strong guarantee + guaranteeSize = 32 * 1024; + exceptionHandlerHandle = nullptr; + // Register as first handler in current chain + exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); + // Pass in guarantee size to be filled + SetThreadStackGuarantee(&guaranteeSize); + } + + void FatalConditionHandler::reset() { + if (isSet) { + RemoveVectoredExceptionHandler(exceptionHandlerHandle); + SetThreadStackGuarantee(&guaranteeSize); + exceptionHandlerHandle = nullptr; + isSet = false; + } + } + + FatalConditionHandler::~FatalConditionHandler() { + reset(); + } + +bool FatalConditionHandler::isSet = false; +ULONG FatalConditionHandler::guaranteeSize = 0; +PVOID FatalConditionHandler::exceptionHandlerHandle = nullptr; + +} // namespace Catch + +#elif defined( CATCH_CONFIG_POSIX_SIGNALS ) + +namespace Catch { + + struct SignalDefs { + int id; + const char* name; + }; + + // 32kb for the alternate stack seems to be sufficient. However, this value + // is experimentally determined, so that's not guaranteed. + constexpr static std::size_t sigStackSize = 32768 >= MINSIGSTKSZ ? 32768 : MINSIGSTKSZ; + + static SignalDefs signalDefs[] = { + { SIGINT, "SIGINT - Terminal interrupt signal" }, + { SIGILL, "SIGILL - Illegal instruction signal" }, + { SIGFPE, "SIGFPE - Floating point error signal" }, + { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, + { SIGTERM, "SIGTERM - Termination request signal" }, + { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } + }; + + void FatalConditionHandler::handleSignal( int sig ) { + char const * name = "<unknown signal>"; + for (auto const& def : signalDefs) { + if (sig == def.id) { + name = def.name; + break; + } + } + reset(); + reportFatal(name); + raise( sig ); + } + + FatalConditionHandler::FatalConditionHandler() { + isSet = true; + stack_t sigStack; + sigStack.ss_sp = altStackMem; + sigStack.ss_size = sigStackSize; + sigStack.ss_flags = 0; + sigaltstack(&sigStack, &oldSigStack); + struct sigaction sa = { }; + + sa.sa_handler = handleSignal; + sa.sa_flags = SA_ONSTACK; + for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) { + sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); + } + } + + FatalConditionHandler::~FatalConditionHandler() { + reset(); + } + + void FatalConditionHandler::reset() { + if( isSet ) { + // Set signals back to previous values -- hopefully nobody overwrote them in the meantime + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) { + sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); + } + // Return the old stack + sigaltstack(&oldSigStack, nullptr); + isSet = false; + } + } + + bool FatalConditionHandler::isSet = false; + struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {}; + stack_t FatalConditionHandler::oldSigStack = {}; + char FatalConditionHandler::altStackMem[sigStackSize] = {}; + +} // namespace Catch + +#else + +namespace Catch { + void FatalConditionHandler::reset() {} +} + +#endif // signals/SEH handling + +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif +// end catch_fatal_condition.cpp +// start catch_generators.cpp + +// start catch_random_number_generator.h + +#include <algorithm> +#include <random> + +namespace Catch { + + struct IConfig; + + std::mt19937& rng(); + void seedRng( IConfig const& config ); + unsigned int rngSeed(); + +} + +// end catch_random_number_generator.h +#include <limits> +#include <set> + +namespace Catch { + +IGeneratorTracker::~IGeneratorTracker() {} + +namespace Generators { + + GeneratorBase::~GeneratorBase() {} + + std::vector<size_t> randomiseIndices( size_t selectionSize, size_t sourceSize ) { + + assert( selectionSize <= sourceSize ); + std::vector<size_t> indices; + indices.reserve( selectionSize ); + std::uniform_int_distribution<size_t> uid( 0, sourceSize-1 ); + + std::set<size_t> seen; + // !TBD: improve this algorithm + while( indices.size() < selectionSize ) { + auto index = uid( rng() ); + if( seen.insert( index ).second ) + indices.push_back( index ); + } + return indices; + } + + auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& { + return getResultCapture().acquireGeneratorTracker( lineInfo ); + } + + template<> + auto all<int>() -> Generator<int> { + return range( std::numeric_limits<int>::min(), std::numeric_limits<int>::max() ); + } + +} // namespace Generators +} // namespace Catch +// end catch_generators.cpp +// start catch_interfaces_capture.cpp + +namespace Catch { + IResultCapture::~IResultCapture() = default; +} +// end catch_interfaces_capture.cpp +// start catch_interfaces_config.cpp + +namespace Catch { + IConfig::~IConfig() = default; +} +// end catch_interfaces_config.cpp +// start catch_interfaces_exception.cpp + +namespace Catch { + IExceptionTranslator::~IExceptionTranslator() = default; + IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() = default; +} +// end catch_interfaces_exception.cpp +// start catch_interfaces_registry_hub.cpp + +namespace Catch { + IRegistryHub::~IRegistryHub() = default; + IMutableRegistryHub::~IMutableRegistryHub() = default; +} +// end catch_interfaces_registry_hub.cpp +// start catch_interfaces_reporter.cpp + +// start catch_reporter_listening.h + +namespace Catch { + + class ListeningReporter : public IStreamingReporter { + using Reporters = std::vector<IStreamingReporterPtr>; + Reporters m_listeners; + IStreamingReporterPtr m_reporter = nullptr; + ReporterPreferences m_preferences; + + public: + ListeningReporter(); + + void addListener( IStreamingReporterPtr&& listener ); + void addReporter( IStreamingReporterPtr&& reporter ); + + public: // IStreamingReporter + + ReporterPreferences getPreferences() const override; + + void noMatchingTestCases( std::string const& spec ) override; + + static std::set<Verbosity> getSupportedVerbosities(); + + void benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) override; + void benchmarkEnded( BenchmarkStats const& benchmarkStats ) override; + + void testRunStarting( TestRunInfo const& testRunInfo ) override; + void testGroupStarting( GroupInfo const& groupInfo ) override; + void testCaseStarting( TestCaseInfo const& testInfo ) override; + void sectionStarting( SectionInfo const& sectionInfo ) override; + void assertionStarting( AssertionInfo const& assertionInfo ) override; + + // The return value indicates if the messages buffer should be cleared: + bool assertionEnded( AssertionStats const& assertionStats ) override; + void sectionEnded( SectionStats const& sectionStats ) override; + void testCaseEnded( TestCaseStats const& testCaseStats ) override; + void testGroupEnded( TestGroupStats const& testGroupStats ) override; + void testRunEnded( TestRunStats const& testRunStats ) override; + + void skipTest( TestCaseInfo const& testInfo ) override; + bool isMulti() const override; + + }; + +} // end namespace Catch + +// end catch_reporter_listening.h +namespace Catch { + + ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig ) + : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} + + ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig, std::ostream& _stream ) + : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} + + std::ostream& ReporterConfig::stream() const { return *m_stream; } + IConfigPtr ReporterConfig::fullConfig() const { return m_fullConfig; } + + TestRunInfo::TestRunInfo( std::string const& _name ) : name( _name ) {} + + GroupInfo::GroupInfo( std::string const& _name, + std::size_t _groupIndex, + std::size_t _groupsCount ) + : name( _name ), + groupIndex( _groupIndex ), + groupsCounts( _groupsCount ) + {} + + AssertionStats::AssertionStats( AssertionResult const& _assertionResult, + std::vector<MessageInfo> const& _infoMessages, + Totals const& _totals ) + : assertionResult( _assertionResult ), + infoMessages( _infoMessages ), + totals( _totals ) + { + assertionResult.m_resultData.lazyExpression.m_transientExpression = _assertionResult.m_resultData.lazyExpression.m_transientExpression; + + if( assertionResult.hasMessage() ) { + // Copy message into messages list. + // !TBD This should have been done earlier, somewhere + MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); + builder << assertionResult.getMessage(); + builder.m_info.message = builder.m_stream.str(); + + infoMessages.push_back( builder.m_info ); + } + } + + AssertionStats::~AssertionStats() = default; + + SectionStats::SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ) + : sectionInfo( _sectionInfo ), + assertions( _assertions ), + durationInSeconds( _durationInSeconds ), + missingAssertions( _missingAssertions ) + {} + + SectionStats::~SectionStats() = default; + + TestCaseStats::TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ) + : testInfo( _testInfo ), + totals( _totals ), + stdOut( _stdOut ), + stdErr( _stdErr ), + aborting( _aborting ) + {} + + TestCaseStats::~TestCaseStats() = default; + + TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo, + Totals const& _totals, + bool _aborting ) + : groupInfo( _groupInfo ), + totals( _totals ), + aborting( _aborting ) + {} + + TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo ) + : groupInfo( _groupInfo ), + aborting( false ) + {} + + TestGroupStats::~TestGroupStats() = default; + + TestRunStats::TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ) + : runInfo( _runInfo ), + totals( _totals ), + aborting( _aborting ) + {} + + TestRunStats::~TestRunStats() = default; + + void IStreamingReporter::fatalErrorEncountered( StringRef ) {} + bool IStreamingReporter::isMulti() const { return false; } + + IReporterFactory::~IReporterFactory() = default; + IReporterRegistry::~IReporterRegistry() = default; + +} // end namespace Catch +// end catch_interfaces_reporter.cpp +// start catch_interfaces_runner.cpp + +namespace Catch { + IRunner::~IRunner() = default; +} +// end catch_interfaces_runner.cpp +// start catch_interfaces_testcase.cpp + +namespace Catch { + ITestInvoker::~ITestInvoker() = default; + ITestCaseRegistry::~ITestCaseRegistry() = default; +} +// end catch_interfaces_testcase.cpp +// start catch_leak_detector.cpp + +#ifdef CATCH_CONFIG_WINDOWS_CRTDBG +#include <crtdbg.h> + +namespace Catch { + + LeakDetector::LeakDetector() { + int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); + flag |= _CRTDBG_LEAK_CHECK_DF; + flag |= _CRTDBG_ALLOC_MEM_DF; + _CrtSetDbgFlag(flag); + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + // Change this to leaking allocation's number to break there + _CrtSetBreakAlloc(-1); + } +} + +#else + + Catch::LeakDetector::LeakDetector() {} + +#endif + +Catch::LeakDetector::~LeakDetector() { + Catch::cleanUp(); +} +// end catch_leak_detector.cpp +// start catch_list.cpp + +// start catch_list.h + +#include <set> + +namespace Catch { + + std::size_t listTests( Config const& config ); + + std::size_t listTestsNamesOnly( Config const& config ); + + struct TagInfo { + void add( std::string const& spelling ); + std::string all() const; + + std::set<std::string> spellings; + std::size_t count = 0; + }; + + std::size_t listTags( Config const& config ); + + std::size_t listReporters(); + + Option<std::size_t> list( Config const& config ); + +} // end namespace Catch + +// end catch_list.h +// start catch_text.h + +namespace Catch { + using namespace clara::TextFlow; +} + +// end catch_text.h +#include <limits> +#include <algorithm> +#include <iomanip> + +namespace Catch { + + std::size_t listTests( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( config.hasTestFilters() ) + Catch::cout() << "Matching test cases:\n"; + else { + Catch::cout() << "All available test cases:\n"; + } + + auto matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( auto const& testCaseInfo : matchedTestCases ) { + Colour::Code colour = testCaseInfo.isHidden() + ? Colour::SecondaryText + : Colour::None; + Colour colourGuard( colour ); + + Catch::cout() << Column( testCaseInfo.name ).initialIndent( 2 ).indent( 4 ) << "\n"; + if( config.verbosity() >= Verbosity::High ) { + Catch::cout() << Column( Catch::Detail::stringify( testCaseInfo.lineInfo ) ).indent(4) << std::endl; + std::string description = testCaseInfo.description; + if( description.empty() ) + description = "(NO DESCRIPTION)"; + Catch::cout() << Column( description ).indent(4) << std::endl; + } + if( !testCaseInfo.tags.empty() ) + Catch::cout() << Column( testCaseInfo.tagsAsString() ).indent( 6 ) << "\n"; + } + + if( !config.hasTestFilters() ) + Catch::cout() << pluralise( matchedTestCases.size(), "test case" ) << '\n' << std::endl; + else + Catch::cout() << pluralise( matchedTestCases.size(), "matching test case" ) << '\n' << std::endl; + return matchedTestCases.size(); + } + + std::size_t listTestsNamesOnly( Config const& config ) { + TestSpec testSpec = config.testSpec(); + std::size_t matchedTests = 0; + std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( auto const& testCaseInfo : matchedTestCases ) { + matchedTests++; + if( startsWith( testCaseInfo.name, '#' ) ) + Catch::cout() << '"' << testCaseInfo.name << '"'; + else + Catch::cout() << testCaseInfo.name; + if ( config.verbosity() >= Verbosity::High ) + Catch::cout() << "\t@" << testCaseInfo.lineInfo; + Catch::cout() << std::endl; + } + return matchedTests; + } + + void TagInfo::add( std::string const& spelling ) { + ++count; + spellings.insert( spelling ); + } + + std::string TagInfo::all() const { + std::string out; + for( auto const& spelling : spellings ) + out += "[" + spelling + "]"; + return out; + } + + std::size_t listTags( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( config.hasTestFilters() ) + Catch::cout() << "Tags for matching test cases:\n"; + else { + Catch::cout() << "All available tags:\n"; + } + + std::map<std::string, TagInfo> tagCounts; + + std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( auto const& testCase : matchedTestCases ) { + for( auto const& tagName : testCase.getTestCaseInfo().tags ) { + std::string lcaseTagName = toLower( tagName ); + auto countIt = tagCounts.find( lcaseTagName ); + if( countIt == tagCounts.end() ) + countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first; + countIt->second.add( tagName ); + } + } + + for( auto const& tagCount : tagCounts ) { + ReusableStringStream rss; + rss << " " << std::setw(2) << tagCount.second.count << " "; + auto str = rss.str(); + auto wrapper = Column( tagCount.second.all() ) + .initialIndent( 0 ) + .indent( str.size() ) + .width( CATCH_CONFIG_CONSOLE_WIDTH-10 ); + Catch::cout() << str << wrapper << '\n'; + } + Catch::cout() << pluralise( tagCounts.size(), "tag" ) << '\n' << std::endl; + return tagCounts.size(); + } + + std::size_t listReporters() { + Catch::cout() << "Available reporters:\n"; + IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); + std::size_t maxNameLen = 0; + for( auto const& factoryKvp : factories ) + maxNameLen = (std::max)( maxNameLen, factoryKvp.first.size() ); + + for( auto const& factoryKvp : factories ) { + Catch::cout() + << Column( factoryKvp.first + ":" ) + .indent(2) + .width( 5+maxNameLen ) + + Column( factoryKvp.second->getDescription() ) + .initialIndent(0) + .indent(2) + .width( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) + << "\n"; + } + Catch::cout() << std::endl; + return factories.size(); + } + + Option<std::size_t> list( Config const& config ) { + Option<std::size_t> listedCount; + if( config.listTests() ) + listedCount = listedCount.valueOr(0) + listTests( config ); + if( config.listTestNamesOnly() ) + listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config ); + if( config.listTags() ) + listedCount = listedCount.valueOr(0) + listTags( config ); + if( config.listReporters() ) + listedCount = listedCount.valueOr(0) + listReporters(); + return listedCount; + } + +} // end namespace Catch +// end catch_list.cpp +// start catch_matchers.cpp + +namespace Catch { +namespace Matchers { + namespace Impl { + + std::string MatcherUntypedBase::toString() const { + if( m_cachedToString.empty() ) + m_cachedToString = describe(); + return m_cachedToString; + } + + MatcherUntypedBase::~MatcherUntypedBase() = default; + + } // namespace Impl +} // namespace Matchers + +using namespace Matchers; +using Matchers::Impl::MatcherBase; + +} // namespace Catch +// end catch_matchers.cpp +// start catch_matchers_floating.cpp + +// start catch_polyfills.hpp + +namespace Catch { + bool isnan(float f); + bool isnan(double d); +} + +// end catch_polyfills.hpp +// start catch_to_string.hpp + +#include <string> + +namespace Catch { + template <typename T> + std::string to_string(T const& t) { +#if defined(CATCH_CONFIG_CPP11_TO_STRING) + return std::to_string(t); +#else + ReusableStringStream rss; + rss << t; + return rss.str(); +#endif + } +} // end namespace Catch + +// end catch_to_string.hpp +#include <cstdlib> +#include <cstdint> +#include <cstring> + +namespace Catch { +namespace Matchers { +namespace Floating { +enum class FloatingPointKind : uint8_t { + Float, + Double +}; +} +} +} + +namespace { + +template <typename T> +struct Converter; + +template <> +struct Converter<float> { + static_assert(sizeof(float) == sizeof(int32_t), "Important ULP matcher assumption violated"); + Converter(float f) { + std::memcpy(&i, &f, sizeof(f)); + } + int32_t i; +}; + +template <> +struct Converter<double> { + static_assert(sizeof(double) == sizeof(int64_t), "Important ULP matcher assumption violated"); + Converter(double d) { + std::memcpy(&i, &d, sizeof(d)); + } + int64_t i; +}; + +template <typename T> +auto convert(T t) -> Converter<T> { + return Converter<T>(t); +} + +template <typename FP> +bool almostEqualUlps(FP lhs, FP rhs, int maxUlpDiff) { + // Comparison with NaN should always be false. + // This way we can rule it out before getting into the ugly details + if (Catch::isnan(lhs) || Catch::isnan(rhs)) { + return false; + } + + auto lc = convert(lhs); + auto rc = convert(rhs); + + if ((lc.i < 0) != (rc.i < 0)) { + // Potentially we can have +0 and -0 + return lhs == rhs; + } + + auto ulpDiff = std::abs(lc.i - rc.i); + return ulpDiff <= maxUlpDiff; +} + +} + +namespace Catch { +namespace Matchers { +namespace Floating { + WithinAbsMatcher::WithinAbsMatcher(double target, double margin) + :m_target{ target }, m_margin{ margin } { + CATCH_ENFORCE(margin >= 0, "Invalid margin: " << margin << '.' + << " Margin has to be non-negative."); + } + + // Performs equivalent check of std::fabs(lhs - rhs) <= margin + // But without the subtraction to allow for INFINITY in comparison + bool WithinAbsMatcher::match(double const& matchee) const { + return (matchee + m_margin >= m_target) && (m_target + m_margin >= matchee); + } + + std::string WithinAbsMatcher::describe() const { + return "is within " + ::Catch::Detail::stringify(m_margin) + " of " + ::Catch::Detail::stringify(m_target); + } + + WithinUlpsMatcher::WithinUlpsMatcher(double target, int ulps, FloatingPointKind baseType) + :m_target{ target }, m_ulps{ ulps }, m_type{ baseType } { + CATCH_ENFORCE(ulps >= 0, "Invalid ULP setting: " << ulps << '.' + << " ULPs have to be non-negative."); + } + +#if defined(__clang__) +#pragma clang diagnostic push +// Clang <3.5 reports on the default branch in the switch below +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + + bool WithinUlpsMatcher::match(double const& matchee) const { + switch (m_type) { + case FloatingPointKind::Float: + return almostEqualUlps<float>(static_cast<float>(matchee), static_cast<float>(m_target), m_ulps); + case FloatingPointKind::Double: + return almostEqualUlps<double>(matchee, m_target, m_ulps); + default: + CATCH_INTERNAL_ERROR( "Unknown FloatingPointKind value" ); + } + } + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + + std::string WithinUlpsMatcher::describe() const { + return "is within " + Catch::to_string(m_ulps) + " ULPs of " + ::Catch::Detail::stringify(m_target) + ((m_type == FloatingPointKind::Float)? "f" : ""); + } + +}// namespace Floating + +Floating::WithinUlpsMatcher WithinULP(double target, int maxUlpDiff) { + return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Double); +} + +Floating::WithinUlpsMatcher WithinULP(float target, int maxUlpDiff) { + return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Float); +} + +Floating::WithinAbsMatcher WithinAbs(double target, double margin) { + return Floating::WithinAbsMatcher(target, margin); +} + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_floating.cpp +// start catch_matchers_generic.cpp + +std::string Catch::Matchers::Generic::Detail::finalizeDescription(const std::string& desc) { + if (desc.empty()) { + return "matches undescribed predicate"; + } else { + return "matches predicate: \"" + desc + '"'; + } +} +// end catch_matchers_generic.cpp +// start catch_matchers_string.cpp + +#include <regex> + +namespace Catch { +namespace Matchers { + + namespace StdString { + + CasedString::CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_str( adjustString( str ) ) + {} + std::string CasedString::adjustString( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No + ? toLower( str ) + : str; + } + std::string CasedString::caseSensitivitySuffix() const { + return m_caseSensitivity == CaseSensitive::No + ? " (case insensitive)" + : std::string(); + } + + StringMatcherBase::StringMatcherBase( std::string const& operation, CasedString const& comparator ) + : m_comparator( comparator ), + m_operation( operation ) { + } + + std::string StringMatcherBase::describe() const { + std::string description; + description.reserve(5 + m_operation.size() + m_comparator.m_str.size() + + m_comparator.caseSensitivitySuffix().size()); + description += m_operation; + description += ": \""; + description += m_comparator.m_str; + description += "\""; + description += m_comparator.caseSensitivitySuffix(); + return description; + } + + EqualsMatcher::EqualsMatcher( CasedString const& comparator ) : StringMatcherBase( "equals", comparator ) {} + + bool EqualsMatcher::match( std::string const& source ) const { + return m_comparator.adjustString( source ) == m_comparator.m_str; + } + + ContainsMatcher::ContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains", comparator ) {} + + bool ContainsMatcher::match( std::string const& source ) const { + return contains( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with", comparator ) {} + + bool StartsWithMatcher::match( std::string const& source ) const { + return startsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "ends with", comparator ) {} + + bool EndsWithMatcher::match( std::string const& source ) const { + return endsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + RegexMatcher::RegexMatcher(std::string regex, CaseSensitive::Choice caseSensitivity): m_regex(std::move(regex)), m_caseSensitivity(caseSensitivity) {} + + bool RegexMatcher::match(std::string const& matchee) const { + auto flags = std::regex::ECMAScript; // ECMAScript is the default syntax option anyway + if (m_caseSensitivity == CaseSensitive::Choice::No) { + flags |= std::regex::icase; + } + auto reg = std::regex(m_regex, flags); + return std::regex_match(matchee, reg); + } + + std::string RegexMatcher::describe() const { + return "matches " + ::Catch::Detail::stringify(m_regex) + ((m_caseSensitivity == CaseSensitive::Choice::Yes)? " case sensitively" : " case insensitively"); + } + + } // namespace StdString + + StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::EqualsMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::ContainsMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::EndsWithMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::StartsWithMatcher( StdString::CasedString( str, caseSensitivity) ); + } + + StdString::RegexMatcher Matches(std::string const& regex, CaseSensitive::Choice caseSensitivity) { + return StdString::RegexMatcher(regex, caseSensitivity); + } + +} // namespace Matchers +} // namespace Catch +// end catch_matchers_string.cpp +// start catch_message.cpp + +// start catch_uncaught_exceptions.h + +namespace Catch { + bool uncaught_exceptions(); +} // end namespace Catch + +// end catch_uncaught_exceptions.h +#include <cassert> +#include <stack> + +namespace Catch { + + MessageInfo::MessageInfo( StringRef const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + type( _type ), + sequence( ++globalCount ) + {} + + bool MessageInfo::operator==( MessageInfo const& other ) const { + return sequence == other.sequence; + } + + bool MessageInfo::operator<( MessageInfo const& other ) const { + return sequence < other.sequence; + } + + // This may need protecting if threading support is added + unsigned int MessageInfo::globalCount = 0; + + //////////////////////////////////////////////////////////////////////////// + + Catch::MessageBuilder::MessageBuilder( StringRef const& macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ) + :m_info(macroName, lineInfo, type) {} + + //////////////////////////////////////////////////////////////////////////// + + ScopedMessage::ScopedMessage( MessageBuilder const& builder ) + : m_info( builder.m_info ) + { + m_info.message = builder.m_stream.str(); + getResultCapture().pushScopedMessage( m_info ); + } + + ScopedMessage::~ScopedMessage() { + if ( !uncaught_exceptions() ){ + getResultCapture().popScopedMessage(m_info); + } + } + + Capturer::Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ) { + auto trimmed = [&] (size_t start, size_t end) { + while (names[start] == ',' || isspace(names[start])) { + ++start; + } + while (names[end] == ',' || isspace(names[end])) { + --end; + } + return names.substr(start, end - start + 1); + }; + + size_t start = 0; + std::stack<char> openings; + for (size_t pos = 0; pos < names.size(); ++pos) { + char c = names[pos]; + switch (c) { + case '[': + case '{': + case '(': + // It is basically impossible to disambiguate between + // comparison and start of template args in this context +// case '<': + openings.push(c); + break; + case ']': + case '}': + case ')': +// case '>': + openings.pop(); + break; + case ',': + if (start != pos && openings.size() == 0) { + m_messages.emplace_back(macroName, lineInfo, resultType); + m_messages.back().message = trimmed(start, pos); + m_messages.back().message += " := "; + start = pos; + } + } + } + assert(openings.size() == 0 && "Mismatched openings"); + m_messages.emplace_back(macroName, lineInfo, resultType); + m_messages.back().message = trimmed(start, names.size() - 1); + m_messages.back().message += " := "; + } + Capturer::~Capturer() { + if ( !uncaught_exceptions() ){ + assert( m_captured == m_messages.size() ); + for( size_t i = 0; i < m_captured; ++i ) + m_resultCapture.popScopedMessage( m_messages[i] ); + } + } + + void Capturer::captureValue( size_t index, std::string const& value ) { + assert( index < m_messages.size() ); + m_messages[index].message += value; + m_resultCapture.pushScopedMessage( m_messages[index] ); + m_captured++; + } + +} // end namespace Catch +// end catch_message.cpp +// start catch_output_redirect.cpp + +// start catch_output_redirect.h +#ifndef TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H +#define TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H + +#include <cstdio> +#include <iosfwd> +#include <string> + +namespace Catch { + + class RedirectedStream { + std::ostream& m_originalStream; + std::ostream& m_redirectionStream; + std::streambuf* m_prevBuf; + + public: + RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream ); + ~RedirectedStream(); + }; + + class RedirectedStdOut { + ReusableStringStream m_rss; + RedirectedStream m_cout; + public: + RedirectedStdOut(); + auto str() const -> std::string; + }; + + // StdErr has two constituent streams in C++, std::cerr and std::clog + // This means that we need to redirect 2 streams into 1 to keep proper + // order of writes + class RedirectedStdErr { + ReusableStringStream m_rss; + RedirectedStream m_cerr; + RedirectedStream m_clog; + public: + RedirectedStdErr(); + auto str() const -> std::string; + }; + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + + // Windows's implementation of std::tmpfile is terrible (it tries + // to create a file inside system folder, thus requiring elevated + // privileges for the binary), so we have to use tmpnam(_s) and + // create the file ourselves there. + class TempFile { + public: + TempFile(TempFile const&) = delete; + TempFile& operator=(TempFile const&) = delete; + TempFile(TempFile&&) = delete; + TempFile& operator=(TempFile&&) = delete; + + TempFile(); + ~TempFile(); + + std::FILE* getFile(); + std::string getContents(); + + private: + std::FILE* m_file = nullptr; + #if defined(_MSC_VER) + char m_buffer[L_tmpnam] = { 0 }; + #endif + }; + + class OutputRedirect { + public: + OutputRedirect(OutputRedirect const&) = delete; + OutputRedirect& operator=(OutputRedirect const&) = delete; + OutputRedirect(OutputRedirect&&) = delete; + OutputRedirect& operator=(OutputRedirect&&) = delete; + + OutputRedirect(std::string& stdout_dest, std::string& stderr_dest); + ~OutputRedirect(); + + private: + int m_originalStdout = -1; + int m_originalStderr = -1; + TempFile m_stdoutFile; + TempFile m_stderrFile; + std::string& m_stdoutDest; + std::string& m_stderrDest; + }; + +#endif + +} // end namespace Catch + +#endif // TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H +// end catch_output_redirect.h +#include <cstdio> +#include <cstring> +#include <fstream> +#include <sstream> +#include <stdexcept> + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + #if defined(_MSC_VER) + #include <io.h> //_dup and _dup2 + #define dup _dup + #define dup2 _dup2 + #define fileno _fileno + #else + #include <unistd.h> // dup and dup2 + #endif +#endif + +namespace Catch { + + RedirectedStream::RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream ) + : m_originalStream( originalStream ), + m_redirectionStream( redirectionStream ), + m_prevBuf( m_originalStream.rdbuf() ) + { + m_originalStream.rdbuf( m_redirectionStream.rdbuf() ); + } + + RedirectedStream::~RedirectedStream() { + m_originalStream.rdbuf( m_prevBuf ); + } + + RedirectedStdOut::RedirectedStdOut() : m_cout( Catch::cout(), m_rss.get() ) {} + auto RedirectedStdOut::str() const -> std::string { return m_rss.str(); } + + RedirectedStdErr::RedirectedStdErr() + : m_cerr( Catch::cerr(), m_rss.get() ), + m_clog( Catch::clog(), m_rss.get() ) + {} + auto RedirectedStdErr::str() const -> std::string { return m_rss.str(); } + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + +#if defined(_MSC_VER) + TempFile::TempFile() { + if (tmpnam_s(m_buffer)) { + CATCH_RUNTIME_ERROR("Could not get a temp filename"); + } + if (fopen_s(&m_file, m_buffer, "w")) { + char buffer[100]; + if (strerror_s(buffer, errno)) { + CATCH_RUNTIME_ERROR("Could not translate errno to a string"); + } + CATCH_RUNTIME_ERROR("Coul dnot open the temp file: '" << m_buffer << "' because: " << buffer); + } + } +#else + TempFile::TempFile() { + m_file = std::tmpfile(); + if (!m_file) { + CATCH_RUNTIME_ERROR("Could not create a temp file."); + } + } + +#endif + + TempFile::~TempFile() { + // TBD: What to do about errors here? + std::fclose(m_file); + // We manually create the file on Windows only, on Linux + // it will be autodeleted +#if defined(_MSC_VER) + std::remove(m_buffer); +#endif + } + + FILE* TempFile::getFile() { + return m_file; + } + + std::string TempFile::getContents() { + std::stringstream sstr; + char buffer[100] = {}; + std::rewind(m_file); + while (std::fgets(buffer, sizeof(buffer), m_file)) { + sstr << buffer; + } + return sstr.str(); + } + + OutputRedirect::OutputRedirect(std::string& stdout_dest, std::string& stderr_dest) : + m_originalStdout(dup(1)), + m_originalStderr(dup(2)), + m_stdoutDest(stdout_dest), + m_stderrDest(stderr_dest) { + dup2(fileno(m_stdoutFile.getFile()), 1); + dup2(fileno(m_stderrFile.getFile()), 2); + } + + OutputRedirect::~OutputRedirect() { + Catch::cout() << std::flush; + fflush(stdout); + // Since we support overriding these streams, we flush cerr + // even though std::cerr is unbuffered + Catch::cerr() << std::flush; + Catch::clog() << std::flush; + fflush(stderr); + + dup2(m_originalStdout, 1); + dup2(m_originalStderr, 2); + + m_stdoutDest += m_stdoutFile.getContents(); + m_stderrDest += m_stderrFile.getContents(); + } + +#endif // CATCH_CONFIG_NEW_CAPTURE + +} // namespace Catch + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + #if defined(_MSC_VER) + #undef dup + #undef dup2 + #undef fileno + #endif +#endif +// end catch_output_redirect.cpp +// start catch_polyfills.cpp + +#include <cmath> + +namespace Catch { + +#if !defined(CATCH_CONFIG_POLYFILL_ISNAN) + bool isnan(float f) { + return std::isnan(f); + } + bool isnan(double d) { + return std::isnan(d); + } +#else + // For now we only use this for embarcadero + bool isnan(float f) { + return std::_isnan(f); + } + bool isnan(double d) { + return std::_isnan(d); + } +#endif + +} // end namespace Catch +// end catch_polyfills.cpp +// start catch_random_number_generator.cpp + +namespace Catch { + + std::mt19937& rng() { + static std::mt19937 s_rng; + return s_rng; + } + + void seedRng( IConfig const& config ) { + if( config.rngSeed() != 0 ) { + std::srand( config.rngSeed() ); + rng().seed( config.rngSeed() ); + } + } + + unsigned int rngSeed() { + return getCurrentContext().getConfig()->rngSeed(); + } +} +// end catch_random_number_generator.cpp +// start catch_registry_hub.cpp + +// start catch_test_case_registry_impl.h + +#include <vector> +#include <set> +#include <algorithm> +#include <ios> + +namespace Catch { + + class TestCase; + struct IConfig; + + std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases ); + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + + void enforceNoDuplicateTestCases( std::vector<TestCase> const& functions ); + + std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ); + + class TestRegistry : public ITestCaseRegistry { + public: + virtual ~TestRegistry() = default; + + virtual void registerTest( TestCase const& testCase ); + + std::vector<TestCase> const& getAllTests() const override; + std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const override; + + private: + std::vector<TestCase> m_functions; + mutable RunTests::InWhatOrder m_currentSortOrder = RunTests::InDeclarationOrder; + mutable std::vector<TestCase> m_sortedFunctions; + std::size_t m_unnamedCount = 0; + std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised + }; + + /////////////////////////////////////////////////////////////////////////// + + class TestInvokerAsFunction : public ITestInvoker { + void(*m_testAsFunction)(); + public: + TestInvokerAsFunction( void(*testAsFunction)() ) noexcept; + + void invoke() const override; + }; + + std::string extractClassName( StringRef const& classOrQualifiedMethodName ); + + /////////////////////////////////////////////////////////////////////////// + +} // end namespace Catch + +// end catch_test_case_registry_impl.h +// start catch_reporter_registry.h + +#include <map> + +namespace Catch { + + class ReporterRegistry : public IReporterRegistry { + + public: + + ~ReporterRegistry() override; + + IStreamingReporterPtr create( std::string const& name, IConfigPtr const& config ) const override; + + void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ); + void registerListener( IReporterFactoryPtr const& factory ); + + FactoryMap const& getFactories() const override; + Listeners const& getListeners() const override; + + private: + FactoryMap m_factories; + Listeners m_listeners; + }; +} + +// end catch_reporter_registry.h +// start catch_tag_alias_registry.h + +// start catch_tag_alias.h + +#include <string> + +namespace Catch { + + struct TagAlias { + TagAlias(std::string const& _tag, SourceLineInfo _lineInfo); + + std::string tag; + SourceLineInfo lineInfo; + }; + +} // end namespace Catch + +// end catch_tag_alias.h +#include <map> + +namespace Catch { + + class TagAliasRegistry : public ITagAliasRegistry { + public: + ~TagAliasRegistry() override; + TagAlias const* find( std::string const& alias ) const override; + std::string expandAliases( std::string const& unexpandedTestSpec ) const override; + void add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ); + + private: + std::map<std::string, TagAlias> m_registry; + }; + +} // end namespace Catch + +// end catch_tag_alias_registry.h +// start catch_startup_exception_registry.h + +#include <vector> +#include <exception> + +namespace Catch { + + class StartupExceptionRegistry { + public: + void add(std::exception_ptr const& exception) noexcept; + std::vector<std::exception_ptr> const& getExceptions() const noexcept; + private: + std::vector<std::exception_ptr> m_exceptions; + }; + +} // end namespace Catch + +// end catch_startup_exception_registry.h +// start catch_singletons.hpp + +namespace Catch { + + struct ISingleton { + virtual ~ISingleton(); + }; + + void addSingleton( ISingleton* singleton ); + void cleanupSingletons(); + + template<typename SingletonImplT, typename InterfaceT = SingletonImplT, typename MutableInterfaceT = InterfaceT> + class Singleton : SingletonImplT, public ISingleton { + + static auto getInternal() -> Singleton* { + static Singleton* s_instance = nullptr; + if( !s_instance ) { + s_instance = new Singleton; + addSingleton( s_instance ); + } + return s_instance; + } + + public: + static auto get() -> InterfaceT const& { + return *getInternal(); + } + static auto getMutable() -> MutableInterfaceT& { + return *getInternal(); + } + }; + +} // namespace Catch + +// end catch_singletons.hpp +namespace Catch { + + namespace { + + class RegistryHub : public IRegistryHub, public IMutableRegistryHub, + private NonCopyable { + + public: // IRegistryHub + RegistryHub() = default; + IReporterRegistry const& getReporterRegistry() const override { + return m_reporterRegistry; + } + ITestCaseRegistry const& getTestCaseRegistry() const override { + return m_testCaseRegistry; + } + IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const override { + return m_exceptionTranslatorRegistry; + } + ITagAliasRegistry const& getTagAliasRegistry() const override { + return m_tagAliasRegistry; + } + StartupExceptionRegistry const& getStartupExceptionRegistry() const override { + return m_exceptionRegistry; + } + + public: // IMutableRegistryHub + void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) override { + m_reporterRegistry.registerReporter( name, factory ); + } + void registerListener( IReporterFactoryPtr const& factory ) override { + m_reporterRegistry.registerListener( factory ); + } + void registerTest( TestCase const& testInfo ) override { + m_testCaseRegistry.registerTest( testInfo ); + } + void registerTranslator( const IExceptionTranslator* translator ) override { + m_exceptionTranslatorRegistry.registerTranslator( translator ); + } + void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) override { + m_tagAliasRegistry.add( alias, tag, lineInfo ); + } + void registerStartupException() noexcept override { + m_exceptionRegistry.add(std::current_exception()); + } + + private: + TestRegistry m_testCaseRegistry; + ReporterRegistry m_reporterRegistry; + ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; + TagAliasRegistry m_tagAliasRegistry; + StartupExceptionRegistry m_exceptionRegistry; + }; + } + + using RegistryHubSingleton = Singleton<RegistryHub, IRegistryHub, IMutableRegistryHub>; + + IRegistryHub const& getRegistryHub() { + return RegistryHubSingleton::get(); + } + IMutableRegistryHub& getMutableRegistryHub() { + return RegistryHubSingleton::getMutable(); + } + void cleanUp() { + cleanupSingletons(); + cleanUpContext(); + } + std::string translateActiveException() { + return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); + } + +} // end namespace Catch +// end catch_registry_hub.cpp +// start catch_reporter_registry.cpp + +namespace Catch { + + ReporterRegistry::~ReporterRegistry() = default; + + IStreamingReporterPtr ReporterRegistry::create( std::string const& name, IConfigPtr const& config ) const { + auto it = m_factories.find( name ); + if( it == m_factories.end() ) + return nullptr; + return it->second->create( ReporterConfig( config ) ); + } + + void ReporterRegistry::registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) { + m_factories.emplace(name, factory); + } + void ReporterRegistry::registerListener( IReporterFactoryPtr const& factory ) { + m_listeners.push_back( factory ); + } + + IReporterRegistry::FactoryMap const& ReporterRegistry::getFactories() const { + return m_factories; + } + IReporterRegistry::Listeners const& ReporterRegistry::getListeners() const { + return m_listeners; + } + +} +// end catch_reporter_registry.cpp +// start catch_result_type.cpp + +namespace Catch { + + bool isOk( ResultWas::OfType resultType ) { + return ( resultType & ResultWas::FailureBit ) == 0; + } + bool isJustInfo( int flags ) { + return flags == ResultWas::Info; + } + + ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { + return static_cast<ResultDisposition::Flags>( static_cast<int>( lhs ) | static_cast<int>( rhs ) ); + } + + bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } + bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } + +} // end namespace Catch +// end catch_result_type.cpp +// start catch_run_context.cpp + +#include <cassert> +#include <algorithm> +#include <sstream> + +namespace Catch { + + namespace Generators { + struct GeneratorTracker : TestCaseTracking::TrackerBase, IGeneratorTracker { + size_t m_index = static_cast<size_t>( -1 ); + GeneratorBasePtr m_generator; + + GeneratorTracker( TestCaseTracking::NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : TrackerBase( nameAndLocation, ctx, parent ) + {} + ~GeneratorTracker(); + + static GeneratorTracker& acquire( TrackerContext& ctx, TestCaseTracking::NameAndLocation const& nameAndLocation ) { + std::shared_ptr<GeneratorTracker> tracker; + + ITracker& currentTracker = ctx.currentTracker(); + if( TestCaseTracking::ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isIndexTracker() ); + tracker = std::static_pointer_cast<GeneratorTracker>( childTracker ); + } + else { + tracker = std::make_shared<GeneratorTracker>( nameAndLocation, ctx, ¤tTracker ); + currentTracker.addChild( tracker ); + } + + if( !ctx.completedCycle() && !tracker->isComplete() ) { + if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun ) + tracker->moveNext(); + tracker->open(); + } + + return *tracker; + } + + void moveNext() { + m_index++; + m_children.clear(); + } + + // TrackerBase interface + bool isIndexTracker() const override { return true; } + auto hasGenerator() const -> bool override { + return !!m_generator; + } + void close() override { + TrackerBase::close(); + if( m_runState == CompletedSuccessfully && m_index < m_generator->size()-1 ) + m_runState = Executing; + } + + // IGeneratorTracker interface + auto getGenerator() const -> GeneratorBasePtr const& override { + return m_generator; + } + void setGenerator( GeneratorBasePtr&& generator ) override { + m_generator = std::move( generator ); + } + auto getIndex() const -> size_t override { + return m_index; + } + }; + GeneratorTracker::~GeneratorTracker() {} + } + + RunContext::RunContext(IConfigPtr const& _config, IStreamingReporterPtr&& reporter) + : m_runInfo(_config->name()), + m_context(getCurrentMutableContext()), + m_config(_config), + m_reporter(std::move(reporter)), + m_lastAssertionInfo{ StringRef(), SourceLineInfo("",0), StringRef(), ResultDisposition::Normal }, + m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions ) + { + m_context.setRunner(this); + m_context.setConfig(m_config); + m_context.setResultCapture(this); + m_reporter->testRunStarting(m_runInfo); + } + + RunContext::~RunContext() { + m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting())); + } + + void RunContext::testGroupStarting(std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount) { + m_reporter->testGroupStarting(GroupInfo(testSpec, groupIndex, groupsCount)); + } + + void RunContext::testGroupEnded(std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount) { + m_reporter->testGroupEnded(TestGroupStats(GroupInfo(testSpec, groupIndex, groupsCount), totals, aborting())); + } + + Totals RunContext::runTest(TestCase const& testCase) { + Totals prevTotals = m_totals; + + std::string redirectedCout; + std::string redirectedCerr; + + auto const& testInfo = testCase.getTestCaseInfo(); + + m_reporter->testCaseStarting(testInfo); + + m_activeTestCase = &testCase; + + ITracker& rootTracker = m_trackerContext.startRun(); + assert(rootTracker.isSectionTracker()); + static_cast<SectionTracker&>(rootTracker).addInitialFilters(m_config->getSectionsToRun()); + do { + m_trackerContext.startCycle(); + m_testCaseTracker = &SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(testInfo.name, testInfo.lineInfo)); + runCurrentTest(redirectedCout, redirectedCerr); + } while (!m_testCaseTracker->isSuccessfullyCompleted() && !aborting()); + + Totals deltaTotals = m_totals.delta(prevTotals); + if (testInfo.expectedToFail() && deltaTotals.testCases.passed > 0) { + deltaTotals.assertions.failed++; + deltaTotals.testCases.passed--; + deltaTotals.testCases.failed++; + } + m_totals.testCases += deltaTotals.testCases; + m_reporter->testCaseEnded(TestCaseStats(testInfo, + deltaTotals, + redirectedCout, + redirectedCerr, + aborting())); + + m_activeTestCase = nullptr; + m_testCaseTracker = nullptr; + + return deltaTotals; + } + + IConfigPtr RunContext::config() const { + return m_config; + } + + IStreamingReporter& RunContext::reporter() const { + return *m_reporter; + } + + void RunContext::assertionEnded(AssertionResult const & result) { + if (result.getResultType() == ResultWas::Ok) { + m_totals.assertions.passed++; + m_lastAssertionPassed = true; + } else if (!result.isOk()) { + m_lastAssertionPassed = false; + if( m_activeTestCase->getTestCaseInfo().okToFail() ) + m_totals.assertions.failedButOk++; + else + m_totals.assertions.failed++; + } + else { + m_lastAssertionPassed = true; + } + + // We have no use for the return value (whether messages should be cleared), because messages were made scoped + // and should be let to clear themselves out. + static_cast<void>(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals))); + + // Reset working state + resetAssertionInfo(); + m_lastResult = result; + } + void RunContext::resetAssertionInfo() { + m_lastAssertionInfo.macroName = StringRef(); + m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"_sr; + } + + bool RunContext::sectionStarted(SectionInfo const & sectionInfo, Counts & assertions) { + ITracker& sectionTracker = SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(sectionInfo.name, sectionInfo.lineInfo)); + if (!sectionTracker.isOpen()) + return false; + m_activeSections.push_back(§ionTracker); + + m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; + + m_reporter->sectionStarting(sectionInfo); + + assertions = m_totals.assertions; + + return true; + } + auto RunContext::acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& { + using namespace Generators; + GeneratorTracker& tracker = GeneratorTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocation( "generator", lineInfo ) ); + assert( tracker.isOpen() ); + m_lastAssertionInfo.lineInfo = lineInfo; + return tracker; + } + + bool RunContext::testForMissingAssertions(Counts& assertions) { + if (assertions.total() != 0) + return false; + if (!m_config->warnAboutMissingAssertions()) + return false; + if (m_trackerContext.currentTracker().hasChildren()) + return false; + m_totals.assertions.failed++; + assertions.failed++; + return true; + } + + void RunContext::sectionEnded(SectionEndInfo const & endInfo) { + Counts assertions = m_totals.assertions - endInfo.prevAssertions; + bool missingAssertions = testForMissingAssertions(assertions); + + if (!m_activeSections.empty()) { + m_activeSections.back()->close(); + m_activeSections.pop_back(); + } + + m_reporter->sectionEnded(SectionStats(endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions)); + m_messages.clear(); + } + + void RunContext::sectionEndedEarly(SectionEndInfo const & endInfo) { + if (m_unfinishedSections.empty()) + m_activeSections.back()->fail(); + else + m_activeSections.back()->close(); + m_activeSections.pop_back(); + + m_unfinishedSections.push_back(endInfo); + } + void RunContext::benchmarkStarting( BenchmarkInfo const& info ) { + m_reporter->benchmarkStarting( info ); + } + void RunContext::benchmarkEnded( BenchmarkStats const& stats ) { + m_reporter->benchmarkEnded( stats ); + } + + void RunContext::pushScopedMessage(MessageInfo const & message) { + m_messages.push_back(message); + } + + void RunContext::popScopedMessage(MessageInfo const & message) { + m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end()); + } + + std::string RunContext::getCurrentTestName() const { + return m_activeTestCase + ? m_activeTestCase->getTestCaseInfo().name + : std::string(); + } + + const AssertionResult * RunContext::getLastResult() const { + return &(*m_lastResult); + } + + void RunContext::exceptionEarlyReported() { + m_shouldReportUnexpected = false; + } + + void RunContext::handleFatalErrorCondition( StringRef message ) { + // First notify reporter that bad things happened + m_reporter->fatalErrorEncountered(message); + + // Don't rebuild the result -- the stringification itself can cause more fatal errors + // Instead, fake a result data. + AssertionResultData tempResult( ResultWas::FatalErrorCondition, { false } ); + tempResult.message = message; + AssertionResult result(m_lastAssertionInfo, tempResult); + + assertionEnded(result); + + handleUnfinishedSections(); + + // Recreate section for test case (as we will lose the one that was in scope) + auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); + + Counts assertions; + assertions.failed = 1; + SectionStats testCaseSectionStats(testCaseSection, assertions, 0, false); + m_reporter->sectionEnded(testCaseSectionStats); + + auto const& testInfo = m_activeTestCase->getTestCaseInfo(); + + Totals deltaTotals; + deltaTotals.testCases.failed = 1; + deltaTotals.assertions.failed = 1; + m_reporter->testCaseEnded(TestCaseStats(testInfo, + deltaTotals, + std::string(), + std::string(), + false)); + m_totals.testCases.failed++; + testGroupEnded(std::string(), m_totals, 1, 1); + m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false)); + } + + bool RunContext::lastAssertionPassed() { + return m_lastAssertionPassed; + } + + void RunContext::assertionPassed() { + m_lastAssertionPassed = true; + ++m_totals.assertions.passed; + resetAssertionInfo(); + } + + bool RunContext::aborting() const { + return m_totals.assertions.failed >= static_cast<std::size_t>(m_config->abortAfter()); + } + + void RunContext::runCurrentTest(std::string & redirectedCout, std::string & redirectedCerr) { + auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); + m_reporter->sectionStarting(testCaseSection); + Counts prevAssertions = m_totals.assertions; + double duration = 0; + m_shouldReportUnexpected = true; + m_lastAssertionInfo = { "TEST_CASE"_sr, testCaseInfo.lineInfo, StringRef(), ResultDisposition::Normal }; + + seedRng(*m_config); + + Timer timer; + CATCH_TRY { + if (m_reporter->getPreferences().shouldRedirectStdOut) { +#if !defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) + RedirectedStdOut redirectedStdOut; + RedirectedStdErr redirectedStdErr; + + timer.start(); + invokeActiveTestCase(); + redirectedCout += redirectedStdOut.str(); + redirectedCerr += redirectedStdErr.str(); +#else + OutputRedirect r(redirectedCout, redirectedCerr); + timer.start(); + invokeActiveTestCase(); +#endif + } else { + timer.start(); + invokeActiveTestCase(); + } + duration = timer.getElapsedSeconds(); + } CATCH_CATCH_ANON (TestFailureException&) { + // This just means the test was aborted due to failure + } CATCH_CATCH_ALL { + // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions + // are reported without translation at the point of origin. + if( m_shouldReportUnexpected ) { + AssertionReaction dummyReaction; + handleUnexpectedInflightException( m_lastAssertionInfo, translateActiveException(), dummyReaction ); + } + } + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions(assertions); + + m_testCaseTracker->close(); + handleUnfinishedSections(); + m_messages.clear(); + + SectionStats testCaseSectionStats(testCaseSection, assertions, duration, missingAssertions); + m_reporter->sectionEnded(testCaseSectionStats); + } + + void RunContext::invokeActiveTestCase() { + FatalConditionHandler fatalConditionHandler; // Handle signals + m_activeTestCase->invoke(); + fatalConditionHandler.reset(); + } + + void RunContext::handleUnfinishedSections() { + // If sections ended prematurely due to an exception we stored their + // infos here so we can tear them down outside the unwind process. + for (auto it = m_unfinishedSections.rbegin(), + itEnd = m_unfinishedSections.rend(); + it != itEnd; + ++it) + sectionEnded(*it); + m_unfinishedSections.clear(); + } + + void RunContext::handleExpr( + AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction + ) { + m_reporter->assertionStarting( info ); + + bool negated = isFalseTest( info.resultDisposition ); + bool result = expr.getResult() != negated; + + if( result ) { + if (!m_includeSuccessfulResults) { + assertionPassed(); + } + else { + reportExpr(info, ResultWas::Ok, &expr, negated); + } + } + else { + reportExpr(info, ResultWas::ExpressionFailed, &expr, negated ); + populateReaction( reaction ); + } + } + void RunContext::reportExpr( + AssertionInfo const &info, + ResultWas::OfType resultType, + ITransientExpression const *expr, + bool negated ) { + + m_lastAssertionInfo = info; + AssertionResultData data( resultType, LazyExpression( negated ) ); + + AssertionResult assertionResult{ info, data }; + assertionResult.m_resultData.lazyExpression.m_transientExpression = expr; + + assertionEnded( assertionResult ); + } + + void RunContext::handleMessage( + AssertionInfo const& info, + ResultWas::OfType resultType, + StringRef const& message, + AssertionReaction& reaction + ) { + m_reporter->assertionStarting( info ); + + m_lastAssertionInfo = info; + + AssertionResultData data( resultType, LazyExpression( false ) ); + data.message = message; + AssertionResult assertionResult{ m_lastAssertionInfo, data }; + assertionEnded( assertionResult ); + if( !assertionResult.isOk() ) + populateReaction( reaction ); + } + void RunContext::handleUnexpectedExceptionNotThrown( + AssertionInfo const& info, + AssertionReaction& reaction + ) { + handleNonExpr(info, Catch::ResultWas::DidntThrowException, reaction); + } + + void RunContext::handleUnexpectedInflightException( + AssertionInfo const& info, + std::string const& message, + AssertionReaction& reaction + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); + data.message = message; + AssertionResult assertionResult{ info, data }; + assertionEnded( assertionResult ); + populateReaction( reaction ); + } + + void RunContext::populateReaction( AssertionReaction& reaction ) { + reaction.shouldDebugBreak = m_config->shouldDebugBreak(); + reaction.shouldThrow = aborting() || (m_lastAssertionInfo.resultDisposition & ResultDisposition::Normal); + } + + void RunContext::handleIncomplete( + AssertionInfo const& info + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); + data.message = "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"; + AssertionResult assertionResult{ info, data }; + assertionEnded( assertionResult ); + } + void RunContext::handleNonExpr( + AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( resultType, LazyExpression( false ) ); + AssertionResult assertionResult{ info, data }; + assertionEnded( assertionResult ); + + if( !assertionResult.isOk() ) + populateReaction( reaction ); + } + + IResultCapture& getResultCapture() { + if (auto* capture = getCurrentContext().getResultCapture()) + return *capture; + else + CATCH_INTERNAL_ERROR("No result capture instance"); + } +} +// end catch_run_context.cpp +// start catch_section.cpp + +namespace Catch { + + Section::Section( SectionInfo const& info ) + : m_info( info ), + m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) + { + m_timer.start(); + } + + Section::~Section() { + if( m_sectionIncluded ) { + SectionEndInfo endInfo{ m_info, m_assertions, m_timer.getElapsedSeconds() }; + if( uncaught_exceptions() ) + getResultCapture().sectionEndedEarly( endInfo ); + else + getResultCapture().sectionEnded( endInfo ); + } + } + + // This indicates whether the section should be executed or not + Section::operator bool() const { + return m_sectionIncluded; + } + +} // end namespace Catch +// end catch_section.cpp +// start catch_section_info.cpp + +namespace Catch { + + SectionInfo::SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name ) + : name( _name ), + lineInfo( _lineInfo ) + {} + +} // end namespace Catch +// end catch_section_info.cpp +// start catch_session.cpp + +// start catch_session.h + +#include <memory> + +namespace Catch { + + class Session : NonCopyable { + public: + + Session(); + ~Session() override; + + void showHelp() const; + void libIdentify(); + + int applyCommandLine( int argc, char const * const * argv ); + #if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(UNICODE) + int applyCommandLine( int argc, wchar_t const * const * argv ); + #endif + + void useConfigData( ConfigData const& configData ); + + template<typename CharT> + int run(int argc, CharT const * const argv[]) { + if (m_startupExceptions) + return 1; + int returnCode = applyCommandLine(argc, argv); + if (returnCode == 0) + returnCode = run(); + return returnCode; + } + + int run(); + + clara::Parser const& cli() const; + void cli( clara::Parser const& newParser ); + ConfigData& configData(); + Config& config(); + private: + int runInternal(); + + clara::Parser m_cli; + ConfigData m_configData; + std::shared_ptr<Config> m_config; + bool m_startupExceptions = false; + }; + +} // end namespace Catch + +// end catch_session.h +// start catch_version.h + +#include <iosfwd> + +namespace Catch { + + // Versioning information + struct Version { + Version( Version const& ) = delete; + Version& operator=( Version const& ) = delete; + Version( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const * const _branchName, + unsigned int _buildNumber ); + + unsigned int const majorVersion; + unsigned int const minorVersion; + unsigned int const patchNumber; + + // buildNumber is only used if branchName is not null + char const * const branchName; + unsigned int const buildNumber; + + friend std::ostream& operator << ( std::ostream& os, Version const& version ); + }; + + Version const& libraryVersion(); +} + +// end catch_version.h +#include <cstdlib> +#include <iomanip> + +namespace Catch { + + namespace { + const int MaxExitCode = 255; + + IStreamingReporterPtr createReporter(std::string const& reporterName, IConfigPtr const& config) { + auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, config); + CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << "'"); + + return reporter; + } + + IStreamingReporterPtr makeReporter(std::shared_ptr<Config> const& config) { + if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty()) { + return createReporter(config->getReporterName(), config); + } + + auto multi = std::unique_ptr<ListeningReporter>(new ListeningReporter); + + auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners(); + for (auto const& listener : listeners) { + multi->addListener(listener->create(Catch::ReporterConfig(config))); + } + multi->addReporter(createReporter(config->getReporterName(), config)); + return std::move(multi); + } + + Catch::Totals runTests(std::shared_ptr<Config> const& config) { + auto reporter = makeReporter(config); + + RunContext context(config, std::move(reporter)); + + Totals totals; + + context.testGroupStarting(config->name(), 1, 1); + + TestSpec testSpec = config->testSpec(); + + auto const& allTestCases = getAllTestCasesSorted(*config); + for (auto const& testCase : allTestCases) { + if (!context.aborting() && matchTest(testCase, testSpec, *config)) + totals += context.runTest(testCase); + else + context.reporter().skipTest(testCase); + } + + if (config->warnAboutNoTests() && totals.testCases.total() == 0) { + ReusableStringStream testConfig; + + bool first = true; + for (const auto& input : config->getTestsOrTags()) { + if (!first) { testConfig << ' '; } + first = false; + testConfig << input; + } + + context.reporter().noMatchingTestCases(testConfig.str()); + totals.error = -1; + } + + context.testGroupEnded(config->name(), totals, 1, 1); + return totals; + } + + void applyFilenamesAsTags(Catch::IConfig const& config) { + auto& tests = const_cast<std::vector<TestCase>&>(getAllTestCasesSorted(config)); + for (auto& testCase : tests) { + auto tags = testCase.tags; + + std::string filename = testCase.lineInfo.file; + auto lastSlash = filename.find_last_of("\\/"); + if (lastSlash != std::string::npos) { + filename.erase(0, lastSlash); + filename[0] = '#'; + } + + auto lastDot = filename.find_last_of('.'); + if (lastDot != std::string::npos) { + filename.erase(lastDot); + } + + tags.push_back(std::move(filename)); + setTags(testCase, tags); + } + } + + } // anon namespace + + Session::Session() { + static bool alreadyInstantiated = false; + if( alreadyInstantiated ) { + CATCH_TRY { CATCH_INTERNAL_ERROR( "Only one instance of Catch::Session can ever be used" ); } + CATCH_CATCH_ALL { getMutableRegistryHub().registerStartupException(); } + } + + // There cannot be exceptions at startup in no-exception mode. +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions(); + if ( !exceptions.empty() ) { + m_startupExceptions = true; + Colour colourGuard( Colour::Red ); + Catch::cerr() << "Errors occurred during startup!" << '\n'; + // iterate over all exceptions and notify user + for ( const auto& ex_ptr : exceptions ) { + try { + std::rethrow_exception(ex_ptr); + } catch ( std::exception const& ex ) { + Catch::cerr() << Column( ex.what() ).indent(2) << '\n'; + } + } + } +#endif + + alreadyInstantiated = true; + m_cli = makeCommandLineParser( m_configData ); + } + Session::~Session() { + Catch::cleanUp(); + } + + void Session::showHelp() const { + Catch::cout() + << "\nCatch v" << libraryVersion() << "\n" + << m_cli << std::endl + << "For more detailed usage please see the project docs\n" << std::endl; + } + void Session::libIdentify() { + Catch::cout() + << std::left << std::setw(16) << "description: " << "A Catch test executable\n" + << std::left << std::setw(16) << "category: " << "testframework\n" + << std::left << std::setw(16) << "framework: " << "Catch Test\n" + << std::left << std::setw(16) << "version: " << libraryVersion() << std::endl; + } + + int Session::applyCommandLine( int argc, char const * const * argv ) { + if( m_startupExceptions ) + return 1; + + auto result = m_cli.parse( clara::Args( argc, argv ) ); + if( !result ) { + Catch::cerr() + << Colour( Colour::Red ) + << "\nError(s) in input:\n" + << Column( result.errorMessage() ).indent( 2 ) + << "\n\n"; + Catch::cerr() << "Run with -? for usage\n" << std::endl; + return MaxExitCode; + } + + if( m_configData.showHelp ) + showHelp(); + if( m_configData.libIdentify ) + libIdentify(); + m_config.reset(); + return 0; + } + +#if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(UNICODE) + int Session::applyCommandLine( int argc, wchar_t const * const * argv ) { + + char **utf8Argv = new char *[ argc ]; + + for ( int i = 0; i < argc; ++i ) { + int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, NULL, 0, NULL, NULL ); + + utf8Argv[ i ] = new char[ bufSize ]; + + WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, NULL, NULL ); + } + + int returnCode = applyCommandLine( argc, utf8Argv ); + + for ( int i = 0; i < argc; ++i ) + delete [] utf8Argv[ i ]; + + delete [] utf8Argv; + + return returnCode; + } +#endif + + void Session::useConfigData( ConfigData const& configData ) { + m_configData = configData; + m_config.reset(); + } + + int Session::run() { + if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) { + Catch::cout() << "...waiting for enter/ return before starting" << std::endl; + static_cast<void>(std::getchar()); + } + int exitCode = runInternal(); + if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) { + Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << std::endl; + static_cast<void>(std::getchar()); + } + return exitCode; + } + + clara::Parser const& Session::cli() const { + return m_cli; + } + void Session::cli( clara::Parser const& newParser ) { + m_cli = newParser; + } + ConfigData& Session::configData() { + return m_configData; + } + Config& Session::config() { + if( !m_config ) + m_config = std::make_shared<Config>( m_configData ); + return *m_config; + } + + int Session::runInternal() { + if( m_startupExceptions ) + return 1; + + if (m_configData.showHelp || m_configData.libIdentify) { + return 0; + } + + CATCH_TRY { + config(); // Force config to be constructed + + seedRng( *m_config ); + + if( m_configData.filenamesAsTags ) + applyFilenamesAsTags( *m_config ); + + // Handle list request + if( Option<std::size_t> listed = list( config() ) ) + return static_cast<int>( *listed ); + + auto totals = runTests( m_config ); + // Note that on unices only the lower 8 bits are usually used, clamping + // the return value to 255 prevents false negative when some multiple + // of 256 tests has failed + return (std::min) (MaxExitCode, (std::max) (totals.error, static_cast<int>(totals.assertions.failed))); + } +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + catch( std::exception& ex ) { + Catch::cerr() << ex.what() << std::endl; + return MaxExitCode; + } +#endif + } + +} // end namespace Catch +// end catch_session.cpp +// start catch_singletons.cpp + +#include <vector> + +namespace Catch { + + namespace { + static auto getSingletons() -> std::vector<ISingleton*>*& { + static std::vector<ISingleton*>* g_singletons = nullptr; + if( !g_singletons ) + g_singletons = new std::vector<ISingleton*>(); + return g_singletons; + } + } + + ISingleton::~ISingleton() {} + + void addSingleton(ISingleton* singleton ) { + getSingletons()->push_back( singleton ); + } + void cleanupSingletons() { + auto& singletons = getSingletons(); + for( auto singleton : *singletons ) + delete singleton; + delete singletons; + singletons = nullptr; + } + +} // namespace Catch +// end catch_singletons.cpp +// start catch_startup_exception_registry.cpp + +namespace Catch { +void StartupExceptionRegistry::add( std::exception_ptr const& exception ) noexcept { + CATCH_TRY { + m_exceptions.push_back(exception); + } CATCH_CATCH_ALL { + // If we run out of memory during start-up there's really not a lot more we can do about it + std::terminate(); + } + } + + std::vector<std::exception_ptr> const& StartupExceptionRegistry::getExceptions() const noexcept { + return m_exceptions; + } + +} // end namespace Catch +// end catch_startup_exception_registry.cpp +// start catch_stream.cpp + +#include <cstdio> +#include <iostream> +#include <fstream> +#include <sstream> +#include <vector> +#include <memory> + +namespace Catch { + + Catch::IStream::~IStream() = default; + + namespace detail { namespace { + template<typename WriterF, std::size_t bufferSize=256> + class StreamBufImpl : public std::streambuf { + char data[bufferSize]; + WriterF m_writer; + + public: + StreamBufImpl() { + setp( data, data + sizeof(data) ); + } + + ~StreamBufImpl() noexcept { + StreamBufImpl::sync(); + } + + private: + int overflow( int c ) override { + sync(); + + if( c != EOF ) { + if( pbase() == epptr() ) + m_writer( std::string( 1, static_cast<char>( c ) ) ); + else + sputc( static_cast<char>( c ) ); + } + return 0; + } + + int sync() override { + if( pbase() != pptr() ) { + m_writer( std::string( pbase(), static_cast<std::string::size_type>( pptr() - pbase() ) ) ); + setp( pbase(), epptr() ); + } + return 0; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + struct OutputDebugWriter { + + void operator()( std::string const&str ) { + writeToDebugConsole( str ); + } + }; + + /////////////////////////////////////////////////////////////////////////// + + class FileStream : public IStream { + mutable std::ofstream m_ofs; + public: + FileStream( StringRef filename ) { + m_ofs.open( filename.c_str() ); + CATCH_ENFORCE( !m_ofs.fail(), "Unable to open file: '" << filename << "'" ); + } + ~FileStream() override = default; + public: // IStream + std::ostream& stream() const override { + return m_ofs; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + class CoutStream : public IStream { + mutable std::ostream m_os; + public: + // Store the streambuf from cout up-front because + // cout may get redirected when running tests + CoutStream() : m_os( Catch::cout().rdbuf() ) {} + ~CoutStream() override = default; + + public: // IStream + std::ostream& stream() const override { return m_os; } + }; + + /////////////////////////////////////////////////////////////////////////// + + class DebugOutStream : public IStream { + std::unique_ptr<StreamBufImpl<OutputDebugWriter>> m_streamBuf; + mutable std::ostream m_os; + public: + DebugOutStream() + : m_streamBuf( new StreamBufImpl<OutputDebugWriter>() ), + m_os( m_streamBuf.get() ) + {} + + ~DebugOutStream() override = default; + + public: // IStream + std::ostream& stream() const override { return m_os; } + }; + + }} // namespace anon::detail + + /////////////////////////////////////////////////////////////////////////// + + auto makeStream( StringRef const &filename ) -> IStream const* { + if( filename.empty() ) + return new detail::CoutStream(); + else if( filename[0] == '%' ) { + if( filename == "%debug" ) + return new detail::DebugOutStream(); + else + CATCH_ERROR( "Unrecognised stream: '" << filename << "'" ); + } + else + return new detail::FileStream( filename ); + } + + // This class encapsulates the idea of a pool of ostringstreams that can be reused. + struct StringStreams { + std::vector<std::unique_ptr<std::ostringstream>> m_streams; + std::vector<std::size_t> m_unused; + std::ostringstream m_referenceStream; // Used for copy state/ flags from + + auto add() -> std::size_t { + if( m_unused.empty() ) { + m_streams.push_back( std::unique_ptr<std::ostringstream>( new std::ostringstream ) ); + return m_streams.size()-1; + } + else { + auto index = m_unused.back(); + m_unused.pop_back(); + return index; + } + } + + void release( std::size_t index ) { + m_streams[index]->copyfmt( m_referenceStream ); // Restore initial flags and other state + m_unused.push_back(index); + } + }; + + ReusableStringStream::ReusableStringStream() + : m_index( Singleton<StringStreams>::getMutable().add() ), + m_oss( Singleton<StringStreams>::getMutable().m_streams[m_index].get() ) + {} + + ReusableStringStream::~ReusableStringStream() { + static_cast<std::ostringstream*>( m_oss )->str(""); + m_oss->clear(); + Singleton<StringStreams>::getMutable().release( m_index ); + } + + auto ReusableStringStream::str() const -> std::string { + return static_cast<std::ostringstream*>( m_oss )->str(); + } + + /////////////////////////////////////////////////////////////////////////// + +#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions + std::ostream& cout() { return std::cout; } + std::ostream& cerr() { return std::cerr; } + std::ostream& clog() { return std::clog; } +#endif +} +// end catch_stream.cpp +// start catch_string_manip.cpp + +#include <algorithm> +#include <ostream> +#include <cstring> +#include <cctype> + +namespace Catch { + + namespace { + char toLowerCh(char c) { + return static_cast<char>( std::tolower( c ) ); + } + } + + bool startsWith( std::string const& s, std::string const& prefix ) { + return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin()); + } + bool startsWith( std::string const& s, char prefix ) { + return !s.empty() && s[0] == prefix; + } + bool endsWith( std::string const& s, std::string const& suffix ) { + return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin()); + } + bool endsWith( std::string const& s, char suffix ) { + return !s.empty() && s[s.size()-1] == suffix; + } + bool contains( std::string const& s, std::string const& infix ) { + return s.find( infix ) != std::string::npos; + } + void toLowerInPlace( std::string& s ) { + std::transform( s.begin(), s.end(), s.begin(), toLowerCh ); + } + std::string toLower( std::string const& s ) { + std::string lc = s; + toLowerInPlace( lc ); + return lc; + } + std::string trim( std::string const& str ) { + static char const* whitespaceChars = "\n\r\t "; + std::string::size_type start = str.find_first_not_of( whitespaceChars ); + std::string::size_type end = str.find_last_not_of( whitespaceChars ); + + return start != std::string::npos ? str.substr( start, 1+end-start ) : std::string(); + } + + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { + bool replaced = false; + std::size_t i = str.find( replaceThis ); + while( i != std::string::npos ) { + replaced = true; + str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); + if( i < str.size()-withThis.size() ) + i = str.find( replaceThis, i+withThis.size() ); + else + i = std::string::npos; + } + return replaced; + } + + pluralise::pluralise( std::size_t count, std::string const& label ) + : m_count( count ), + m_label( label ) + {} + + std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { + os << pluraliser.m_count << ' ' << pluraliser.m_label; + if( pluraliser.m_count != 1 ) + os << 's'; + return os; + } + +} +// end catch_string_manip.cpp +// start catch_stringref.cpp + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +#include <ostream> +#include <cstring> +#include <cstdint> + +namespace { + const uint32_t byte_2_lead = 0xC0; + const uint32_t byte_3_lead = 0xE0; + const uint32_t byte_4_lead = 0xF0; +} + +namespace Catch { + StringRef::StringRef( char const* rawChars ) noexcept + : StringRef( rawChars, static_cast<StringRef::size_type>(std::strlen(rawChars) ) ) + {} + + StringRef::operator std::string() const { + return std::string( m_start, m_size ); + } + + void StringRef::swap( StringRef& other ) noexcept { + std::swap( m_start, other.m_start ); + std::swap( m_size, other.m_size ); + std::swap( m_data, other.m_data ); + } + + auto StringRef::c_str() const -> char const* { + if( isSubstring() ) + const_cast<StringRef*>( this )->takeOwnership(); + return m_start; + } + auto StringRef::currentData() const noexcept -> char const* { + return m_start; + } + + auto StringRef::isOwned() const noexcept -> bool { + return m_data != nullptr; + } + auto StringRef::isSubstring() const noexcept -> bool { + return m_start[m_size] != '\0'; + } + + void StringRef::takeOwnership() { + if( !isOwned() ) { + m_data = new char[m_size+1]; + memcpy( m_data, m_start, m_size ); + m_data[m_size] = '\0'; + m_start = m_data; + } + } + auto StringRef::substr( size_type start, size_type size ) const noexcept -> StringRef { + if( start < m_size ) + return StringRef( m_start+start, size ); + else + return StringRef(); + } + auto StringRef::operator == ( StringRef const& other ) const noexcept -> bool { + return + size() == other.size() && + (std::strncmp( m_start, other.m_start, size() ) == 0); + } + auto StringRef::operator != ( StringRef const& other ) const noexcept -> bool { + return !operator==( other ); + } + + auto StringRef::operator[](size_type index) const noexcept -> char { + return m_start[index]; + } + + auto StringRef::numberOfCharacters() const noexcept -> size_type { + size_type noChars = m_size; + // Make adjustments for uft encodings + for( size_type i=0; i < m_size; ++i ) { + char c = m_start[i]; + if( ( c & byte_2_lead ) == byte_2_lead ) { + noChars--; + if (( c & byte_3_lead ) == byte_3_lead ) + noChars--; + if( ( c & byte_4_lead ) == byte_4_lead ) + noChars--; + } + } + return noChars; + } + + auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string { + std::string str; + str.reserve( lhs.size() + rhs.size() ); + str += lhs; + str += rhs; + return str; + } + auto operator + ( StringRef const& lhs, const char* rhs ) -> std::string { + return std::string( lhs ) + std::string( rhs ); + } + auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string { + return std::string( lhs ) + std::string( rhs ); + } + + auto operator << ( std::ostream& os, StringRef const& str ) -> std::ostream& { + return os.write(str.currentData(), str.size()); + } + + auto operator+=( std::string& lhs, StringRef const& rhs ) -> std::string& { + lhs.append(rhs.currentData(), rhs.size()); + return lhs; + } + +} // namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif +// end catch_stringref.cpp +// start catch_tag_alias.cpp + +namespace Catch { + TagAlias::TagAlias(std::string const & _tag, SourceLineInfo _lineInfo): tag(_tag), lineInfo(_lineInfo) {} +} +// end catch_tag_alias.cpp +// start catch_tag_alias_autoregistrar.cpp + +namespace Catch { + + RegistrarForTagAliases::RegistrarForTagAliases(char const* alias, char const* tag, SourceLineInfo const& lineInfo) { + CATCH_TRY { + getMutableRegistryHub().registerTagAlias(alias, tag, lineInfo); + } CATCH_CATCH_ALL { + // Do not throw when constructing global objects, instead register the exception to be processed later + getMutableRegistryHub().registerStartupException(); + } + } + +} +// end catch_tag_alias_autoregistrar.cpp +// start catch_tag_alias_registry.cpp + +#include <sstream> + +namespace Catch { + + TagAliasRegistry::~TagAliasRegistry() {} + + TagAlias const* TagAliasRegistry::find( std::string const& alias ) const { + auto it = m_registry.find( alias ); + if( it != m_registry.end() ) + return &(it->second); + else + return nullptr; + } + + std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { + std::string expandedTestSpec = unexpandedTestSpec; + for( auto const& registryKvp : m_registry ) { + std::size_t pos = expandedTestSpec.find( registryKvp.first ); + if( pos != std::string::npos ) { + expandedTestSpec = expandedTestSpec.substr( 0, pos ) + + registryKvp.second.tag + + expandedTestSpec.substr( pos + registryKvp.first.size() ); + } + } + return expandedTestSpec; + } + + void TagAliasRegistry::add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) { + CATCH_ENFORCE( startsWith(alias, "[@") && endsWith(alias, ']'), + "error: tag alias, '" << alias << "' is not of the form [@alias name].\n" << lineInfo ); + + CATCH_ENFORCE( m_registry.insert(std::make_pair(alias, TagAlias(tag, lineInfo))).second, + "error: tag alias, '" << alias << "' already registered.\n" + << "\tFirst seen at: " << find(alias)->lineInfo << "\n" + << "\tRedefined at: " << lineInfo ); + } + + ITagAliasRegistry::~ITagAliasRegistry() {} + + ITagAliasRegistry const& ITagAliasRegistry::get() { + return getRegistryHub().getTagAliasRegistry(); + } + +} // end namespace Catch +// end catch_tag_alias_registry.cpp +// start catch_test_case_info.cpp + +#include <cctype> +#include <exception> +#include <algorithm> +#include <sstream> + +namespace Catch { + + namespace { + TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { + if( startsWith( tag, '.' ) || + tag == "!hide" ) + return TestCaseInfo::IsHidden; + else if( tag == "!throws" ) + return TestCaseInfo::Throws; + else if( tag == "!shouldfail" ) + return TestCaseInfo::ShouldFail; + else if( tag == "!mayfail" ) + return TestCaseInfo::MayFail; + else if( tag == "!nonportable" ) + return TestCaseInfo::NonPortable; + else if( tag == "!benchmark" ) + return static_cast<TestCaseInfo::SpecialProperties>( TestCaseInfo::Benchmark | TestCaseInfo::IsHidden ); + else + return TestCaseInfo::None; + } + bool isReservedTag( std::string const& tag ) { + return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( static_cast<unsigned char>(tag[0]) ); + } + void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { + CATCH_ENFORCE( !isReservedTag(tag), + "Tag name: [" << tag << "] is not allowed.\n" + << "Tag names starting with non alpha-numeric characters are reserved\n" + << _lineInfo ); + } + } + + TestCase makeTestCase( ITestInvoker* _testCase, + std::string const& _className, + NameAndTags const& nameAndTags, + SourceLineInfo const& _lineInfo ) + { + bool isHidden = false; + + // Parse out tags + std::vector<std::string> tags; + std::string desc, tag; + bool inTag = false; + std::string _descOrTags = nameAndTags.tags; + for (char c : _descOrTags) { + if( !inTag ) { + if( c == '[' ) + inTag = true; + else + desc += c; + } + else { + if( c == ']' ) { + TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); + if( ( prop & TestCaseInfo::IsHidden ) != 0 ) + isHidden = true; + else if( prop == TestCaseInfo::None ) + enforceNotReservedTag( tag, _lineInfo ); + + tags.push_back( tag ); + tag.clear(); + inTag = false; + } + else + tag += c; + } + } + if( isHidden ) { + tags.push_back( "." ); + } + + TestCaseInfo info( nameAndTags.name, _className, desc, tags, _lineInfo ); + return TestCase( _testCase, std::move(info) ); + } + + void setTags( TestCaseInfo& testCaseInfo, std::vector<std::string> tags ) { + std::sort(begin(tags), end(tags)); + tags.erase(std::unique(begin(tags), end(tags)), end(tags)); + testCaseInfo.lcaseTags.clear(); + + for( auto const& tag : tags ) { + std::string lcaseTag = toLower( tag ); + testCaseInfo.properties = static_cast<TestCaseInfo::SpecialProperties>( testCaseInfo.properties | parseSpecialTag( lcaseTag ) ); + testCaseInfo.lcaseTags.push_back( lcaseTag ); + } + testCaseInfo.tags = std::move(tags); + } + + TestCaseInfo::TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::vector<std::string> const& _tags, + SourceLineInfo const& _lineInfo ) + : name( _name ), + className( _className ), + description( _description ), + lineInfo( _lineInfo ), + properties( None ) + { + setTags( *this, _tags ); + } + + bool TestCaseInfo::isHidden() const { + return ( properties & IsHidden ) != 0; + } + bool TestCaseInfo::throws() const { + return ( properties & Throws ) != 0; + } + bool TestCaseInfo::okToFail() const { + return ( properties & (ShouldFail | MayFail ) ) != 0; + } + bool TestCaseInfo::expectedToFail() const { + return ( properties & (ShouldFail ) ) != 0; + } + + std::string TestCaseInfo::tagsAsString() const { + std::string ret; + // '[' and ']' per tag + std::size_t full_size = 2 * tags.size(); + for (const auto& tag : tags) { + full_size += tag.size(); + } + ret.reserve(full_size); + for (const auto& tag : tags) { + ret.push_back('['); + ret.append(tag); + ret.push_back(']'); + } + + return ret; + } + + TestCase::TestCase( ITestInvoker* testCase, TestCaseInfo&& info ) : TestCaseInfo( std::move(info) ), test( testCase ) {} + + TestCase TestCase::withName( std::string const& _newName ) const { + TestCase other( *this ); + other.name = _newName; + return other; + } + + void TestCase::invoke() const { + test->invoke(); + } + + bool TestCase::operator == ( TestCase const& other ) const { + return test.get() == other.test.get() && + name == other.name && + className == other.className; + } + + bool TestCase::operator < ( TestCase const& other ) const { + return name < other.name; + } + + TestCaseInfo const& TestCase::getTestCaseInfo() const + { + return *this; + } + +} // end namespace Catch +// end catch_test_case_info.cpp +// start catch_test_case_registry_impl.cpp + +#include <sstream> + +namespace Catch { + + std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases ) { + + std::vector<TestCase> sorted = unsortedTestCases; + + switch( config.runOrder() ) { + case RunTests::InLexicographicalOrder: + std::sort( sorted.begin(), sorted.end() ); + break; + case RunTests::InRandomOrder: + seedRng( config ); + std::shuffle( sorted.begin(), sorted.end(), rng() ); + break; + case RunTests::InDeclarationOrder: + // already in declaration order + break; + } + return sorted; + } + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) { + return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() ); + } + + void enforceNoDuplicateTestCases( std::vector<TestCase> const& functions ) { + std::set<TestCase> seenFunctions; + for( auto const& function : functions ) { + auto prev = seenFunctions.insert( function ); + CATCH_ENFORCE( prev.second, + "error: TEST_CASE( \"" << function.name << "\" ) already defined.\n" + << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n" + << "\tRedefined at " << function.getTestCaseInfo().lineInfo ); + } + } + + std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config ) { + std::vector<TestCase> filtered; + filtered.reserve( testCases.size() ); + for( auto const& testCase : testCases ) + if( matchTest( testCase, testSpec, config ) ) + filtered.push_back( testCase ); + return filtered; + } + std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ) { + return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); + } + + void TestRegistry::registerTest( TestCase const& testCase ) { + std::string name = testCase.getTestCaseInfo().name; + if( name.empty() ) { + ReusableStringStream rss; + rss << "Anonymous test case " << ++m_unnamedCount; + return registerTest( testCase.withName( rss.str() ) ); + } + m_functions.push_back( testCase ); + } + + std::vector<TestCase> const& TestRegistry::getAllTests() const { + return m_functions; + } + std::vector<TestCase> const& TestRegistry::getAllTestsSorted( IConfig const& config ) const { + if( m_sortedFunctions.empty() ) + enforceNoDuplicateTestCases( m_functions ); + + if( m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) { + m_sortedFunctions = sortTests( config, m_functions ); + m_currentSortOrder = config.runOrder(); + } + return m_sortedFunctions; + } + + /////////////////////////////////////////////////////////////////////////// + TestInvokerAsFunction::TestInvokerAsFunction( void(*testAsFunction)() ) noexcept : m_testAsFunction( testAsFunction ) {} + + void TestInvokerAsFunction::invoke() const { + m_testAsFunction(); + } + + std::string extractClassName( StringRef const& classOrQualifiedMethodName ) { + std::string className = classOrQualifiedMethodName; + if( startsWith( className, '&' ) ) + { + std::size_t lastColons = className.rfind( "::" ); + std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); + if( penultimateColons == std::string::npos ) + penultimateColons = 1; + className = className.substr( penultimateColons, lastColons-penultimateColons ); + } + return className; + } + +} // end namespace Catch +// end catch_test_case_registry_impl.cpp +// start catch_test_case_tracker.cpp + +#include <algorithm> +#include <cassert> +#include <stdexcept> +#include <memory> +#include <sstream> + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +namespace Catch { +namespace TestCaseTracking { + + NameAndLocation::NameAndLocation( std::string const& _name, SourceLineInfo const& _location ) + : name( _name ), + location( _location ) + {} + + ITracker::~ITracker() = default; + + TrackerContext& TrackerContext::instance() { + static TrackerContext s_instance; + return s_instance; + } + + ITracker& TrackerContext::startRun() { + m_rootTracker = std::make_shared<SectionTracker>( NameAndLocation( "{root}", CATCH_INTERNAL_LINEINFO ), *this, nullptr ); + m_currentTracker = nullptr; + m_runState = Executing; + return *m_rootTracker; + } + + void TrackerContext::endRun() { + m_rootTracker.reset(); + m_currentTracker = nullptr; + m_runState = NotStarted; + } + + void TrackerContext::startCycle() { + m_currentTracker = m_rootTracker.get(); + m_runState = Executing; + } + void TrackerContext::completeCycle() { + m_runState = CompletedCycle; + } + + bool TrackerContext::completedCycle() const { + return m_runState == CompletedCycle; + } + ITracker& TrackerContext::currentTracker() { + return *m_currentTracker; + } + void TrackerContext::setCurrentTracker( ITracker* tracker ) { + m_currentTracker = tracker; + } + + TrackerBase::TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : m_nameAndLocation( nameAndLocation ), + m_ctx( ctx ), + m_parent( parent ) + {} + + NameAndLocation const& TrackerBase::nameAndLocation() const { + return m_nameAndLocation; + } + bool TrackerBase::isComplete() const { + return m_runState == CompletedSuccessfully || m_runState == Failed; + } + bool TrackerBase::isSuccessfullyCompleted() const { + return m_runState == CompletedSuccessfully; + } + bool TrackerBase::isOpen() const { + return m_runState != NotStarted && !isComplete(); + } + bool TrackerBase::hasChildren() const { + return !m_children.empty(); + } + + void TrackerBase::addChild( ITrackerPtr const& child ) { + m_children.push_back( child ); + } + + ITrackerPtr TrackerBase::findChild( NameAndLocation const& nameAndLocation ) { + auto it = std::find_if( m_children.begin(), m_children.end(), + [&nameAndLocation]( ITrackerPtr const& tracker ){ + return + tracker->nameAndLocation().location == nameAndLocation.location && + tracker->nameAndLocation().name == nameAndLocation.name; + } ); + return( it != m_children.end() ) + ? *it + : nullptr; + } + ITracker& TrackerBase::parent() { + assert( m_parent ); // Should always be non-null except for root + return *m_parent; + } + + void TrackerBase::openChild() { + if( m_runState != ExecutingChildren ) { + m_runState = ExecutingChildren; + if( m_parent ) + m_parent->openChild(); + } + } + + bool TrackerBase::isSectionTracker() const { return false; } + bool TrackerBase::isIndexTracker() const { return false; } + + void TrackerBase::open() { + m_runState = Executing; + moveToThis(); + if( m_parent ) + m_parent->openChild(); + } + + void TrackerBase::close() { + + // Close any still open children (e.g. generators) + while( &m_ctx.currentTracker() != this ) + m_ctx.currentTracker().close(); + + switch( m_runState ) { + case NeedsAnotherRun: + break; + + case Executing: + m_runState = CompletedSuccessfully; + break; + case ExecutingChildren: + if( m_children.empty() || m_children.back()->isComplete() ) + m_runState = CompletedSuccessfully; + break; + + case NotStarted: + case CompletedSuccessfully: + case Failed: + CATCH_INTERNAL_ERROR( "Illogical state: " << m_runState ); + + default: + CATCH_INTERNAL_ERROR( "Unknown state: " << m_runState ); + } + moveToParent(); + m_ctx.completeCycle(); + } + void TrackerBase::fail() { + m_runState = Failed; + if( m_parent ) + m_parent->markAsNeedingAnotherRun(); + moveToParent(); + m_ctx.completeCycle(); + } + void TrackerBase::markAsNeedingAnotherRun() { + m_runState = NeedsAnotherRun; + } + + void TrackerBase::moveToParent() { + assert( m_parent ); + m_ctx.setCurrentTracker( m_parent ); + } + void TrackerBase::moveToThis() { + m_ctx.setCurrentTracker( this ); + } + + SectionTracker::SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : TrackerBase( nameAndLocation, ctx, parent ) + { + if( parent ) { + while( !parent->isSectionTracker() ) + parent = &parent->parent(); + + SectionTracker& parentSection = static_cast<SectionTracker&>( *parent ); + addNextFilters( parentSection.m_filters ); + } + } + + bool SectionTracker::isSectionTracker() const { return true; } + + SectionTracker& SectionTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ) { + std::shared_ptr<SectionTracker> section; + + ITracker& currentTracker = ctx.currentTracker(); + if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isSectionTracker() ); + section = std::static_pointer_cast<SectionTracker>( childTracker ); + } + else { + section = std::make_shared<SectionTracker>( nameAndLocation, ctx, ¤tTracker ); + currentTracker.addChild( section ); + } + if( !ctx.completedCycle() ) + section->tryOpen(); + return *section; + } + + void SectionTracker::tryOpen() { + if( !isComplete() && (m_filters.empty() || m_filters[0].empty() || m_filters[0] == m_nameAndLocation.name ) ) + open(); + } + + void SectionTracker::addInitialFilters( std::vector<std::string> const& filters ) { + if( !filters.empty() ) { + m_filters.push_back(""); // Root - should never be consulted + m_filters.push_back(""); // Test Case - not a section filter + m_filters.insert( m_filters.end(), filters.begin(), filters.end() ); + } + } + void SectionTracker::addNextFilters( std::vector<std::string> const& filters ) { + if( filters.size() > 1 ) + m_filters.insert( m_filters.end(), ++filters.begin(), filters.end() ); + } + + IndexTracker::IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size ) + : TrackerBase( nameAndLocation, ctx, parent ), + m_size( size ) + {} + + bool IndexTracker::isIndexTracker() const { return true; } + + IndexTracker& IndexTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ) { + std::shared_ptr<IndexTracker> tracker; + + ITracker& currentTracker = ctx.currentTracker(); + if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isIndexTracker() ); + tracker = std::static_pointer_cast<IndexTracker>( childTracker ); + } + else { + tracker = std::make_shared<IndexTracker>( nameAndLocation, ctx, ¤tTracker, size ); + currentTracker.addChild( tracker ); + } + + if( !ctx.completedCycle() && !tracker->isComplete() ) { + if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun ) + tracker->moveNext(); + tracker->open(); + } + + return *tracker; + } + + int IndexTracker::index() const { return m_index; } + + void IndexTracker::moveNext() { + m_index++; + m_children.clear(); + } + + void IndexTracker::close() { + TrackerBase::close(); + if( m_runState == CompletedSuccessfully && m_index < m_size-1 ) + m_runState = Executing; + } + +} // namespace TestCaseTracking + +using TestCaseTracking::ITracker; +using TestCaseTracking::TrackerContext; +using TestCaseTracking::SectionTracker; +using TestCaseTracking::IndexTracker; + +} // namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif +// end catch_test_case_tracker.cpp +// start catch_test_registry.cpp + +namespace Catch { + + auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker* { + return new(std::nothrow) TestInvokerAsFunction( testAsFunction ); + } + + NameAndTags::NameAndTags( StringRef const& name_ , StringRef const& tags_ ) noexcept : name( name_ ), tags( tags_ ) {} + + AutoReg::AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept { + CATCH_TRY { + getMutableRegistryHub() + .registerTest( + makeTestCase( + invoker, + extractClassName( classOrMethod ), + nameAndTags, + lineInfo)); + } CATCH_CATCH_ALL { + // Do not throw when constructing global objects, instead register the exception to be processed later + getMutableRegistryHub().registerStartupException(); + } + } + + AutoReg::~AutoReg() = default; +} +// end catch_test_registry.cpp +// start catch_test_spec.cpp + +#include <algorithm> +#include <string> +#include <vector> +#include <memory> + +namespace Catch { + + TestSpec::Pattern::~Pattern() = default; + TestSpec::NamePattern::~NamePattern() = default; + TestSpec::TagPattern::~TagPattern() = default; + TestSpec::ExcludedPattern::~ExcludedPattern() = default; + + TestSpec::NamePattern::NamePattern( std::string const& name ) + : m_wildcardPattern( toLower( name ), CaseSensitive::No ) + {} + bool TestSpec::NamePattern::matches( TestCaseInfo const& testCase ) const { + return m_wildcardPattern.matches( toLower( testCase.name ) ); + } + + TestSpec::TagPattern::TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} + bool TestSpec::TagPattern::matches( TestCaseInfo const& testCase ) const { + return std::find(begin(testCase.lcaseTags), + end(testCase.lcaseTags), + m_tag) != end(testCase.lcaseTags); + } + + TestSpec::ExcludedPattern::ExcludedPattern( PatternPtr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} + bool TestSpec::ExcludedPattern::matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } + + bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const { + // All patterns in a filter must match for the filter to be a match + for( auto const& pattern : m_patterns ) { + if( !pattern->matches( testCase ) ) + return false; + } + return true; + } + + bool TestSpec::hasFilters() const { + return !m_filters.empty(); + } + bool TestSpec::matches( TestCaseInfo const& testCase ) const { + // A TestSpec matches if any filter matches + for( auto const& filter : m_filters ) + if( filter.matches( testCase ) ) + return true; + return false; + } +} +// end catch_test_spec.cpp +// start catch_test_spec_parser.cpp + +namespace Catch { + + TestSpecParser::TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} + + TestSpecParser& TestSpecParser::parse( std::string const& arg ) { + m_mode = None; + m_exclusion = false; + m_start = std::string::npos; + m_arg = m_tagAliases->expandAliases( arg ); + m_escapeChars.clear(); + for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) + visitChar( m_arg[m_pos] ); + if( m_mode == Name ) + addPattern<TestSpec::NamePattern>(); + return *this; + } + TestSpec TestSpecParser::testSpec() { + addFilter(); + return m_testSpec; + } + + void TestSpecParser::visitChar( char c ) { + if( m_mode == None ) { + switch( c ) { + case ' ': return; + case '~': m_exclusion = true; return; + case '[': return startNewMode( Tag, ++m_pos ); + case '"': return startNewMode( QuotedName, ++m_pos ); + case '\\': return escape(); + default: startNewMode( Name, m_pos ); break; + } + } + if( m_mode == Name ) { + if( c == ',' ) { + addPattern<TestSpec::NamePattern>(); + addFilter(); + } + else if( c == '[' ) { + if( subString() == "exclude:" ) + m_exclusion = true; + else + addPattern<TestSpec::NamePattern>(); + startNewMode( Tag, ++m_pos ); + } + else if( c == '\\' ) + escape(); + } + else if( m_mode == EscapedName ) + m_mode = Name; + else if( m_mode == QuotedName && c == '"' ) + addPattern<TestSpec::NamePattern>(); + else if( m_mode == Tag && c == ']' ) + addPattern<TestSpec::TagPattern>(); + } + void TestSpecParser::startNewMode( Mode mode, std::size_t start ) { + m_mode = mode; + m_start = start; + } + void TestSpecParser::escape() { + if( m_mode == None ) + m_start = m_pos; + m_mode = EscapedName; + m_escapeChars.push_back( m_pos ); + } + std::string TestSpecParser::subString() const { return m_arg.substr( m_start, m_pos - m_start ); } + + void TestSpecParser::addFilter() { + if( !m_currentFilter.m_patterns.empty() ) { + m_testSpec.m_filters.push_back( m_currentFilter ); + m_currentFilter = TestSpec::Filter(); + } + } + + TestSpec parseTestSpec( std::string const& arg ) { + return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); + } + +} // namespace Catch +// end catch_test_spec_parser.cpp +// start catch_timer.cpp + +#include <chrono> + +static const uint64_t nanosecondsInSecond = 1000000000; + +namespace Catch { + + auto getCurrentNanosecondsSinceEpoch() -> uint64_t { + return std::chrono::duration_cast<std::chrono::nanoseconds>( std::chrono::high_resolution_clock::now().time_since_epoch() ).count(); + } + + namespace { + auto estimateClockResolution() -> uint64_t { + uint64_t sum = 0; + static const uint64_t iterations = 1000000; + + auto startTime = getCurrentNanosecondsSinceEpoch(); + + for( std::size_t i = 0; i < iterations; ++i ) { + + uint64_t ticks; + uint64_t baseTicks = getCurrentNanosecondsSinceEpoch(); + do { + ticks = getCurrentNanosecondsSinceEpoch(); + } while( ticks == baseTicks ); + + auto delta = ticks - baseTicks; + sum += delta; + + // If we have been calibrating for over 3 seconds -- the clock + // is terrible and we should move on. + // TBD: How to signal that the measured resolution is probably wrong? + if (ticks > startTime + 3 * nanosecondsInSecond) { + return sum / i; + } + } + + // We're just taking the mean, here. To do better we could take the std. dev and exclude outliers + // - and potentially do more iterations if there's a high variance. + return sum/iterations; + } + } + auto getEstimatedClockResolution() -> uint64_t { + static auto s_resolution = estimateClockResolution(); + return s_resolution; + } + + void Timer::start() { + m_nanoseconds = getCurrentNanosecondsSinceEpoch(); + } + auto Timer::getElapsedNanoseconds() const -> uint64_t { + return getCurrentNanosecondsSinceEpoch() - m_nanoseconds; + } + auto Timer::getElapsedMicroseconds() const -> uint64_t { + return getElapsedNanoseconds()/1000; + } + auto Timer::getElapsedMilliseconds() const -> unsigned int { + return static_cast<unsigned int>(getElapsedMicroseconds()/1000); + } + auto Timer::getElapsedSeconds() const -> double { + return getElapsedMicroseconds()/1000000.0; + } + +} // namespace Catch +// end catch_timer.cpp +// start catch_tostring.cpp + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +# pragma clang diagnostic ignored "-Wglobal-constructors" +#endif + +// Enable specific decls locally +#if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +#endif + +#include <cmath> +#include <iomanip> + +namespace Catch { + +namespace Detail { + + const std::string unprintableString = "{?}"; + + namespace { + const int hexThreshold = 255; + + struct Endianness { + enum Arch { Big, Little }; + + static Arch which() { + union _{ + int asInt; + char asChar[sizeof (int)]; + } u; + + u.asInt = 1; + return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little; + } + }; + } + + std::string rawMemoryToString( const void *object, std::size_t size ) { + // Reverse order for little endian architectures + int i = 0, end = static_cast<int>( size ), inc = 1; + if( Endianness::which() == Endianness::Little ) { + i = end-1; + end = inc = -1; + } + + unsigned char const *bytes = static_cast<unsigned char const *>(object); + ReusableStringStream rss; + rss << "0x" << std::setfill('0') << std::hex; + for( ; i != end; i += inc ) + rss << std::setw(2) << static_cast<unsigned>(bytes[i]); + return rss.str(); + } +} + +template<typename T> +std::string fpToString( T value, int precision ) { + if (Catch::isnan(value)) { + return "nan"; + } + + ReusableStringStream rss; + rss << std::setprecision( precision ) + << std::fixed + << value; + std::string d = rss.str(); + std::size_t i = d.find_last_not_of( '0' ); + if( i != std::string::npos && i != d.size()-1 ) { + if( d[i] == '.' ) + i++; + d = d.substr( 0, i+1 ); + } + return d; +} + +//// ======================================================= //// +// +// Out-of-line defs for full specialization of StringMaker +// +//// ======================================================= //// + +std::string StringMaker<std::string>::convert(const std::string& str) { + if (!getCurrentContext().getConfig()->showInvisibles()) { + return '"' + str + '"'; + } + + std::string s("\""); + for (char c : str) { + switch (c) { + case '\n': + s.append("\\n"); + break; + case '\t': + s.append("\\t"); + break; + default: + s.push_back(c); + break; + } + } + s.append("\""); + return s; +} + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW +std::string StringMaker<std::string_view>::convert(std::string_view str) { + return ::Catch::Detail::stringify(std::string{ str }); +} +#endif + +std::string StringMaker<char const*>::convert(char const* str) { + if (str) { + return ::Catch::Detail::stringify(std::string{ str }); + } else { + return{ "{null string}" }; + } +} +std::string StringMaker<char*>::convert(char* str) { + if (str) { + return ::Catch::Detail::stringify(std::string{ str }); + } else { + return{ "{null string}" }; + } +} + +#ifdef CATCH_CONFIG_WCHAR +std::string StringMaker<std::wstring>::convert(const std::wstring& wstr) { + std::string s; + s.reserve(wstr.size()); + for (auto c : wstr) { + s += (c <= 0xff) ? static_cast<char>(c) : '?'; + } + return ::Catch::Detail::stringify(s); +} + +# ifdef CATCH_CONFIG_CPP17_STRING_VIEW +std::string StringMaker<std::wstring_view>::convert(std::wstring_view str) { + return StringMaker<std::wstring>::convert(std::wstring(str)); +} +# endif + +std::string StringMaker<wchar_t const*>::convert(wchar_t const * str) { + if (str) { + return ::Catch::Detail::stringify(std::wstring{ str }); + } else { + return{ "{null string}" }; + } +} +std::string StringMaker<wchar_t *>::convert(wchar_t * str) { + if (str) { + return ::Catch::Detail::stringify(std::wstring{ str }); + } else { + return{ "{null string}" }; + } +} +#endif + +std::string StringMaker<int>::convert(int value) { + return ::Catch::Detail::stringify(static_cast<long long>(value)); +} +std::string StringMaker<long>::convert(long value) { + return ::Catch::Detail::stringify(static_cast<long long>(value)); +} +std::string StringMaker<long long>::convert(long long value) { + ReusableStringStream rss; + rss << value; + if (value > Detail::hexThreshold) { + rss << " (0x" << std::hex << value << ')'; + } + return rss.str(); +} + +std::string StringMaker<unsigned int>::convert(unsigned int value) { + return ::Catch::Detail::stringify(static_cast<unsigned long long>(value)); +} +std::string StringMaker<unsigned long>::convert(unsigned long value) { + return ::Catch::Detail::stringify(static_cast<unsigned long long>(value)); +} +std::string StringMaker<unsigned long long>::convert(unsigned long long value) { + ReusableStringStream rss; + rss << value; + if (value > Detail::hexThreshold) { + rss << " (0x" << std::hex << value << ')'; + } + return rss.str(); +} + +std::string StringMaker<bool>::convert(bool b) { + return b ? "true" : "false"; +} + +std::string StringMaker<signed char>::convert(signed char value) { + if (value == '\r') { + return "'\\r'"; + } else if (value == '\f') { + return "'\\f'"; + } else if (value == '\n') { + return "'\\n'"; + } else if (value == '\t') { + return "'\\t'"; + } else if ('\0' <= value && value < ' ') { + return ::Catch::Detail::stringify(static_cast<unsigned int>(value)); + } else { + char chstr[] = "' '"; + chstr[1] = value; + return chstr; + } +} +std::string StringMaker<char>::convert(char c) { + return ::Catch::Detail::stringify(static_cast<signed char>(c)); +} +std::string StringMaker<unsigned char>::convert(unsigned char c) { + return ::Catch::Detail::stringify(static_cast<char>(c)); +} + +std::string StringMaker<std::nullptr_t>::convert(std::nullptr_t) { + return "nullptr"; +} + +std::string StringMaker<float>::convert(float value) { + return fpToString(value, 5) + 'f'; +} +std::string StringMaker<double>::convert(double value) { + return fpToString(value, 10); +} + +std::string ratio_string<std::atto>::symbol() { return "a"; } +std::string ratio_string<std::femto>::symbol() { return "f"; } +std::string ratio_string<std::pico>::symbol() { return "p"; } +std::string ratio_string<std::nano>::symbol() { return "n"; } +std::string ratio_string<std::micro>::symbol() { return "u"; } +std::string ratio_string<std::milli>::symbol() { return "m"; } + +} // end namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +// end catch_tostring.cpp +// start catch_totals.cpp + +namespace Catch { + + Counts Counts::operator - ( Counts const& other ) const { + Counts diff; + diff.passed = passed - other.passed; + diff.failed = failed - other.failed; + diff.failedButOk = failedButOk - other.failedButOk; + return diff; + } + + Counts& Counts::operator += ( Counts const& other ) { + passed += other.passed; + failed += other.failed; + failedButOk += other.failedButOk; + return *this; + } + + std::size_t Counts::total() const { + return passed + failed + failedButOk; + } + bool Counts::allPassed() const { + return failed == 0 && failedButOk == 0; + } + bool Counts::allOk() const { + return failed == 0; + } + + Totals Totals::operator - ( Totals const& other ) const { + Totals diff; + diff.assertions = assertions - other.assertions; + diff.testCases = testCases - other.testCases; + return diff; + } + + Totals& Totals::operator += ( Totals const& other ) { + assertions += other.assertions; + testCases += other.testCases; + return *this; + } + + Totals Totals::delta( Totals const& prevTotals ) const { + Totals diff = *this - prevTotals; + if( diff.assertions.failed > 0 ) + ++diff.testCases.failed; + else if( diff.assertions.failedButOk > 0 ) + ++diff.testCases.failedButOk; + else + ++diff.testCases.passed; + return diff; + } + +} +// end catch_totals.cpp +// start catch_uncaught_exceptions.cpp + +#include <exception> + +namespace Catch { + bool uncaught_exceptions() { +#if defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) + return std::uncaught_exceptions() > 0; +#else + return std::uncaught_exception(); +#endif + } +} // end namespace Catch +// end catch_uncaught_exceptions.cpp +// start catch_version.cpp + +#include <ostream> + +namespace Catch { + + Version::Version + ( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const * const _branchName, + unsigned int _buildNumber ) + : majorVersion( _majorVersion ), + minorVersion( _minorVersion ), + patchNumber( _patchNumber ), + branchName( _branchName ), + buildNumber( _buildNumber ) + {} + + std::ostream& operator << ( std::ostream& os, Version const& version ) { + os << version.majorVersion << '.' + << version.minorVersion << '.' + << version.patchNumber; + // branchName is never null -> 0th char is \0 if it is empty + if (version.branchName[0]) { + os << '-' << version.branchName + << '.' << version.buildNumber; + } + return os; + } + + Version const& libraryVersion() { + static Version version( 2, 5, 0, "", 0 ); + return version; + } + +} +// end catch_version.cpp +// start catch_wildcard_pattern.cpp + +#include <sstream> + +namespace Catch { + + WildcardPattern::WildcardPattern( std::string const& pattern, + CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_pattern( adjustCase( pattern ) ) + { + if( startsWith( m_pattern, '*' ) ) { + m_pattern = m_pattern.substr( 1 ); + m_wildcard = WildcardAtStart; + } + if( endsWith( m_pattern, '*' ) ) { + m_pattern = m_pattern.substr( 0, m_pattern.size()-1 ); + m_wildcard = static_cast<WildcardPosition>( m_wildcard | WildcardAtEnd ); + } + } + + bool WildcardPattern::matches( std::string const& str ) const { + switch( m_wildcard ) { + case NoWildcard: + return m_pattern == adjustCase( str ); + case WildcardAtStart: + return endsWith( adjustCase( str ), m_pattern ); + case WildcardAtEnd: + return startsWith( adjustCase( str ), m_pattern ); + case WildcardAtBothEnds: + return contains( adjustCase( str ), m_pattern ); + default: + CATCH_INTERNAL_ERROR( "Unknown enum" ); + } + } + + std::string WildcardPattern::adjustCase( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str; + } +} +// end catch_wildcard_pattern.cpp +// start catch_xmlwriter.cpp + +#include <iomanip> + +using uchar = unsigned char; + +namespace Catch { + +namespace { + + size_t trailingBytes(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return 2; + } + if ((c & 0xF0) == 0xE0) { + return 3; + } + if ((c & 0xF8) == 0xF0) { + return 4; + } + CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + uint32_t headerValue(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return c & 0x1F; + } + if ((c & 0xF0) == 0xE0) { + return c & 0x0F; + } + if ((c & 0xF8) == 0xF0) { + return c & 0x07; + } + CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + void hexEscapeChar(std::ostream& os, unsigned char c) { + os << "\\x" + << std::uppercase << std::hex << std::setfill('0') << std::setw(2) + << static_cast<int>(c); + } + +} // anonymous namespace + + XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat ) + : m_str( str ), + m_forWhat( forWhat ) + {} + + void XmlEncode::encodeTo( std::ostream& os ) const { + // Apostrophe escaping not necessary if we always use " to write attributes + // (see: http://www.w3.org/TR/xml/#syntax) + + for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) { + uchar c = m_str[idx]; + switch (c) { + case '<': os << "<"; break; + case '&': os << "&"; break; + + case '>': + // See: http://www.w3.org/TR/xml/#syntax + if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') + os << ">"; + else + os << c; + break; + + case '\"': + if (m_forWhat == ForAttributes) + os << """; + else + os << c; + break; + + default: + // Check for control characters and invalid utf-8 + + // Escape control characters in standard ascii + // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 + if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) { + hexEscapeChar(os, c); + break; + } + + // Plain ASCII: Write it to stream + if (c < 0x7F) { + os << c; + break; + } + + // UTF-8 territory + // Check if the encoding is valid and if it is not, hex escape bytes. + // Important: We do not check the exact decoded values for validity, only the encoding format + // First check that this bytes is a valid lead byte: + // This means that it is not encoded as 1111 1XXX + // Or as 10XX XXXX + if (c < 0xC0 || + c >= 0xF8) { + hexEscapeChar(os, c); + break; + } + + auto encBytes = trailingBytes(c); + // Are there enough bytes left to avoid accessing out-of-bounds memory? + if (idx + encBytes - 1 >= m_str.size()) { + hexEscapeChar(os, c); + break; + } + // The header is valid, check data + // The next encBytes bytes must together be a valid utf-8 + // This means: bitpattern 10XX XXXX and the extracted value is sane (ish) + bool valid = true; + uint32_t value = headerValue(c); + for (std::size_t n = 1; n < encBytes; ++n) { + uchar nc = m_str[idx + n]; + valid &= ((nc & 0xC0) == 0x80); + value = (value << 6) | (nc & 0x3F); + } + + if ( + // Wrong bit pattern of following bytes + (!valid) || + // Overlong encodings + (value < 0x80) || + (0x80 <= value && value < 0x800 && encBytes > 2) || + (0x800 < value && value < 0x10000 && encBytes > 3) || + // Encoded value out of range + (value >= 0x110000) + ) { + hexEscapeChar(os, c); + break; + } + + // If we got here, this is in fact a valid(ish) utf-8 sequence + for (std::size_t n = 0; n < encBytes; ++n) { + os << m_str[idx + n]; + } + idx += encBytes - 1; + break; + } + } + } + + std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { + xmlEncode.encodeTo( os ); + return os; + } + + XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer ) + : m_writer( writer ) + {} + + XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept + : m_writer( other.m_writer ){ + other.m_writer = nullptr; + } + XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept { + if ( m_writer ) { + m_writer->endElement(); + } + m_writer = other.m_writer; + other.m_writer = nullptr; + return *this; + } + + XmlWriter::ScopedElement::~ScopedElement() { + if( m_writer ) + m_writer->endElement(); + } + + XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) { + m_writer->writeText( text, indent ); + return *this; + } + + XmlWriter::XmlWriter( std::ostream& os ) : m_os( os ) + { + writeDeclaration(); + } + + XmlWriter::~XmlWriter() { + while( !m_tags.empty() ) + endElement(); + } + + XmlWriter& XmlWriter::startElement( std::string const& name ) { + ensureTagClosed(); + newlineIfNecessary(); + m_os << m_indent << '<' << name; + m_tags.push_back( name ); + m_indent += " "; + m_tagIsOpen = true; + return *this; + } + + XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) { + ScopedElement scoped( this ); + startElement( name ); + return scoped; + } + + XmlWriter& XmlWriter::endElement() { + newlineIfNecessary(); + m_indent = m_indent.substr( 0, m_indent.size()-2 ); + if( m_tagIsOpen ) { + m_os << "/>"; + m_tagIsOpen = false; + } + else { + m_os << m_indent << "</" << m_tags.back() << ">"; + } + m_os << std::endl; + m_tags.pop_back(); + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) { + if( !name.empty() && !attribute.empty() ) + m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) { + m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) { + if( !text.empty() ){ + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if( tagWasOpen && indent ) + m_os << m_indent; + m_os << XmlEncode( text ); + m_needsNewline = true; + } + return *this; + } + + XmlWriter& XmlWriter::writeComment( std::string const& text ) { + ensureTagClosed(); + m_os << m_indent << "<!--" << text << "-->"; + m_needsNewline = true; + return *this; + } + + void XmlWriter::writeStylesheetRef( std::string const& url ) { + m_os << "<?xml-stylesheet type=\"text/xsl\" href=\"" << url << "\"?>\n"; + } + + XmlWriter& XmlWriter::writeBlankLine() { + ensureTagClosed(); + m_os << '\n'; + return *this; + } + + void XmlWriter::ensureTagClosed() { + if( m_tagIsOpen ) { + m_os << ">" << std::endl; + m_tagIsOpen = false; + } + } + + void XmlWriter::writeDeclaration() { + m_os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; + } + + void XmlWriter::newlineIfNecessary() { + if( m_needsNewline ) { + m_os << std::endl; + m_needsNewline = false; + } + } +} +// end catch_xmlwriter.cpp +// start catch_reporter_bases.cpp + +#include <cstring> +#include <cfloat> +#include <cstdio> +#include <cassert> +#include <memory> + +namespace Catch { + void prepareExpandedExpression(AssertionResult& result) { + result.getExpandedExpression(); + } + + // Because formatting using c++ streams is stateful, drop down to C is required + // Alternatively we could use stringstream, but its performance is... not good. + std::string getFormattedDuration( double duration ) { + // Max exponent + 1 is required to represent the whole part + // + 1 for decimal point + // + 3 for the 3 decimal places + // + 1 for null terminator + const std::size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1; + char buffer[maxDoubleSize]; + + // Save previous errno, to prevent sprintf from overwriting it + ErrnoGuard guard; +#ifdef _MSC_VER + sprintf_s(buffer, "%.3f", duration); +#else + sprintf(buffer, "%.3f", duration); +#endif + return std::string(buffer); + } + + TestEventListenerBase::TestEventListenerBase(ReporterConfig const & _config) + :StreamingReporterBase(_config) {} + + std::set<Verbosity> TestEventListenerBase::getSupportedVerbosities() { + return { Verbosity::Quiet, Verbosity::Normal, Verbosity::High }; + } + + void TestEventListenerBase::assertionStarting(AssertionInfo const &) {} + + bool TestEventListenerBase::assertionEnded(AssertionStats const &) { + return false; + } + +} // end namespace Catch +// end catch_reporter_bases.cpp +// start catch_reporter_compact.cpp + +namespace { + +#ifdef CATCH_PLATFORM_MAC + const char* failedString() { return "FAILED"; } + const char* passedString() { return "PASSED"; } +#else + const char* failedString() { return "failed"; } + const char* passedString() { return "passed"; } +#endif + + // Colour::LightGrey + Catch::Colour::Code dimColour() { return Catch::Colour::FileName; } + + std::string bothOrAll( std::size_t count ) { + return count == 1 ? std::string() : + count == 2 ? "both " : "all " ; + } + +} // anon namespace + +namespace Catch { +namespace { +// Colour, message variants: +// - white: No tests ran. +// - red: Failed [both/all] N test cases, failed [both/all] M assertions. +// - white: Passed [both/all] N test cases (no assertions). +// - red: Failed N tests cases, failed M assertions. +// - green: Passed [both/all] N tests cases with M assertions. +void printTotals(std::ostream& out, const Totals& totals) { + if (totals.testCases.total() == 0) { + out << "No tests ran."; + } else if (totals.testCases.failed == totals.testCases.total()) { + Colour colour(Colour::ResultError); + const std::string qualify_assertions_failed = + totals.assertions.failed == totals.assertions.total() ? + bothOrAll(totals.assertions.failed) : std::string(); + out << + "Failed " << bothOrAll(totals.testCases.failed) + << pluralise(totals.testCases.failed, "test case") << ", " + "failed " << qualify_assertions_failed << + pluralise(totals.assertions.failed, "assertion") << '.'; + } else if (totals.assertions.total() == 0) { + out << + "Passed " << bothOrAll(totals.testCases.total()) + << pluralise(totals.testCases.total(), "test case") + << " (no assertions)."; + } else if (totals.assertions.failed) { + Colour colour(Colour::ResultError); + out << + "Failed " << pluralise(totals.testCases.failed, "test case") << ", " + "failed " << pluralise(totals.assertions.failed, "assertion") << '.'; + } else { + Colour colour(Colour::ResultSuccess); + out << + "Passed " << bothOrAll(totals.testCases.passed) + << pluralise(totals.testCases.passed, "test case") << + " with " << pluralise(totals.assertions.passed, "assertion") << '.'; + } +} + +// Implementation of CompactReporter formatting +class AssertionPrinter { +public: + AssertionPrinter& operator= (AssertionPrinter const&) = delete; + AssertionPrinter(AssertionPrinter const&) = delete; + AssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages) + : stream(_stream) + , result(_stats.assertionResult) + , messages(_stats.infoMessages) + , itMessage(_stats.infoMessages.begin()) + , printInfoMessages(_printInfoMessages) {} + + void print() { + printSourceInfo(); + + itMessage = messages.begin(); + + switch (result.getResultType()) { + case ResultWas::Ok: + printResultType(Colour::ResultSuccess, passedString()); + printOriginalExpression(); + printReconstructedExpression(); + if (!result.hasExpression()) + printRemainingMessages(Colour::None); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + if (result.isOk()) + printResultType(Colour::ResultSuccess, failedString() + std::string(" - but was ok")); + else + printResultType(Colour::Error, failedString()); + printOriginalExpression(); + printReconstructedExpression(); + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType(Colour::Error, failedString()); + printIssue("unexpected exception with message:"); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::FatalErrorCondition: + printResultType(Colour::Error, failedString()); + printIssue("fatal error condition with message:"); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType(Colour::Error, failedString()); + printIssue("expected exception, got none"); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType(Colour::None, "info"); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType(Colour::None, "warning"); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType(Colour::Error, failedString()); + printIssue("explicitly"); + printRemainingMessages(Colour::None); + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + printResultType(Colour::Error, "** internal error **"); + break; + } + } + +private: + void printSourceInfo() const { + Colour colourGuard(Colour::FileName); + stream << result.getSourceInfo() << ':'; + } + + void printResultType(Colour::Code colour, std::string const& passOrFail) const { + if (!passOrFail.empty()) { + { + Colour colourGuard(colour); + stream << ' ' << passOrFail; + } + stream << ':'; + } + } + + void printIssue(std::string const& issue) const { + stream << ' ' << issue; + } + + void printExpressionWas() { + if (result.hasExpression()) { + stream << ';'; + { + Colour colour(dimColour()); + stream << " expression was:"; + } + printOriginalExpression(); + } + } + + void printOriginalExpression() const { + if (result.hasExpression()) { + stream << ' ' << result.getExpression(); + } + } + + void printReconstructedExpression() const { + if (result.hasExpandedExpression()) { + { + Colour colour(dimColour()); + stream << " for: "; + } + stream << result.getExpandedExpression(); + } + } + + void printMessage() { + if (itMessage != messages.end()) { + stream << " '" << itMessage->message << '\''; + ++itMessage; + } + } + + void printRemainingMessages(Colour::Code colour = dimColour()) { + if (itMessage == messages.end()) + return; + + // using messages.end() directly yields (or auto) compilation error: + std::vector<MessageInfo>::const_iterator itEnd = messages.end(); + const std::size_t N = static_cast<std::size_t>(std::distance(itMessage, itEnd)); + + { + Colour colourGuard(colour); + stream << " with " << pluralise(N, "message") << ':'; + } + + for (; itMessage != itEnd; ) { + // If this assertion is a warning ignore any INFO messages + if (printInfoMessages || itMessage->type != ResultWas::Info) { + stream << " '" << itMessage->message << '\''; + if (++itMessage != itEnd) { + Colour colourGuard(dimColour()); + stream << " and"; + } + } + } + } + +private: + std::ostream& stream; + AssertionResult const& result; + std::vector<MessageInfo> messages; + std::vector<MessageInfo>::const_iterator itMessage; + bool printInfoMessages; +}; + +} // anon namespace + + std::string CompactReporter::getDescription() { + return "Reports test results on a single line, suitable for IDEs"; + } + + ReporterPreferences CompactReporter::getPreferences() const { + return m_reporterPrefs; + } + + void CompactReporter::noMatchingTestCases( std::string const& spec ) { + stream << "No test cases matched '" << spec << '\'' << std::endl; + } + + void CompactReporter::assertionStarting( AssertionInfo const& ) {} + + bool CompactReporter::assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning ) + return false; + printInfoMessages = false; + } + + AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + printer.print(); + + stream << std::endl; + return true; + } + + void CompactReporter::sectionEnded(SectionStats const& _sectionStats) { + if (m_config->showDurations() == ShowDurations::Always) { + stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + } + } + + void CompactReporter::testRunEnded( TestRunStats const& _testRunStats ) { + printTotals( stream, _testRunStats.totals ); + stream << '\n' << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + CompactReporter::~CompactReporter() {} + + CATCH_REGISTER_REPORTER( "compact", CompactReporter ) + +} // end namespace Catch +// end catch_reporter_compact.cpp +// start catch_reporter_console.cpp + +#include <cfloat> +#include <cstdio> + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled + // and default is missing) is enabled +#endif + +namespace Catch { + +namespace { + +// Formatter impl for ConsoleReporter +class ConsoleAssertionPrinter { +public: + ConsoleAssertionPrinter& operator= (ConsoleAssertionPrinter const&) = delete; + ConsoleAssertionPrinter(ConsoleAssertionPrinter const&) = delete; + ConsoleAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages) + : stream(_stream), + stats(_stats), + result(_stats.assertionResult), + colour(Colour::None), + message(result.getMessage()), + messages(_stats.infoMessages), + printInfoMessages(_printInfoMessages) { + switch (result.getResultType()) { + case ResultWas::Ok: + colour = Colour::Success; + passOrFail = "PASSED"; + //if( result.hasMessage() ) + if (_stats.infoMessages.size() == 1) + messageLabel = "with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "with messages"; + break; + case ResultWas::ExpressionFailed: + if (result.isOk()) { + colour = Colour::Success; + passOrFail = "FAILED - but was ok"; + } else { + colour = Colour::Error; + passOrFail = "FAILED"; + } + if (_stats.infoMessages.size() == 1) + messageLabel = "with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "with messages"; + break; + case ResultWas::ThrewException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to unexpected exception with "; + if (_stats.infoMessages.size() == 1) + messageLabel += "message"; + if (_stats.infoMessages.size() > 1) + messageLabel += "messages"; + break; + case ResultWas::FatalErrorCondition: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to a fatal error condition"; + break; + case ResultWas::DidntThrowException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "because no exception was thrown where one was expected"; + break; + case ResultWas::Info: + messageLabel = "info"; + break; + case ResultWas::Warning: + messageLabel = "warning"; + break; + case ResultWas::ExplicitFailure: + passOrFail = "FAILED"; + colour = Colour::Error; + if (_stats.infoMessages.size() == 1) + messageLabel = "explicitly with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "explicitly with messages"; + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + passOrFail = "** internal error **"; + colour = Colour::Error; + break; + } + } + + void print() const { + printSourceInfo(); + if (stats.totals.assertions.total() > 0) { + printResultType(); + printOriginalExpression(); + printReconstructedExpression(); + } else { + stream << '\n'; + } + printMessage(); + } + +private: + void printResultType() const { + if (!passOrFail.empty()) { + Colour colourGuard(colour); + stream << passOrFail << ":\n"; + } + } + void printOriginalExpression() const { + if (result.hasExpression()) { + Colour colourGuard(Colour::OriginalExpression); + stream << " "; + stream << result.getExpressionInMacro(); + stream << '\n'; + } + } + void printReconstructedExpression() const { + if (result.hasExpandedExpression()) { + stream << "with expansion:\n"; + Colour colourGuard(Colour::ReconstructedExpression); + stream << Column(result.getExpandedExpression()).indent(2) << '\n'; + } + } + void printMessage() const { + if (!messageLabel.empty()) + stream << messageLabel << ':' << '\n'; + for (auto const& msg : messages) { + // If this assertion is a warning ignore any INFO messages + if (printInfoMessages || msg.type != ResultWas::Info) + stream << Column(msg.message).indent(2) << '\n'; + } + } + void printSourceInfo() const { + Colour colourGuard(Colour::FileName); + stream << result.getSourceInfo() << ": "; + } + + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + Colour::Code colour; + std::string passOrFail; + std::string messageLabel; + std::string message; + std::vector<MessageInfo> messages; + bool printInfoMessages; +}; + +std::size_t makeRatio(std::size_t number, std::size_t total) { + std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number / total : 0; + return (ratio == 0 && number > 0) ? 1 : ratio; +} + +std::size_t& findMax(std::size_t& i, std::size_t& j, std::size_t& k) { + if (i > j && i > k) + return i; + else if (j > k) + return j; + else + return k; +} + +struct ColumnInfo { + enum Justification { Left, Right }; + std::string name; + int width; + Justification justification; +}; +struct ColumnBreak {}; +struct RowBreak {}; + +class Duration { + enum class Unit { + Auto, + Nanoseconds, + Microseconds, + Milliseconds, + Seconds, + Minutes + }; + static const uint64_t s_nanosecondsInAMicrosecond = 1000; + static const uint64_t s_nanosecondsInAMillisecond = 1000 * s_nanosecondsInAMicrosecond; + static const uint64_t s_nanosecondsInASecond = 1000 * s_nanosecondsInAMillisecond; + static const uint64_t s_nanosecondsInAMinute = 60 * s_nanosecondsInASecond; + + uint64_t m_inNanoseconds; + Unit m_units; + +public: + explicit Duration(uint64_t inNanoseconds, Unit units = Unit::Auto) + : m_inNanoseconds(inNanoseconds), + m_units(units) { + if (m_units == Unit::Auto) { + if (m_inNanoseconds < s_nanosecondsInAMicrosecond) + m_units = Unit::Nanoseconds; + else if (m_inNanoseconds < s_nanosecondsInAMillisecond) + m_units = Unit::Microseconds; + else if (m_inNanoseconds < s_nanosecondsInASecond) + m_units = Unit::Milliseconds; + else if (m_inNanoseconds < s_nanosecondsInAMinute) + m_units = Unit::Seconds; + else + m_units = Unit::Minutes; + } + + } + + auto value() const -> double { + switch (m_units) { + case Unit::Microseconds: + return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMicrosecond); + case Unit::Milliseconds: + return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMillisecond); + case Unit::Seconds: + return m_inNanoseconds / static_cast<double>(s_nanosecondsInASecond); + case Unit::Minutes: + return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMinute); + default: + return static_cast<double>(m_inNanoseconds); + } + } + auto unitsAsString() const -> std::string { + switch (m_units) { + case Unit::Nanoseconds: + return "ns"; + case Unit::Microseconds: + return "µs"; + case Unit::Milliseconds: + return "ms"; + case Unit::Seconds: + return "s"; + case Unit::Minutes: + return "m"; + default: + return "** internal error **"; + } + + } + friend auto operator << (std::ostream& os, Duration const& duration) -> std::ostream& { + return os << duration.value() << " " << duration.unitsAsString(); + } +}; +} // end anon namespace + +class TablePrinter { + std::ostream& m_os; + std::vector<ColumnInfo> m_columnInfos; + std::ostringstream m_oss; + int m_currentColumn = -1; + bool m_isOpen = false; + +public: + TablePrinter( std::ostream& os, std::vector<ColumnInfo> columnInfos ) + : m_os( os ), + m_columnInfos( std::move( columnInfos ) ) {} + + auto columnInfos() const -> std::vector<ColumnInfo> const& { + return m_columnInfos; + } + + void open() { + if (!m_isOpen) { + m_isOpen = true; + *this << RowBreak(); + for (auto const& info : m_columnInfos) + *this << info.name << ColumnBreak(); + *this << RowBreak(); + m_os << Catch::getLineOfChars<'-'>() << "\n"; + } + } + void close() { + if (m_isOpen) { + *this << RowBreak(); + m_os << std::endl; + m_isOpen = false; + } + } + + template<typename T> + friend TablePrinter& operator << (TablePrinter& tp, T const& value) { + tp.m_oss << value; + return tp; + } + + friend TablePrinter& operator << (TablePrinter& tp, ColumnBreak) { + auto colStr = tp.m_oss.str(); + // This takes account of utf8 encodings + auto strSize = Catch::StringRef(colStr).numberOfCharacters(); + tp.m_oss.str(""); + tp.open(); + if (tp.m_currentColumn == static_cast<int>(tp.m_columnInfos.size() - 1)) { + tp.m_currentColumn = -1; + tp.m_os << "\n"; + } + tp.m_currentColumn++; + + auto colInfo = tp.m_columnInfos[tp.m_currentColumn]; + auto padding = (strSize + 2 < static_cast<std::size_t>(colInfo.width)) + ? std::string(colInfo.width - (strSize + 2), ' ') + : std::string(); + if (colInfo.justification == ColumnInfo::Left) + tp.m_os << colStr << padding << " "; + else + tp.m_os << padding << colStr << " "; + return tp; + } + + friend TablePrinter& operator << (TablePrinter& tp, RowBreak) { + if (tp.m_currentColumn > 0) { + tp.m_os << "\n"; + tp.m_currentColumn = -1; + } + return tp; + } +}; + +ConsoleReporter::ConsoleReporter(ReporterConfig const& config) + : StreamingReporterBase(config), + m_tablePrinter(new TablePrinter(config.stream(), + { + { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 32, ColumnInfo::Left }, + { "iters", 8, ColumnInfo::Right }, + { "elapsed ns", 14, ColumnInfo::Right }, + { "average", 14, ColumnInfo::Right } + })) {} +ConsoleReporter::~ConsoleReporter() = default; + +std::string ConsoleReporter::getDescription() { + return "Reports test results as plain lines of text"; +} + +void ConsoleReporter::noMatchingTestCases(std::string const& spec) { + stream << "No test cases matched '" << spec << '\'' << std::endl; +} + +void ConsoleReporter::assertionStarting(AssertionInfo const&) {} + +bool ConsoleReporter::assertionEnded(AssertionStats const& _assertionStats) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + // Drop out if result was successful but we're not printing them. + if (!includeResults && result.getResultType() != ResultWas::Warning) + return false; + + lazyPrint(); + + ConsoleAssertionPrinter printer(stream, _assertionStats, includeResults); + printer.print(); + stream << std::endl; + return true; +} + +void ConsoleReporter::sectionStarting(SectionInfo const& _sectionInfo) { + m_headerPrinted = false; + StreamingReporterBase::sectionStarting(_sectionInfo); +} +void ConsoleReporter::sectionEnded(SectionStats const& _sectionStats) { + m_tablePrinter->close(); + if (_sectionStats.missingAssertions) { + lazyPrint(); + Colour colour(Colour::ResultError); + if (m_sectionStack.size() > 1) + stream << "\nNo assertions in section"; + else + stream << "\nNo assertions in test case"; + stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; + } + if (m_config->showDurations() == ShowDurations::Always) { + stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + } + if (m_headerPrinted) { + m_headerPrinted = false; + } + StreamingReporterBase::sectionEnded(_sectionStats); +} + +void ConsoleReporter::benchmarkStarting(BenchmarkInfo const& info) { + lazyPrintWithoutClosingBenchmarkTable(); + + auto nameCol = Column( info.name ).width( static_cast<std::size_t>( m_tablePrinter->columnInfos()[0].width - 2 ) ); + + bool firstLine = true; + for (auto line : nameCol) { + if (!firstLine) + (*m_tablePrinter) << ColumnBreak() << ColumnBreak() << ColumnBreak(); + else + firstLine = false; + + (*m_tablePrinter) << line << ColumnBreak(); + } +} +void ConsoleReporter::benchmarkEnded(BenchmarkStats const& stats) { + Duration average(stats.elapsedTimeInNanoseconds / stats.iterations); + (*m_tablePrinter) + << stats.iterations << ColumnBreak() + << stats.elapsedTimeInNanoseconds << ColumnBreak() + << average << ColumnBreak(); +} + +void ConsoleReporter::testCaseEnded(TestCaseStats const& _testCaseStats) { + m_tablePrinter->close(); + StreamingReporterBase::testCaseEnded(_testCaseStats); + m_headerPrinted = false; +} +void ConsoleReporter::testGroupEnded(TestGroupStats const& _testGroupStats) { + if (currentGroupInfo.used) { + printSummaryDivider(); + stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; + printTotals(_testGroupStats.totals); + stream << '\n' << std::endl; + } + StreamingReporterBase::testGroupEnded(_testGroupStats); +} +void ConsoleReporter::testRunEnded(TestRunStats const& _testRunStats) { + printTotalsDivider(_testRunStats.totals); + printTotals(_testRunStats.totals); + stream << std::endl; + StreamingReporterBase::testRunEnded(_testRunStats); +} + +void ConsoleReporter::lazyPrint() { + + m_tablePrinter->close(); + lazyPrintWithoutClosingBenchmarkTable(); +} + +void ConsoleReporter::lazyPrintWithoutClosingBenchmarkTable() { + + if (!currentTestRunInfo.used) + lazyPrintRunInfo(); + if (!currentGroupInfo.used) + lazyPrintGroupInfo(); + + if (!m_headerPrinted) { + printTestCaseAndSectionHeader(); + m_headerPrinted = true; + } +} +void ConsoleReporter::lazyPrintRunInfo() { + stream << '\n' << getLineOfChars<'~'>() << '\n'; + Colour colour(Colour::SecondaryText); + stream << currentTestRunInfo->name + << " is a Catch v" << libraryVersion() << " host application.\n" + << "Run with -? for options\n\n"; + + if (m_config->rngSeed() != 0) + stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; + + currentTestRunInfo.used = true; +} +void ConsoleReporter::lazyPrintGroupInfo() { + if (!currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1) { + printClosedHeader("Group: " + currentGroupInfo->name); + currentGroupInfo.used = true; + } +} +void ConsoleReporter::printTestCaseAndSectionHeader() { + assert(!m_sectionStack.empty()); + printOpenHeader(currentTestCaseInfo->name); + + if (m_sectionStack.size() > 1) { + Colour colourGuard(Colour::Headers); + + auto + it = m_sectionStack.begin() + 1, // Skip first section (test case) + itEnd = m_sectionStack.end(); + for (; it != itEnd; ++it) + printHeaderString(it->name, 2); + } + + SourceLineInfo lineInfo = m_sectionStack.back().lineInfo; + + if (!lineInfo.empty()) { + stream << getLineOfChars<'-'>() << '\n'; + Colour colourGuard(Colour::FileName); + stream << lineInfo << '\n'; + } + stream << getLineOfChars<'.'>() << '\n' << std::endl; +} + +void ConsoleReporter::printClosedHeader(std::string const& _name) { + printOpenHeader(_name); + stream << getLineOfChars<'.'>() << '\n'; +} +void ConsoleReporter::printOpenHeader(std::string const& _name) { + stream << getLineOfChars<'-'>() << '\n'; + { + Colour colourGuard(Colour::Headers); + printHeaderString(_name); + } +} + +// if string has a : in first line will set indent to follow it on +// subsequent lines +void ConsoleReporter::printHeaderString(std::string const& _string, std::size_t indent) { + std::size_t i = _string.find(": "); + if (i != std::string::npos) + i += 2; + else + i = 0; + stream << Column(_string).indent(indent + i).initialIndent(indent) << '\n'; +} + +struct SummaryColumn { + + SummaryColumn( std::string _label, Colour::Code _colour ) + : label( std::move( _label ) ), + colour( _colour ) {} + SummaryColumn addRow( std::size_t count ) { + ReusableStringStream rss; + rss << count; + std::string row = rss.str(); + for (auto& oldRow : rows) { + while (oldRow.size() < row.size()) + oldRow = ' ' + oldRow; + while (oldRow.size() > row.size()) + row = ' ' + row; + } + rows.push_back(row); + return *this; + } + + std::string label; + Colour::Code colour; + std::vector<std::string> rows; + +}; + +void ConsoleReporter::printTotals( Totals const& totals ) { + if (totals.testCases.total() == 0) { + stream << Colour(Colour::Warning) << "No tests ran\n"; + } else if (totals.assertions.total() > 0 && totals.testCases.allPassed()) { + stream << Colour(Colour::ResultSuccess) << "All tests passed"; + stream << " (" + << pluralise(totals.assertions.passed, "assertion") << " in " + << pluralise(totals.testCases.passed, "test case") << ')' + << '\n'; + } else { + + std::vector<SummaryColumn> columns; + columns.push_back(SummaryColumn("", Colour::None) + .addRow(totals.testCases.total()) + .addRow(totals.assertions.total())); + columns.push_back(SummaryColumn("passed", Colour::Success) + .addRow(totals.testCases.passed) + .addRow(totals.assertions.passed)); + columns.push_back(SummaryColumn("failed", Colour::ResultError) + .addRow(totals.testCases.failed) + .addRow(totals.assertions.failed)); + columns.push_back(SummaryColumn("failed as expected", Colour::ResultExpectedFailure) + .addRow(totals.testCases.failedButOk) + .addRow(totals.assertions.failedButOk)); + + printSummaryRow("test cases", columns, 0); + printSummaryRow("assertions", columns, 1); + } +} +void ConsoleReporter::printSummaryRow(std::string const& label, std::vector<SummaryColumn> const& cols, std::size_t row) { + for (auto col : cols) { + std::string value = col.rows[row]; + if (col.label.empty()) { + stream << label << ": "; + if (value != "0") + stream << value; + else + stream << Colour(Colour::Warning) << "- none -"; + } else if (value != "0") { + stream << Colour(Colour::LightGrey) << " | "; + stream << Colour(col.colour) + << value << ' ' << col.label; + } + } + stream << '\n'; +} + +void ConsoleReporter::printTotalsDivider(Totals const& totals) { + if (totals.testCases.total() > 0) { + std::size_t failedRatio = makeRatio(totals.testCases.failed, totals.testCases.total()); + std::size_t failedButOkRatio = makeRatio(totals.testCases.failedButOk, totals.testCases.total()); + std::size_t passedRatio = makeRatio(totals.testCases.passed, totals.testCases.total()); + while (failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH - 1) + findMax(failedRatio, failedButOkRatio, passedRatio)++; + while (failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH - 1) + findMax(failedRatio, failedButOkRatio, passedRatio)--; + + stream << Colour(Colour::Error) << std::string(failedRatio, '='); + stream << Colour(Colour::ResultExpectedFailure) << std::string(failedButOkRatio, '='); + if (totals.testCases.allPassed()) + stream << Colour(Colour::ResultSuccess) << std::string(passedRatio, '='); + else + stream << Colour(Colour::Success) << std::string(passedRatio, '='); + } else { + stream << Colour(Colour::Warning) << std::string(CATCH_CONFIG_CONSOLE_WIDTH - 1, '='); + } + stream << '\n'; +} +void ConsoleReporter::printSummaryDivider() { + stream << getLineOfChars<'-'>() << '\n'; +} + +CATCH_REGISTER_REPORTER("console", ConsoleReporter) + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +// end catch_reporter_console.cpp +// start catch_reporter_junit.cpp + +#include <cassert> +#include <sstream> +#include <ctime> +#include <algorithm> + +namespace Catch { + + namespace { + std::string getCurrentTimestamp() { + // Beware, this is not reentrant because of backward compatibility issues + // Also, UTC only, again because of backward compatibility (%z is C++11) + time_t rawtime; + std::time(&rawtime); + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + +#ifdef _MSC_VER + std::tm timeInfo = {}; + gmtime_s(&timeInfo, &rawtime); +#else + std::tm* timeInfo; + timeInfo = std::gmtime(&rawtime); +#endif + + char timeStamp[timeStampSize]; + const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; + +#ifdef _MSC_VER + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); +#else + std::strftime(timeStamp, timeStampSize, fmt, timeInfo); +#endif + return std::string(timeStamp); + } + + std::string fileNameTag(const std::vector<std::string> &tags) { + auto it = std::find_if(begin(tags), + end(tags), + [] (std::string const& tag) {return tag.front() == '#'; }); + if (it != tags.end()) + return it->substr(1); + return std::string(); + } + } // anonymous namespace + + JunitReporter::JunitReporter( ReporterConfig const& _config ) + : CumulativeReporterBase( _config ), + xml( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = true; + m_reporterPrefs.shouldReportAllAssertions = true; + } + + JunitReporter::~JunitReporter() {} + + std::string JunitReporter::getDescription() { + return "Reports test results in an XML format that looks like Ant's junitreport target"; + } + + void JunitReporter::noMatchingTestCases( std::string const& /*spec*/ ) {} + + void JunitReporter::testRunStarting( TestRunInfo const& runInfo ) { + CumulativeReporterBase::testRunStarting( runInfo ); + xml.startElement( "testsuites" ); + } + + void JunitReporter::testGroupStarting( GroupInfo const& groupInfo ) { + suiteTimer.start(); + stdOutForSuite.clear(); + stdErrForSuite.clear(); + unexpectedExceptions = 0; + CumulativeReporterBase::testGroupStarting( groupInfo ); + } + + void JunitReporter::testCaseStarting( TestCaseInfo const& testCaseInfo ) { + m_okToFail = testCaseInfo.okToFail(); + } + + bool JunitReporter::assertionEnded( AssertionStats const& assertionStats ) { + if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail ) + unexpectedExceptions++; + return CumulativeReporterBase::assertionEnded( assertionStats ); + } + + void JunitReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + stdOutForSuite += testCaseStats.stdOut; + stdErrForSuite += testCaseStats.stdErr; + CumulativeReporterBase::testCaseEnded( testCaseStats ); + } + + void JunitReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { + double suiteTime = suiteTimer.getElapsedSeconds(); + CumulativeReporterBase::testGroupEnded( testGroupStats ); + writeGroup( *m_testGroups.back(), suiteTime ); + } + + void JunitReporter::testRunEndedCumulative() { + xml.endElement(); + } + + void JunitReporter::writeGroup( TestGroupNode const& groupNode, double suiteTime ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); + TestGroupStats const& stats = groupNode.value; + xml.writeAttribute( "name", stats.groupInfo.name ); + xml.writeAttribute( "errors", unexpectedExceptions ); + xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); + xml.writeAttribute( "tests", stats.totals.assertions.total() ); + xml.writeAttribute( "hostname", "tbd" ); // !TBD + if( m_config->showDurations() == ShowDurations::Never ) + xml.writeAttribute( "time", "" ); + else + xml.writeAttribute( "time", suiteTime ); + xml.writeAttribute( "timestamp", getCurrentTimestamp() ); + + // Write test cases + for( auto const& child : groupNode.children ) + writeTestCase( *child ); + + xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite ), false ); + xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite ), false ); + } + + void JunitReporter::writeTestCase( TestCaseNode const& testCaseNode ) { + TestCaseStats const& stats = testCaseNode.value; + + // All test cases have exactly one section - which represents the + // test case itself. That section may have 0-n nested sections + assert( testCaseNode.children.size() == 1 ); + SectionNode const& rootSection = *testCaseNode.children.front(); + + std::string className = stats.testInfo.className; + + if( className.empty() ) { + className = fileNameTag(stats.testInfo.tags); + if ( className.empty() ) + className = "global"; + } + + if ( !m_config->name().empty() ) + className = m_config->name() + "." + className; + + writeSection( className, "", rootSection ); + } + + void JunitReporter::writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode ) { + std::string name = trim( sectionNode.stats.sectionInfo.name ); + if( !rootName.empty() ) + name = rootName + '/' + name; + + if( !sectionNode.assertions.empty() || + !sectionNode.stdOut.empty() || + !sectionNode.stdErr.empty() ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); + if( className.empty() ) { + xml.writeAttribute( "classname", name ); + xml.writeAttribute( "name", "root" ); + } + else { + xml.writeAttribute( "classname", className ); + xml.writeAttribute( "name", name ); + } + xml.writeAttribute( "time", ::Catch::Detail::stringify( sectionNode.stats.durationInSeconds ) ); + + writeAssertions( sectionNode ); + + if( !sectionNode.stdOut.empty() ) + xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); + if( !sectionNode.stdErr.empty() ) + xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); + } + for( auto const& childNode : sectionNode.childSections ) + if( className.empty() ) + writeSection( name, "", *childNode ); + else + writeSection( className, name, *childNode ); + } + + void JunitReporter::writeAssertions( SectionNode const& sectionNode ) { + for( auto const& assertion : sectionNode.assertions ) + writeAssertion( assertion ); + } + + void JunitReporter::writeAssertion( AssertionStats const& stats ) { + AssertionResult const& result = stats.assertionResult; + if( !result.isOk() ) { + std::string elementName; + switch( result.getResultType() ) { + case ResultWas::ThrewException: + case ResultWas::FatalErrorCondition: + elementName = "error"; + break; + case ResultWas::ExplicitFailure: + elementName = "failure"; + break; + case ResultWas::ExpressionFailed: + elementName = "failure"; + break; + case ResultWas::DidntThrowException: + elementName = "failure"; + break; + + // We should never see these here: + case ResultWas::Info: + case ResultWas::Warning: + case ResultWas::Ok: + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + elementName = "internalError"; + break; + } + + XmlWriter::ScopedElement e = xml.scopedElement( elementName ); + + xml.writeAttribute( "message", result.getExpandedExpression() ); + xml.writeAttribute( "type", result.getTestMacroName() ); + + ReusableStringStream rss; + if( !result.getMessage().empty() ) + rss << result.getMessage() << '\n'; + for( auto const& msg : stats.infoMessages ) + if( msg.type == ResultWas::Info ) + rss << msg.message << '\n'; + + rss << "at " << result.getSourceInfo(); + xml.writeText( rss.str(), false ); + } + } + + CATCH_REGISTER_REPORTER( "junit", JunitReporter ) + +} // end namespace Catch +// end catch_reporter_junit.cpp +// start catch_reporter_listening.cpp + +#include <cassert> + +namespace Catch { + + ListeningReporter::ListeningReporter() { + // We will assume that listeners will always want all assertions + m_preferences.shouldReportAllAssertions = true; + } + + void ListeningReporter::addListener( IStreamingReporterPtr&& listener ) { + m_listeners.push_back( std::move( listener ) ); + } + + void ListeningReporter::addReporter(IStreamingReporterPtr&& reporter) { + assert(!m_reporter && "Listening reporter can wrap only 1 real reporter"); + m_reporter = std::move( reporter ); + m_preferences.shouldRedirectStdOut = m_reporter->getPreferences().shouldRedirectStdOut; + } + + ReporterPreferences ListeningReporter::getPreferences() const { + return m_preferences; + } + + std::set<Verbosity> ListeningReporter::getSupportedVerbosities() { + return std::set<Verbosity>{ }; + } + + void ListeningReporter::noMatchingTestCases( std::string const& spec ) { + for ( auto const& listener : m_listeners ) { + listener->noMatchingTestCases( spec ); + } + m_reporter->noMatchingTestCases( spec ); + } + + void ListeningReporter::benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) { + for ( auto const& listener : m_listeners ) { + listener->benchmarkStarting( benchmarkInfo ); + } + m_reporter->benchmarkStarting( benchmarkInfo ); + } + void ListeningReporter::benchmarkEnded( BenchmarkStats const& benchmarkStats ) { + for ( auto const& listener : m_listeners ) { + listener->benchmarkEnded( benchmarkStats ); + } + m_reporter->benchmarkEnded( benchmarkStats ); + } + + void ListeningReporter::testRunStarting( TestRunInfo const& testRunInfo ) { + for ( auto const& listener : m_listeners ) { + listener->testRunStarting( testRunInfo ); + } + m_reporter->testRunStarting( testRunInfo ); + } + + void ListeningReporter::testGroupStarting( GroupInfo const& groupInfo ) { + for ( auto const& listener : m_listeners ) { + listener->testGroupStarting( groupInfo ); + } + m_reporter->testGroupStarting( groupInfo ); + } + + void ListeningReporter::testCaseStarting( TestCaseInfo const& testInfo ) { + for ( auto const& listener : m_listeners ) { + listener->testCaseStarting( testInfo ); + } + m_reporter->testCaseStarting( testInfo ); + } + + void ListeningReporter::sectionStarting( SectionInfo const& sectionInfo ) { + for ( auto const& listener : m_listeners ) { + listener->sectionStarting( sectionInfo ); + } + m_reporter->sectionStarting( sectionInfo ); + } + + void ListeningReporter::assertionStarting( AssertionInfo const& assertionInfo ) { + for ( auto const& listener : m_listeners ) { + listener->assertionStarting( assertionInfo ); + } + m_reporter->assertionStarting( assertionInfo ); + } + + // The return value indicates if the messages buffer should be cleared: + bool ListeningReporter::assertionEnded( AssertionStats const& assertionStats ) { + for( auto const& listener : m_listeners ) { + static_cast<void>( listener->assertionEnded( assertionStats ) ); + } + return m_reporter->assertionEnded( assertionStats ); + } + + void ListeningReporter::sectionEnded( SectionStats const& sectionStats ) { + for ( auto const& listener : m_listeners ) { + listener->sectionEnded( sectionStats ); + } + m_reporter->sectionEnded( sectionStats ); + } + + void ListeningReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + for ( auto const& listener : m_listeners ) { + listener->testCaseEnded( testCaseStats ); + } + m_reporter->testCaseEnded( testCaseStats ); + } + + void ListeningReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { + for ( auto const& listener : m_listeners ) { + listener->testGroupEnded( testGroupStats ); + } + m_reporter->testGroupEnded( testGroupStats ); + } + + void ListeningReporter::testRunEnded( TestRunStats const& testRunStats ) { + for ( auto const& listener : m_listeners ) { + listener->testRunEnded( testRunStats ); + } + m_reporter->testRunEnded( testRunStats ); + } + + void ListeningReporter::skipTest( TestCaseInfo const& testInfo ) { + for ( auto const& listener : m_listeners ) { + listener->skipTest( testInfo ); + } + m_reporter->skipTest( testInfo ); + } + + bool ListeningReporter::isMulti() const { + return true; + } + +} // end namespace Catch +// end catch_reporter_listening.cpp +// start catch_reporter_xml.cpp + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled + // and default is missing) is enabled +#endif + +namespace Catch { + XmlReporter::XmlReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_xml(_config.stream()) + { + m_reporterPrefs.shouldRedirectStdOut = true; + m_reporterPrefs.shouldReportAllAssertions = true; + } + + XmlReporter::~XmlReporter() = default; + + std::string XmlReporter::getDescription() { + return "Reports test results as an XML document"; + } + + std::string XmlReporter::getStylesheetRef() const { + return std::string(); + } + + void XmlReporter::writeSourceInfo( SourceLineInfo const& sourceInfo ) { + m_xml + .writeAttribute( "filename", sourceInfo.file ) + .writeAttribute( "line", sourceInfo.line ); + } + + void XmlReporter::noMatchingTestCases( std::string const& s ) { + StreamingReporterBase::noMatchingTestCases( s ); + } + + void XmlReporter::testRunStarting( TestRunInfo const& testInfo ) { + StreamingReporterBase::testRunStarting( testInfo ); + std::string stylesheetRef = getStylesheetRef(); + if( !stylesheetRef.empty() ) + m_xml.writeStylesheetRef( stylesheetRef ); + m_xml.startElement( "Catch" ); + if( !m_config->name().empty() ) + m_xml.writeAttribute( "name", m_config->name() ); + if( m_config->rngSeed() != 0 ) + m_xml.scopedElement( "Randomness" ) + .writeAttribute( "seed", m_config->rngSeed() ); + } + + void XmlReporter::testGroupStarting( GroupInfo const& groupInfo ) { + StreamingReporterBase::testGroupStarting( groupInfo ); + m_xml.startElement( "Group" ) + .writeAttribute( "name", groupInfo.name ); + } + + void XmlReporter::testCaseStarting( TestCaseInfo const& testInfo ) { + StreamingReporterBase::testCaseStarting(testInfo); + m_xml.startElement( "TestCase" ) + .writeAttribute( "name", trim( testInfo.name ) ) + .writeAttribute( "description", testInfo.description ) + .writeAttribute( "tags", testInfo.tagsAsString() ); + + writeSourceInfo( testInfo.lineInfo ); + + if ( m_config->showDurations() == ShowDurations::Always ) + m_testCaseTimer.start(); + m_xml.ensureTagClosed(); + } + + void XmlReporter::sectionStarting( SectionInfo const& sectionInfo ) { + StreamingReporterBase::sectionStarting( sectionInfo ); + if( m_sectionDepth++ > 0 ) { + m_xml.startElement( "Section" ) + .writeAttribute( "name", trim( sectionInfo.name ) ); + writeSourceInfo( sectionInfo.lineInfo ); + m_xml.ensureTagClosed(); + } + } + + void XmlReporter::assertionStarting( AssertionInfo const& ) { } + + bool XmlReporter::assertionEnded( AssertionStats const& assertionStats ) { + + AssertionResult const& result = assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + if( includeResults || result.getResultType() == ResultWas::Warning ) { + // Print any info messages in <Info> tags. + for( auto const& msg : assertionStats.infoMessages ) { + if( msg.type == ResultWas::Info && includeResults ) { + m_xml.scopedElement( "Info" ) + .writeText( msg.message ); + } else if ( msg.type == ResultWas::Warning ) { + m_xml.scopedElement( "Warning" ) + .writeText( msg.message ); + } + } + } + + // Drop out if result was successful but we're not printing them. + if( !includeResults && result.getResultType() != ResultWas::Warning ) + return true; + + // Print the expression if there is one. + if( result.hasExpression() ) { + m_xml.startElement( "Expression" ) + .writeAttribute( "success", result.succeeded() ) + .writeAttribute( "type", result.getTestMacroName() ); + + writeSourceInfo( result.getSourceInfo() ); + + m_xml.scopedElement( "Original" ) + .writeText( result.getExpression() ); + m_xml.scopedElement( "Expanded" ) + .writeText( result.getExpandedExpression() ); + } + + // And... Print a result applicable to each result type. + switch( result.getResultType() ) { + case ResultWas::ThrewException: + m_xml.startElement( "Exception" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::FatalErrorCondition: + m_xml.startElement( "FatalErrorCondition" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::Info: + m_xml.scopedElement( "Info" ) + .writeText( result.getMessage() ); + break; + case ResultWas::Warning: + // Warning will already have been written + break; + case ResultWas::ExplicitFailure: + m_xml.startElement( "Failure" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + default: + break; + } + + if( result.hasExpression() ) + m_xml.endElement(); + + return true; + } + + void XmlReporter::sectionEnded( SectionStats const& sectionStats ) { + StreamingReporterBase::sectionEnded( sectionStats ); + if( --m_sectionDepth > 0 ) { + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); + e.writeAttribute( "successes", sectionStats.assertions.passed ); + e.writeAttribute( "failures", sectionStats.assertions.failed ); + e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); + + m_xml.endElement(); + } + } + + void XmlReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + StreamingReporterBase::testCaseEnded( testCaseStats ); + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); + e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); + + if( !testCaseStats.stdOut.empty() ) + m_xml.scopedElement( "StdOut" ).writeText( trim( testCaseStats.stdOut ), false ); + if( !testCaseStats.stdErr.empty() ) + m_xml.scopedElement( "StdErr" ).writeText( trim( testCaseStats.stdErr ), false ); + + m_xml.endElement(); + } + + void XmlReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { + StreamingReporterBase::testGroupEnded( testGroupStats ); + // TODO: Check testGroupStats.aborting and act accordingly. + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) + .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + void XmlReporter::testRunEnded( TestRunStats const& testRunStats ) { + StreamingReporterBase::testRunEnded( testRunStats ); + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testRunStats.totals.assertions.passed ) + .writeAttribute( "failures", testRunStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + CATCH_REGISTER_REPORTER( "xml", XmlReporter ) + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +// end catch_reporter_xml.cpp + +namespace Catch { + LeakDetector leakDetector; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// end catch_impl.hpp +#endif + +#ifdef CATCH_CONFIG_MAIN +// start catch_default_main.hpp + +#ifndef __OBJC__ + +#if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN) +// Standard C/C++ Win32 Unicode wmain entry point +extern "C" int wmain (int argc, wchar_t * argv[], wchar_t * []) { +#else +// Standard C/C++ main entry point +int main (int argc, char * argv[]) { +#endif + + return Catch::Session().run( argc, argv ); +} + +#else // __OBJC__ + +// Objective-C entry point +int main (int argc, char * const argv[]) { +#if !CATCH_ARC_ENABLED + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; +#endif + + Catch::registerTestMethods(); + int result = Catch::Session().run( argc, (char**)argv ); + +#if !CATCH_ARC_ENABLED + [pool drain]; +#endif + + return result; +} + +#endif // __OBJC__ + +// end catch_default_main.hpp +#endif + +#if !defined(CATCH_CONFIG_IMPL_ONLY) + +#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED +# undef CLARA_CONFIG_MAIN +#endif + +#if !defined(CATCH_CONFIG_DISABLE) +////// +// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ +#ifdef CATCH_CONFIG_PREFIX_ALL + +#define CATCH_REQUIRE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define CATCH_REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + +#define CATCH_REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", __VA_ARGS__ ) +#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) +#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CATCH_REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr ) +#endif// CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + +#define CATCH_CHECK( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) +#define CATCH_CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + +#define CATCH_CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", __VA_ARGS__ ) +#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CATCH_CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) + +#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) +#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) +#define CATCH_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_CAPTURE",__VA_ARGS__ ) + +#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) +#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) +#define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) +#define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) +#define CATCH_DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) +#define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE() + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#else +#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) ) +#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) ) +#endif + +#if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) +#define CATCH_STATIC_REQUIRE( ... ) static_assert( __VA_ARGS__ , #__VA_ARGS__ ); CATCH_SUCCEED( #__VA_ARGS__ ) +#define CATCH_STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); CATCH_SUCCEED( #__VA_ARGS__ ) +#else +#define CATCH_STATIC_REQUIRE( ... ) CATCH_REQUIRE( __VA_ARGS__ ) +#define CATCH_STATIC_REQUIRE_FALSE( ... ) CATCH_REQUIRE_FALSE( __VA_ARGS__ ) +#endif + +// "BDD-style" convenience wrappers +#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) +#define CATCH_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc ) +#define CATCH_AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And given: " << desc ) +#define CATCH_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc ) +#define CATCH_AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And when: " << desc ) +#define CATCH_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc ) +#define CATCH_AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc ) + +// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required +#else + +#define REQUIRE( ... ) INTERNAL_CATCH_TEST( "REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + +#define REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) +#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + +#define CHECK( ... ) INTERNAL_CATCH_TEST( "CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) +#define CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + +#define CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) + +#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) +#define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) +#define CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CAPTURE",__VA_ARGS__ ) + +#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) +#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) +#define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) +#define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) +#define DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) +#define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE() + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#else +#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) ) +#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) ) +#endif + +#if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) +#define STATIC_REQUIRE( ... ) static_assert( __VA_ARGS__, #__VA_ARGS__ ); SUCCEED( #__VA_ARGS__ ) +#define STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); SUCCEED( "!(" #__VA_ARGS__ ")" ) +#else +#define STATIC_REQUIRE( ... ) REQUIRE( __VA_ARGS__ ) +#define STATIC_REQUIRE_FALSE( ... ) REQUIRE_FALSE( __VA_ARGS__ ) +#endif + +#endif + +#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) + +// "BDD-style" convenience wrappers +#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) + +#define GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc ) +#define AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And given: " << desc ) +#define WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc ) +#define AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And when: " << desc ) +#define THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc ) +#define AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc ) + +using Catch::Detail::Approx; + +#else // CATCH_CONFIG_DISABLE + +////// +// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ +#ifdef CATCH_CONFIG_PREFIX_ALL + +#define CATCH_REQUIRE( ... ) (void)(0) +#define CATCH_REQUIRE_FALSE( ... ) (void)(0) + +#define CATCH_REQUIRE_THROWS( ... ) (void)(0) +#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) +#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif// CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_REQUIRE_NOTHROW( ... ) (void)(0) + +#define CATCH_CHECK( ... ) (void)(0) +#define CATCH_CHECK_FALSE( ... ) (void)(0) +#define CATCH_CHECKED_IF( ... ) if (__VA_ARGS__) +#define CATCH_CHECKED_ELSE( ... ) if (!(__VA_ARGS__)) +#define CATCH_CHECK_NOFAIL( ... ) (void)(0) + +#define CATCH_CHECK_THROWS( ... ) (void)(0) +#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) (void)(0) +#define CATCH_CHECK_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_CHECK_NOTHROW( ... ) (void)(0) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THAT( arg, matcher ) (void)(0) + +#define CATCH_REQUIRE_THAT( arg, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define CATCH_INFO( msg ) (void)(0) +#define CATCH_WARN( msg ) (void)(0) +#define CATCH_CAPTURE( msg ) (void)(0) + +#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define CATCH_METHOD_AS_TEST_CASE( method, ... ) +#define CATCH_REGISTER_TEST_CASE( Function, ... ) (void)(0) +#define CATCH_SECTION( ... ) +#define CATCH_DYNAMIC_SECTION( ... ) +#define CATCH_FAIL( ... ) (void)(0) +#define CATCH_FAIL_CHECK( ... ) (void)(0) +#define CATCH_SUCCEED( ... ) (void)(0) + +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) ) +#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), className ) +#else +#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) ) ) +#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), className ) ) +#endif + +// "BDD-style" convenience wrappers +#define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className ) +#define CATCH_GIVEN( desc ) +#define CATCH_AND_GIVEN( desc ) +#define CATCH_WHEN( desc ) +#define CATCH_AND_WHEN( desc ) +#define CATCH_THEN( desc ) +#define CATCH_AND_THEN( desc ) + +#define CATCH_STATIC_REQUIRE( ... ) (void)(0) +#define CATCH_STATIC_REQUIRE_FALSE( ... ) (void)(0) + +// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required +#else + +#define REQUIRE( ... ) (void)(0) +#define REQUIRE_FALSE( ... ) (void)(0) + +#define REQUIRE_THROWS( ... ) (void)(0) +#define REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) +#define REQUIRE_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define REQUIRE_NOTHROW( ... ) (void)(0) + +#define CHECK( ... ) (void)(0) +#define CHECK_FALSE( ... ) (void)(0) +#define CHECKED_IF( ... ) if (__VA_ARGS__) +#define CHECKED_ELSE( ... ) if (!(__VA_ARGS__)) +#define CHECK_NOFAIL( ... ) (void)(0) + +#define CHECK_THROWS( ... ) (void)(0) +#define CHECK_THROWS_AS( expr, exceptionType ) (void)(0) +#define CHECK_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CHECK_NOTHROW( ... ) (void)(0) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THAT( arg, matcher ) (void)(0) + +#define REQUIRE_THAT( arg, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define INFO( msg ) (void)(0) +#define WARN( msg ) (void)(0) +#define CAPTURE( msg ) (void)(0) + +#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define METHOD_AS_TEST_CASE( method, ... ) +#define REGISTER_TEST_CASE( Function, ... ) (void)(0) +#define SECTION( ... ) +#define DYNAMIC_SECTION( ... ) +#define FAIL( ... ) (void)(0) +#define FAIL_CHECK( ... ) (void)(0) +#define SUCCEED( ... ) (void)(0) +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) ) +#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), className ) +#else +#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) ) ) +#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), className ) ) +#endif + +#define STATIC_REQUIRE( ... ) (void)(0) +#define STATIC_REQUIRE_FALSE( ... ) (void)(0) + +#endif + +#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) + +// "BDD-style" convenience wrappers +#define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) ) +#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className ) + +#define GIVEN( desc ) +#define AND_GIVEN( desc ) +#define WHEN( desc ) +#define AND_WHEN( desc ) +#define THEN( desc ) +#define AND_THEN( desc ) + +using Catch::Detail::Approx; + +#endif + +#endif // ! CATCH_CONFIG_IMPL_ONLY + +// start catch_reenable_warnings.h + + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(pop) +# else +# pragma clang diagnostic pop +# endif +#elif defined __GNUC__ +# pragma GCC diagnostic pop +#endif + +// end catch_reenable_warnings.h +// end catch.hpp +#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + diff --git a/components/codecs/test/nvpipe_codec_unit.cpp b/components/codecs/test/nvpipe_codec_unit.cpp new file mode 100644 index 0000000000000000000000000000000000000000..86f773df64f8fcdd55d493305ffa2505d356a22c --- /dev/null +++ b/components/codecs/test/nvpipe_codec_unit.cpp @@ -0,0 +1,121 @@ +#include "catch.hpp" +#include <ftl/codecs/nvpipe_encoder.hpp> +#include <ftl/codecs/nvpipe_decoder.hpp> +#include <ftl/threads.hpp> + +using ftl::codecs::CodecPreset; +using ftl::codecs::preset_t; +using ftl::codecs::definition_t; +using ftl::codecs::codec_t; + +ctpl::thread_pool ftl::pool(4); + +namespace ftl { + bool running = true; + + namespace codecs { + namespace internal { + + void init_encoders() {} + + } + } +} + +TEST_CASE( "NvPipeEncoder::encode() - A colour test image at preset 0" ) { + ftl::codecs::NvPipeEncoder encoder(definition_t::HD1080, definition_t::SD480); + cv::Mat m(cv::Size(1920,1080), CV_8UC3, cv::Scalar(0,0,0)); + + int block_total = 0; + std::atomic<int> block_count = 0; + + const CodecPreset &preset = ftl::codecs::getPreset(ftl::codecs::kPreset0); + + bool r = encoder.encode(m, ftl::codecs::kPreset0, [&block_total, &block_count, preset, m](const ftl::codecs::Packet &pkt) { + REQUIRE( pkt.codec == codec_t::HEVC ); + REQUIRE( pkt.data.size() > 0 ); + REQUIRE( pkt.definition == preset.colour_res ); + + block_total = pkt.block_total; + block_count++; + }); + + REQUIRE( r ); + REQUIRE( block_count == block_total ); +} + +TEST_CASE( "NvPipeEncoder::encode() - A depth test image at preset 0" ) { + ftl::codecs::NvPipeEncoder encoder(definition_t::HD1080, definition_t::SD480); + cv::Mat m(cv::Size(1920,1080), CV_32F, cv::Scalar(0.0f)); + + int block_total = 0; + std::atomic<int> block_count = 0; + + const CodecPreset &preset = ftl::codecs::getPreset(ftl::codecs::kPreset0); + + bool r = encoder.encode(m, ftl::codecs::kPreset0, [&block_total, &block_count, preset](const ftl::codecs::Packet &pkt) { + REQUIRE( pkt.codec == codec_t::HEVC ); + REQUIRE( pkt.data.size() > 0 ); + REQUIRE( pkt.definition == preset.depth_res ); + + block_total = pkt.block_total; + block_count++; + }); + + REQUIRE( r ); + REQUIRE( block_count == block_total ); +} + +TEST_CASE( "NvPipeDecoder::decode() - A colour test image" ) { + ftl::codecs::NvPipeEncoder encoder(definition_t::HD1080, definition_t::SD480); + ftl::codecs::NvPipeDecoder decoder; + + cv::Mat in; + cv::Mat out; + bool r = false; + + SECTION("FHD in and out, FHD encoding") { + in = cv::Mat(cv::Size(1920,1080), CV_8UC3, cv::Scalar(255,0,0)); + out = cv::Mat(cv::Size(1920,1080), CV_8UC3, cv::Scalar(0,0,0)); + + r = encoder.encode(in, ftl::codecs::kPreset0, [&out,&decoder](const ftl::codecs::Packet &pkt) { + REQUIRE( decoder.decode(pkt, out) ); + }); + } + + SECTION("Full HD in, 720 out, FHD encoding") { + in = cv::Mat(cv::Size(1920,1080), CV_8UC3, cv::Scalar(255,0,0)); + out = cv::Mat(cv::Size(1280,720), CV_8UC3, cv::Scalar(0,0,0)); + + r = encoder.encode(in, ftl::codecs::kPreset0, [&out,&decoder](const ftl::codecs::Packet &pkt) { + REQUIRE( decoder.decode(pkt, out) ); + }); + + REQUIRE( (out.rows == 720) ); + } + + SECTION("HHD in, FHD out, FHD encoding") { + in = cv::Mat(cv::Size(1280,720), CV_8UC3, cv::Scalar(255,0,0)); + out = cv::Mat(cv::Size(1920,1080), CV_8UC3, cv::Scalar(0,0,0)); + + r = encoder.encode(in, ftl::codecs::kPreset0, [&out,&decoder](const ftl::codecs::Packet &pkt) { + REQUIRE( decoder.decode(pkt, out) ); + }); + + REQUIRE( (out.rows == 1080) ); + } + + SECTION("FHD in, HHD out, SD encoding") { + in = cv::Mat(cv::Size(1920,1080), CV_8UC3, cv::Scalar(255,0,0)); + out = cv::Mat(cv::Size(1280,720), CV_8UC3, cv::Scalar(0,0,0)); + + r = encoder.encode(in, ftl::codecs::kPreset4, [&out,&decoder](const ftl::codecs::Packet &pkt) { + REQUIRE( decoder.decode(pkt, out) ); + }); + + REQUIRE( (out.rows == 720) ); + } + + REQUIRE( r ); + REQUIRE( (cv::sum(out) != cv::Scalar(0,0,0)) ); +} diff --git a/components/codecs/test/opencv_codec_unit.cpp b/components/codecs/test/opencv_codec_unit.cpp new file mode 100644 index 0000000000000000000000000000000000000000..dd85140dbfcddbd9c48f0c83795a84b0d4914882 --- /dev/null +++ b/components/codecs/test/opencv_codec_unit.cpp @@ -0,0 +1,96 @@ +#include "catch.hpp" +#include <ftl/codecs/opencv_encoder.hpp> +#include <ftl/codecs/opencv_decoder.hpp> +#include <ftl/threads.hpp> + +using ftl::codecs::CodecPreset; +using ftl::codecs::preset_t; +using ftl::codecs::definition_t; +using ftl::codecs::codec_t; + +ctpl::thread_pool ftl::pool(4); + +namespace ftl { + bool running = true; + + namespace codecs { + namespace internal { + + void init_encoders() {} + + } + } +} + +TEST_CASE( "OpenCVEncoder::encode() - A colour test image at preset 0" ) { + ftl::codecs::OpenCVEncoder encoder(definition_t::HD1080, definition_t::SD480); + cv::Mat m(cv::Size(1024,576), CV_8UC3, cv::Scalar(0,0,0)); + + int block_total = 0; + std::atomic<int> block_count = 0; + + const CodecPreset &preset = ftl::codecs::getPreset(ftl::codecs::kPreset4); + + std::mutex mtx; + + bool r = encoder.encode(m, ftl::codecs::kPreset4, [&mtx, &block_total, &block_count, preset, m](const ftl::codecs::Packet &pkt) { + std::unique_lock<std::mutex> lk(mtx); + REQUIRE( pkt.codec == codec_t::JPG ); + REQUIRE( pkt.data.size() > 0 ); + REQUIRE( pkt.definition == preset.colour_res ); + + block_total = pkt.block_total; + block_count++; + + cv::Mat d = cv::imdecode(pkt.data, cv::IMREAD_UNCHANGED); + REQUIRE( !d.empty() ); + REQUIRE( d.cols * d.rows * pkt.block_total == m.cols * m.rows ); + }); + + REQUIRE( r ); + REQUIRE( block_count == block_total ); +} + +TEST_CASE( "OpenCVEncoder::encode() - A depth test image at preset 0" ) { + ftl::codecs::OpenCVEncoder encoder(definition_t::HD1080, definition_t::SD480); + cv::Mat m(cv::Size(1024,576), CV_32F, cv::Scalar(0.0f)); + + int block_total = 0; + std::atomic<int> block_count = 0; + + const CodecPreset &preset = ftl::codecs::getPreset(ftl::codecs::kPreset4); + + std::mutex mtx; + + bool r = encoder.encode(m, ftl::codecs::kPreset4, [&mtx, &block_total, &block_count, preset](const ftl::codecs::Packet &pkt) { + std::unique_lock<std::mutex> lk(mtx); + REQUIRE( pkt.codec == codec_t::PNG ); + REQUIRE( pkt.data.size() > 0 ); + REQUIRE( pkt.definition == preset.depth_res ); + + block_total = pkt.block_total; + block_count++; + + cv::Mat d = cv::imdecode(pkt.data, cv::IMREAD_UNCHANGED); + REQUIRE( !d.empty() ); + }); + + REQUIRE( r ); + REQUIRE( block_count == block_total ); +} + +TEST_CASE( "OpenCVDecoder::decode() - A colour test image no resolution change" ) { + ftl::codecs::OpenCVEncoder encoder(definition_t::HD1080, definition_t::SD480); + ftl::codecs::OpenCVDecoder decoder; + cv::Mat in(cv::Size(1024,576), CV_8UC3, cv::Scalar(255,0,0)); + cv::Mat out(cv::Size(1024,576), CV_8UC3, cv::Scalar(0,0,0)); + + std::mutex mtx; + + bool r = encoder.encode(in, ftl::codecs::kPreset4, [&mtx, &out,&decoder](const ftl::codecs::Packet &pkt) { + std::unique_lock<std::mutex> lk(mtx); + REQUIRE( decoder.decode(pkt, out) ); + }); + + REQUIRE( (cv::sum(out) != cv::Scalar(0,0,0)) ); +} diff --git a/components/codecs/test/tests.cpp b/components/codecs/test/tests.cpp new file mode 100644 index 0000000000000000000000000000000000000000..178916eab8b9c7aabb87ff99894b48443ad6ecb6 --- /dev/null +++ b/components/codecs/test/tests.cpp @@ -0,0 +1,3 @@ +#define CATCH_CONFIG_MAIN +#include "catch.hpp" + diff --git a/components/common/cpp/CMakeLists.txt b/components/common/cpp/CMakeLists.txt index 60577c96040dae74e93ccfe6f8ee4ae0c7abc856..de0f7707c504447a16967625c2f01ab76e1218bb 100644 --- a/components/common/cpp/CMakeLists.txt +++ b/components/common/cpp/CMakeLists.txt @@ -6,6 +6,8 @@ set(COMMONSRC src/loguru.cpp src/opencv_to_pcl.cpp src/cuda_common.cpp + src/ctpl_stl.cpp + src/timer.cpp ) check_function_exists(uriParseSingleUriA HAVE_URIPARSESINGLE) @@ -19,7 +21,7 @@ target_include_directories(ftlcommon PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include> PRIVATE src) -target_link_libraries(ftlcommon Threads::Threads ${OS_LIBS} ${OpenCV_LIBS} ${PCL_LIBRARIES} ${URIPARSER_LIBRARIES}) +target_link_libraries(ftlcommon Threads::Threads ${OS_LIBS} ${OpenCV_LIBS} ${PCL_LIBRARIES} ${URIPARSER_LIBRARIES} ${CUDA_LIBRARIES}) add_subdirectory(test) diff --git a/components/common/cpp/include/ctpl_stl.h b/components/common/cpp/include/ctpl_stl.h index 82d8029850bad486e3e1acfcb9b3f51886e8f6d2..fac0de42a001711a7033dfc5142ff33a54323525 100644 --- a/components/common/cpp/include/ctpl_stl.h +++ b/components/common/cpp/include/ctpl_stl.h @@ -62,6 +62,10 @@ namespace ctpl { std::unique_lock<std::mutex> lock(this->mutex); return this->q.empty(); } + size_t size() { + std::unique_lock<std::mutex> lock(this->mutex); + return this->q.size(); + } private: std::queue<T> q; std::mutex mutex; @@ -87,6 +91,8 @@ namespace ctpl { int n_idle() { return this->nWaiting; } std::thread & get_thread(int i) { return *this->threads[i]; } + size_t q_size() { return this->q.size(); } + // change the number of threads in the pool // should be called from one thread, otherwise be careful to not interleave, also with this->stop() // nThreads must be >= 0 @@ -206,32 +212,7 @@ namespace ctpl { thread_pool & operator=(const thread_pool &);// = delete; thread_pool & operator=(thread_pool &&);// = delete; - void set_thread(int i) { - std::shared_ptr<std::atomic<bool>> flag(this->flags[i]); // a copy of the shared ptr to the flag - auto f = [this, i, flag/* a copy of the shared ptr to the flag */]() { - std::atomic<bool> & _flag = *flag; - std::function<void(int id)> * _f; - bool isPop = this->q.pop(_f); - while (true) { - while (isPop) { // if there is anything in the queue - std::unique_ptr<std::function<void(int id)>> func(_f); // at return, delete the function even if an exception occurred - (*_f)(i); - if (_flag) - return; // the thread is wanted to stop, return even if the queue is not empty yet - else - isPop = this->q.pop(_f); - } - // the queue is empty here, wait for the next command - std::unique_lock<std::mutex> lock(this->mutex); - ++this->nWaiting; - this->cv.wait(lock, [this, &_f, &isPop, &_flag](){ isPop = this->q.pop(_f); return isPop || this->isDone || _flag; }); - --this->nWaiting; - if (!isPop) - return; // if the queue is empty and this->isDone == true or *flag then return - } - }; - this->threads[i].reset(new std::thread(f)); // compiler may not support std::make_unique() - } + void set_thread(int i); void init() { this->nWaiting = 0; this->isStop = false; this->isDone = false; } diff --git a/components/common/cpp/include/ftl/config.h.in b/components/common/cpp/include/ftl/config.h.in index ff965cbb011c5a52d9e7aed4b45c9e73774e847a..540b332035a2ad5f671a51ed80f0de31d60f1ce7 100644 --- a/components/common/cpp/include/ftl/config.h.in +++ b/components/common/cpp/include/ftl/config.h.in @@ -16,6 +16,7 @@ #cmakedefine HAVE_URIPARSESINGLE #cmakedefine HAVE_CUDA #cmakedefine HAVE_OPENCV +#cmakedefine HAVE_OPTFLOW #cmakedefine HAVE_PCL #cmakedefine HAVE_RENDER #cmakedefine HAVE_LIBSGM @@ -23,6 +24,7 @@ #cmakedefine HAVE_NANOGUI #cmakedefine HAVE_LIBARCHIVE #cmakedefine HAVE_OPENVR +#cmakedefine HAVE_NVPIPE extern const char *FTL_BRANCH; extern const char *FTL_VERSION_LONG; diff --git a/components/common/cpp/include/ftl/configurable.hpp b/components/common/cpp/include/ftl/configurable.hpp index 5bae67b4a9972d5f69d0947871d5be259df36be3..94a080eaed480fa2e57b113e9c8c3d7bc04388bd 100644 --- a/components/common/cpp/include/ftl/configurable.hpp +++ b/components/common/cpp/include/ftl/configurable.hpp @@ -68,7 +68,9 @@ class Configurable { template <typename T> T value(const std::string &name, T def) { auto r = get<T>(name); - return (r) ? *r : def; + if (r) return *r; + (*config_)[name] = def; + return def; } /** diff --git a/components/common/cpp/include/ftl/cuda_common.hpp b/components/common/cpp/include/ftl/cuda_common.hpp index 3f004d0c8ffefaf692b84106dea3810e223d70f3..70a6a4ad6d4dc0def715979f9eaf348e621d103e 100644 --- a/components/common/cpp/include/ftl/cuda_common.hpp +++ b/components/common/cpp/include/ftl/cuda_common.hpp @@ -2,12 +2,19 @@ #define _FTL_CUDA_COMMON_HPP_ #include <ftl/config.h> +#include <ftl/traits.hpp> #if defined HAVE_CUDA +#include <ftl/cuda_util.hpp> #include <opencv2/core/cuda.hpp> #include <opencv2/core/cuda/common.hpp> +#ifndef __CUDACC__ +#include <loguru.hpp> +#include <exception> +#endif + /* Grid stride loop macros */ #define STRIDE_Y(I,N) int I = blockIdx.y * blockDim.y + threadIdx.y; I < N; I += blockDim.y * gridDim.y #define STRIDE_X(I,N) int I = blockIdx.x * blockDim.x + threadIdx.x; I < N; I += blockDim.x * gridDim.x @@ -15,69 +22,151 @@ namespace ftl { namespace cuda { -/*template <typename T> -class HisteresisTexture { +bool initialise(); + +bool hasCompute(int major, int minor); + +int deviceCount(); + +/** + * Represent a CUDA texture object. Instances of this class can be used on both + * host and device. A texture object base cannot be constructed directly, it + * must be constructed via a template TextureObject class. + */ +class TextureObjectBase { public: - HisteresisTexture(); - ~HisteresisTexture(); + __host__ __device__ TextureObjectBase() + : texobj_(0), pitch_(0), pitch2_(0), width_(0), height_(0), + ptr_(nullptr), needsfree_(false), needsdestroy_(false), + cvType_(-1) {}; + ~TextureObjectBase(); + + // Remove ability to copy object directly, instead must use + // templated derivative TextureObject. + TextureObjectBase(const TextureObjectBase &)=delete; + TextureObjectBase &operator=(const TextureObjectBase &)=delete; + + TextureObjectBase(TextureObjectBase &&); + TextureObjectBase &operator=(TextureObjectBase &&); + + inline size_t pitch() const { return pitch_; } + inline size_t pixelPitch() const { return pitch2_; } + inline uchar *devicePtr() const { return ptr_; }; + __host__ __device__ inline uchar *devicePtr(int v) const { return &ptr_[v*pitch_]; } + __host__ __device__ inline int width() const { return width_; } + __host__ __device__ inline int height() const { return height_; } + __host__ __device__ inline cudaTextureObject_t cudaTexture() const { return texobj_; } + + void upload(const cv::Mat &, cudaStream_t stream=0); + void download(cv::Mat &, cudaStream_t stream=0) const; - HisteresisTexture<T> &operator=(TextureObject<T> &t); -};*/ + __host__ void free(); + inline int cvType() const { return cvType_; } + + protected: + cudaTextureObject_t texobj_; + size_t pitch_; + size_t pitch2_; // in T units + int width_; + int height_; + uchar *ptr_; // Device memory pointer + bool needsfree_; // We manage memory, so free it + bool needsdestroy_; // The texture object needs to be destroyed + int cvType_; // Used to validate casting +}; + +/** + * Create and manage CUDA texture objects with a particular pixel data type. + * Note: it is not possible to create texture objects for certain types, + * specificially for 3 channel types. + */ template <typename T> -class TextureObject { +class TextureObject : public TextureObjectBase { public: - __host__ __device__ TextureObject() - : texobj_(0), pitch_(0), pitch2_(0), width_(0), height_(0), ptr_(nullptr), needsfree_(false) {}; - TextureObject(const cv::cuda::PtrStepSz<T> &d); + typedef T type; + + static_assert((16u % sizeof(T)) == 0, "Channel format must be aligned with 16 bytes"); + + __host__ __device__ TextureObject() : TextureObjectBase() {}; + explicit TextureObject(const cv::cuda::GpuMat &d); + explicit TextureObject(const cv::cuda::PtrStepSz<T> &d); TextureObject(T *ptr, int pitch, int width, int height); TextureObject(size_t width, size_t height); TextureObject(const TextureObject<T> &t); __host__ __device__ TextureObject(TextureObject<T> &&); ~TextureObject(); - __host__ TextureObject<T> &operator=(const TextureObject<T> &); + TextureObject<T> &operator=(const TextureObject<T> &); __host__ __device__ TextureObject<T> &operator=(TextureObject<T> &&); - - size_t pitch() const { return pitch_; } - size_t pixelPitch() const { return pitch2_; } - T *devicePtr() const { return ptr_; }; - __host__ __device__ T *devicePtr(int v) const { return &ptr_[v*pitch2_]; } - __host__ __device__ int width() const { return width_; } - __host__ __device__ int height() const { return height_; } - __host__ __device__ cudaTextureObject_t cudaTexture() const { return texobj_; } + + operator cv::cuda::GpuMat(); + + __host__ __device__ T *devicePtr() const { return (T*)(ptr_); }; + __host__ __device__ T *devicePtr(int v) const { return &(T*)(ptr_)[v*pitch2_]; } #ifdef __CUDACC__ __device__ inline T tex2D(int u, int v) const { return ::tex2D<T>(texobj_, u, v); } __device__ inline T tex2D(float u, float v) const { return ::tex2D<T>(texobj_, u, v); } #endif - __host__ __device__ inline const T &operator()(int u, int v) const { return ptr_[u+v*pitch2_]; } - __host__ __device__ inline T &operator()(int u, int v) { return ptr_[u+v*pitch2_]; } + __host__ __device__ inline const T &operator()(int u, int v) const { return reinterpret_cast<T*>(ptr_)[u+v*pitch2_]; } + __host__ __device__ inline T &operator()(int u, int v) { return reinterpret_cast<T*>(ptr_)[u+v*pitch2_]; } - void upload(const cv::Mat &, cudaStream_t stream=0); - void download(cv::Mat &, cudaStream_t stream=0) const; - - __host__ void free() { - if (needsfree_) { - if (texobj_ != 0) cudaSafeCall( cudaDestroyTextureObject (texobj_) ); - if (ptr_) cudaFree(ptr_); - ptr_ = nullptr; - texobj_ = 0; - } - } - - private: - cudaTextureObject_t texobj_; - size_t pitch_; - size_t pitch2_; // in T units - int width_; - int height_; - T *ptr_; - bool needsfree_; - //bool needsdestroy_; + /** + * Cast a base texture object to this type of texture object. If the + * underlying pixel types do not match then a bad_cast exception is thrown. + */ + static TextureObject<T> &cast(TextureObjectBase &); }; +#ifndef __CUDACC__ +template <typename T> +TextureObject<T> &TextureObject<T>::cast(TextureObjectBase &b) { + if (b.cvType() != ftl::traits::OpenCVType<T>::value) { + LOG(ERROR) << "Bad cast of texture object"; + throw std::bad_cast(); + } + return reinterpret_cast<TextureObject<T>&>(b); +} + +/** + * Create a 2D array texture from an OpenCV GpuMat object. + */ +template <typename T> +TextureObject<T>::TextureObject(const cv::cuda::GpuMat &d) { + // GpuMat must have correct data type + CHECK(d.type() == ftl::traits::OpenCVType<T>::value); + + cudaResourceDesc resDesc; + memset(&resDesc, 0, sizeof(resDesc)); + resDesc.resType = cudaResourceTypePitch2D; + resDesc.res.pitch2D.devPtr = d.data; + resDesc.res.pitch2D.pitchInBytes = d.step; + resDesc.res.pitch2D.desc = cudaCreateChannelDesc<T>(); + resDesc.res.pitch2D.width = d.cols; + resDesc.res.pitch2D.height = d.rows; + + cudaTextureDesc texDesc; + // cppcheck-suppress memsetClassFloat + memset(&texDesc, 0, sizeof(texDesc)); + texDesc.readMode = cudaReadModeElementType; + + cudaTextureObject_t tex = 0; + cudaSafeCall(cudaCreateTextureObject(&tex, &resDesc, &texDesc, NULL)); + texobj_ = tex; + pitch_ = d.step; + pitch2_ = pitch_ / sizeof(T); + ptr_ = d.data; + width_ = d.cols; + height_ = d.rows; + needsfree_ = false; + cvType_ = ftl::traits::OpenCVType<T>::value; + //needsdestroy_ = true; +} + +#endif // __CUDACC__ + /** * Create a 2D array texture from an OpenCV GpuMat object. */ @@ -93,6 +182,7 @@ TextureObject<T>::TextureObject(const cv::cuda::PtrStepSz<T> &d) { resDesc.res.pitch2D.height = d.rows; cudaTextureDesc texDesc; + // cppcheck-suppress memsetClassFloat memset(&texDesc, 0, sizeof(texDesc)); texDesc.readMode = cudaReadModeElementType; @@ -105,6 +195,7 @@ TextureObject<T>::TextureObject(const cv::cuda::PtrStepSz<T> &d) { width_ = d.cols; height_ = d.rows; needsfree_ = false; + cvType_ = ftl::traits::OpenCVType<T>::value; //needsdestroy_ = true; } @@ -124,6 +215,7 @@ TextureObject<T>::TextureObject(T *ptr, int pitch, int width, int height) { resDesc.res.pitch2D.height = height; cudaTextureDesc texDesc; + // cppcheck-suppress memsetClassFloat memset(&texDesc, 0, sizeof(texDesc)); texDesc.readMode = cudaReadModeElementType; @@ -136,6 +228,7 @@ TextureObject<T>::TextureObject(T *ptr, int pitch, int width, int height) { width_ = width; height_ = height; needsfree_ = false; + cvType_ = ftl::traits::OpenCVType<T>::value; //needsdestroy_ = true; } @@ -156,6 +249,7 @@ TextureObject<T>::TextureObject(size_t width, size_t height) { resDesc.res.pitch2D.height = height; cudaTextureDesc texDesc; + // cppcheck-suppress memsetClassFloat memset(&texDesc, 0, sizeof(texDesc)); texDesc.readMode = cudaReadModeElementType; cudaCreateTextureObject(&tex, &resDesc, &texDesc, NULL); @@ -166,6 +260,7 @@ TextureObject<T>::TextureObject(size_t width, size_t height) { height_ = (int)height; needsfree_ = true; pitch2_ = pitch_ / sizeof(T); + cvType_ = ftl::traits::OpenCVType<T>::value; //needsdestroy_ = true; } @@ -177,6 +272,7 @@ TextureObject<T>::TextureObject(const TextureObject<T> &p) { height_ = p.height_; pitch_ = p.pitch_; pitch2_ = pitch_ / sizeof(T); + cvType_ = ftl::traits::OpenCVType<T>::value; needsfree_ = false; } @@ -192,6 +288,7 @@ TextureObject<T>::TextureObject(TextureObject<T> &&p) { p.texobj_ = 0; p.needsfree_ = false; p.ptr_ = nullptr; + cvType_ = ftl::traits::OpenCVType<T>::value; } template <typename T> @@ -202,6 +299,7 @@ TextureObject<T> &TextureObject<T>::operator=(const TextureObject<T> &p) { height_ = p.height_; pitch_ = p.pitch_; pitch2_ = pitch_ / sizeof(T); + cvType_ = ftl::traits::OpenCVType<T>::value; needsfree_ = false; return *this; } @@ -218,6 +316,7 @@ TextureObject<T> &TextureObject<T>::operator=(TextureObject<T> &&p) { p.texobj_ = 0; p.needsfree_ = false; p.ptr_ = nullptr; + cvType_ = ftl::traits::OpenCVType<T>::value; return *this; } @@ -228,7 +327,7 @@ TextureObject<T>::~TextureObject() { free(); } -template <> +/*template <> void TextureObject<uchar4>::upload(const cv::Mat &m, cudaStream_t stream); template <> @@ -257,7 +356,7 @@ template <> void TextureObject<float4>::download(cv::Mat &m, cudaStream_t stream) const; template <> -void TextureObject<uchar>::download(cv::Mat &m, cudaStream_t stream) const; +void TextureObject<uchar>::download(cv::Mat &m, cudaStream_t stream) const;*/ } } diff --git a/applications/reconstruct/include/ftl/cuda_matrix_util.hpp b/components/common/cpp/include/ftl/cuda_matrix_util.hpp similarity index 99% rename from applications/reconstruct/include/ftl/cuda_matrix_util.hpp rename to components/common/cpp/include/ftl/cuda_matrix_util.hpp index e8d803e6fdf7b7c62d52d456ddce288bf554b233..4dd1db7fa8a58c84a40f9d93abd8cc0a492b2792 100644 --- a/applications/reconstruct/include/ftl/cuda_matrix_util.hpp +++ b/components/common/cpp/include/ftl/cuda_matrix_util.hpp @@ -1606,6 +1606,7 @@ inline __device__ __host__ matNxM<2, 2> matNxM<2, 2>::getInverse() const // To Matrix from floatNxN template<> +// cppcheck-suppress syntaxError template<> inline __device__ __host__ matNxM<1, 1>::matNxM(const float& other) { diff --git a/applications/reconstruct/include/ftl/cuda_operators.hpp b/components/common/cpp/include/ftl/cuda_operators.hpp similarity index 99% rename from applications/reconstruct/include/ftl/cuda_operators.hpp rename to components/common/cpp/include/ftl/cuda_operators.hpp index b7a3b44426bf6fcf490af67f38461ca849ade428..5fc84fbcb158bc599b8bca55e38035757a857648 100644 --- a/applications/reconstruct/include/ftl/cuda_operators.hpp +++ b/components/common/cpp/include/ftl/cuda_operators.hpp @@ -19,7 +19,8 @@ #ifndef _FTL_CUDA_OPERATORS_HPP_ #define _FTL_CUDA_OPERATORS_HPP_ -#include <cuda_runtime.h> +//#include <cuda_runtime.h> +#include <ftl/cuda_util.hpp> //////////////////////////////////////////////////////////////////////////////// @@ -406,6 +407,12 @@ inline __host__ __device__ float length(float3 v) return sqrtf(dot(v, v)); } +// length squared +inline __host__ __device__ float length2(const float3 &v) +{ + return dot(v, v); +} + // normalize inline __host__ __device__ float3 normalize(float3 v) { diff --git a/applications/reconstruct/include/ftl/cuda_util.hpp b/components/common/cpp/include/ftl/cuda_util.hpp similarity index 86% rename from applications/reconstruct/include/ftl/cuda_util.hpp rename to components/common/cpp/include/ftl/cuda_util.hpp index bf018f07919fe52c71a1104a6bf029ce52f17b8e..e55c1430c1c013780e4b5d9fc0381492da281e49 100644 --- a/applications/reconstruct/include/ftl/cuda_util.hpp +++ b/components/common/cpp/include/ftl/cuda_util.hpp @@ -6,7 +6,12 @@ #undef max #undef min +#ifdef CPPCHECK +#define __align__(A) +#endif + #include <cuda_runtime.h> +#include <vector_types.h> #include <ftl/cuda_operators.hpp> // Enable run time assertion checking in kernel code diff --git a/components/common/cpp/include/ftl/exception.hpp b/components/common/cpp/include/ftl/exception.hpp new file mode 100644 index 0000000000000000000000000000000000000000..6719158bba5f5f53abfcdeb0fb39a5b5dda0d5f2 --- /dev/null +++ b/components/common/cpp/include/ftl/exception.hpp @@ -0,0 +1,19 @@ +#ifndef _FTL_EXCEPTION_HPP_ +#define _FTL_EXCEPTION_HPP_ + +namespace ftl { +class exception : public std::exception +{ + public: + explicit exception(const char *msg) : msg_(msg) {}; + + const char * what () const throw () { + return msg_; + } + + private: + const char *msg_; +}; +} + +#endif // _FTL_EXCEPTION_HPP_ diff --git a/components/common/cpp/include/ftl/threads.hpp b/components/common/cpp/include/ftl/threads.hpp index 5de4f5f75b6ff8e7012bd45b5f2aca8a41bc7b63..83086135a4e535d7f2c4f8ce03ab07dadbe871e4 100644 --- a/components/common/cpp/include/ftl/threads.hpp +++ b/components/common/cpp/include/ftl/threads.hpp @@ -7,8 +7,8 @@ #define POOL_SIZE 10 -#define DEBUG_MUTEX -#define MUTEX_TIMEOUT 15 +//#define DEBUG_MUTEX +#define MUTEX_TIMEOUT 5 #if defined DEBUG_MUTEX #include <loguru.hpp> diff --git a/components/common/cpp/include/ftl/timer.hpp b/components/common/cpp/include/ftl/timer.hpp new file mode 100644 index 0000000000000000000000000000000000000000..97776c2c3ee9bcbb62b8d81909d87a7dc43ecd28 --- /dev/null +++ b/components/common/cpp/include/ftl/timer.hpp @@ -0,0 +1,114 @@ +#ifndef _FTL_COMMON_TIMER_HPP_ +#define _FTL_COMMON_TIMER_HPP_ + +#include <functional> + +namespace ftl { + +/** + * A single global timer mechanism to call functions with either high or low + * precision timing accuracy. This is used to provide accurate frame timing for + * capture and rendering sync and it is deliberately a namespace and not a class + * since the entire system should be operating at a single frame rate. It + * controls the entire pipelines behaviour. It uses timestamps that are + * multiples of the millisecond interval between frames. + */ +namespace timer { + +enum timerlevel_t { + kTimerHighPrecision = 0, + kTimerSwap, + kTimerMain, + kTimerIdle1, // 1ms jobs to optionally do whilst idle + kTimerIdle10, // 10ms jobs to optionally do whilst idle + kTimerMAXLEVEL +}; + +/** + * Represents a timer job for control purposes. Use to remove timer jobs in + * a destructor, for example. + */ +struct TimerHandle { + TimerHandle() : id_(-1) {} + explicit TimerHandle(int i) : id_(i) {} + TimerHandle(const TimerHandle &t) : id_(t.id()) {} + + /** + * Cancel the timer job. If currently executing it will block and wait for + * the job to complete. + */ + void cancel() const; + void pause() const; + void unpause() const; + + /** + * Do the timer job every N frames. + */ + void setMultiplier(unsigned int) const; + + /** + * Give the timer job a name for logging output. + */ + void setName(const std::string &) const; + + /** + * Allow copy assignment. + */ + TimerHandle &operator=(const TimerHandle &h) { id_ = h.id(); return *this; } + + inline int id() const { return id_; } + + private: + int id_; +}; + +int64_t get_time(); + +/** + * Milliseconds between calls. + */ +void setInterval(int ms); + +int getInterval(); + +int64_t get_time(); + +/** + * Add the specified number of milliseconds to the clock when generating + * timestamps. This is used to synchronise clocks on multiple machines as it + * influences the behaviour of the timer. + */ +void setClockAdjustment(int64_t ms); + +/** + * Add a timer callback with a given precision and ordering. The highest + * precision callbacks occur within 1ms of required time and should return + * almost immediately to prevent delays to other callbacks. Other precisions + * occur later and in separate thread jobs for each callback. If a callback + * fails to return before the next time step, it is skipped for that timestep. + * If all high precision callbacks together take more than 1ms to complete, a + * warning is produced. + */ +const TimerHandle add(timerlevel_t, const std::function<bool(int64_t ts)> &); + +/** + * Initiate the timer and optionally block the current process. + */ +void start(bool block=false); + +/** + * Stop the timer after any current callbacks complete. Blocks until stopped. + */ +void stop(bool wait=true); + +size_t count(timerlevel_t); + +/** + * Stop and clear all callbacks. Used for testing purposes. + */ +void reset(); + +} +} + +#endif // _FTL_COMMON_TIMER_HPP_ diff --git a/components/common/cpp/include/ftl/traits.hpp b/components/common/cpp/include/ftl/traits.hpp new file mode 100644 index 0000000000000000000000000000000000000000..6ac54e8e66f6a12aa13e39e1228f5cf17ca69312 --- /dev/null +++ b/components/common/cpp/include/ftl/traits.hpp @@ -0,0 +1,44 @@ +#ifndef _FTL_TRAITS_HPP_ +#define _FTL_TRAITS_HPP_ + +#include <opencv2/core.hpp> +#include <ftl/cuda_util.hpp> + +namespace ftl { +namespace traits { + +template <typename T> +struct AlwaysFalse : std::false_type {}; + +template <typename T> struct OpenCVType { + static_assert(AlwaysFalse<T>::value, "Not a valid format type"); +}; +template <> struct OpenCVType<uchar> { static const int value = CV_8UC1; }; +template <> struct OpenCVType<uchar2> { static const int value = CV_8UC2; }; +template <> struct OpenCVType<uchar3> { static const int value = CV_8UC3; }; +template <> struct OpenCVType<uchar4> { static const int value = CV_8UC4; }; +template <> struct OpenCVType<char> { static const int value = CV_8SC1; }; +template <> struct OpenCVType<char2> { static const int value = CV_8SC2; }; +template <> struct OpenCVType<char3> { static const int value = CV_8SC3; }; +template <> struct OpenCVType<char4> { static const int value = CV_8SC4; }; +template <> struct OpenCVType<ushort> { static const int value = CV_16UC1; }; +template <> struct OpenCVType<ushort2> { static const int value = CV_16UC2; }; +template <> struct OpenCVType<ushort3> { static const int value = CV_16UC3; }; +template <> struct OpenCVType<ushort4> { static const int value = CV_16UC4; }; +template <> struct OpenCVType<short> { static const int value = CV_16SC1; }; +template <> struct OpenCVType<short2> { static const int value = CV_16SC2; }; +template <> struct OpenCVType<short3> { static const int value = CV_16SC3; }; +template <> struct OpenCVType<short4> { static const int value = CV_16SC4; }; +template <> struct OpenCVType<int> { static const int value = CV_32SC1; }; +template <> struct OpenCVType<int2> { static const int value = CV_32SC2; }; +template <> struct OpenCVType<int3> { static const int value = CV_32SC3; }; +template <> struct OpenCVType<int4> { static const int value = CV_32SC4; }; +template <> struct OpenCVType<float> { static const int value = CV_32FC1; }; +template <> struct OpenCVType<float2> { static const int value = CV_32FC2; }; +template <> struct OpenCVType<float3> { static const int value = CV_32FC3; }; +template <> struct OpenCVType<float4> { static const int value = CV_32FC4; }; + +} +} + +#endif // _FTL_TRAITS_HPP_ diff --git a/components/common/cpp/src/configuration.cpp b/components/common/cpp/src/configuration.cpp index 991fdc120b46b5f123bf31a89bb9a25df097aafc..8a3849e8ee8799119f3d15700dc277e1dece7654 100644 --- a/components/common/cpp/src/configuration.cpp +++ b/components/common/cpp/src/configuration.cpp @@ -19,6 +19,8 @@ #include <ftl/configurable.hpp> #include <ftl/uri.hpp> #include <ftl/threads.hpp> +#include <ftl/timer.hpp> +#include <ftl/cuda_common.hpp> #include <fstream> #include <string> @@ -35,7 +37,7 @@ using ftl::is_file; using ftl::is_directory; using ftl::Configurable; -ctpl::thread_pool ftl::pool(POOL_SIZE); +ctpl::thread_pool ftl::pool(std::thread::hardware_concurrency()*2); // Store loaded configuration namespace ftl { @@ -437,9 +439,14 @@ static void process_options(Configurable *root, const map<string, string> &opts) } } +static bool sig_int_called = false; + static void signalIntHandler( int signum ) { std::cout << "Closing...\n"; + if (sig_int_called) quick_exit(-1); + sig_int_called = true; + // cleanup and close up stuff here // terminate program @@ -451,7 +458,7 @@ Configurable *ftl::config::configure(ftl::config::json_t &cfg) { loguru::g_preamble_uptime = false; loguru::g_preamble_thread = false; int argc = 1; - const char *argv[] = {"d",0}; + const char *argv[]{"d",0}; loguru::init(argc, const_cast<char**>(argv), "--verbosity"); config_index.clear(); @@ -510,6 +517,16 @@ Configurable *ftl::config::configure(int argc, char **argv, const std::string &r } }); + // Some global settings + ftl::timer::setInterval(1000 / rootcfg->value("fps",20)); + + // Check CUDA + ftl::cuda::initialise(); + + int pool_size = rootcfg->value("thread_pool_factor", 2.0f)*std::thread::hardware_concurrency(); + if (pool_size != ftl::pool.size()) ftl::pool.resize(pool_size); + + //LOG(INFO) << "CONFIG: " << config["vision_default"]; //CHECK_EQ( &config, config_index["ftl://utu.fi"] ); diff --git a/components/common/cpp/src/ctpl_stl.cpp b/components/common/cpp/src/ctpl_stl.cpp new file mode 100644 index 0000000000000000000000000000000000000000..08308a975dfc9fd92b1a0a5d22336f6d68f82a97 --- /dev/null +++ b/components/common/cpp/src/ctpl_stl.cpp @@ -0,0 +1,59 @@ +#ifdef WIN32 +#include <Ws2tcpip.h> +#include <windows.h> +#endif + +#include <ctpl_stl.h> + +void ctpl::thread_pool::set_thread(int i) { + std::shared_ptr<std::atomic<bool>> flag(this->flags[i]); // a copy of the shared ptr to the flag + auto f = [this, i, flag/* a copy of the shared ptr to the flag */]() { + std::atomic<bool> & _flag = *flag; + std::function<void(int id)> * _f; + bool isPop = this->q.pop(_f); + while (true) { + while (isPop) { // if there is anything in the queue + std::unique_ptr<std::function<void(int id)>> func(_f); // at return, delete the function even if an exception occurred + (*_f)(i); + if (_flag) + return; // the thread is wanted to stop, return even if the queue is not empty yet + else + isPop = this->q.pop(_f); + } + // the queue is empty here, wait for the next command + std::unique_lock<std::mutex> lock(this->mutex); + ++this->nWaiting; + this->cv.wait(lock, [this, &_f, &isPop, &_flag](){ isPop = this->q.pop(_f); return isPop || this->isDone || _flag; }); + --this->nWaiting; + if (!isPop) + return; // if the queue is empty and this->isDone == true or *flag then return + } + }; + this->threads[i].reset(new std::thread(f)); // compiler may not support std::make_unique() + + // For excess threads, ensure they only operate if needed. + /*if (i >= std::thread::hardware_concurrency()-1) { + #ifndef WIN32 + sched_param p; + p.sched_priority = sched_get_priority_min(SCHED_FIFO); + pthread_setschedparam(threads[i]->native_handle(), SCHED_FIFO, &p); + #endif + } else { + #ifndef WIN32 + sched_param p; + p.sched_priority = sched_get_priority_max(SCHED_FIFO); + pthread_setschedparam(threads[i]->native_handle(), SCHED_FIFO, &p); + #endif + }*/ + + /* + #ifdef WIN32 + SetThreadAffinityMask(this->threads[i]->native_handle(), 1 << i); + #else + cpu_set_t cpus; + CPU_ZERO(&cpus); + CPU_SET(i, &cpus); + pthread_setaffinity_np(this->threads[i]->native_handle(), sizeof(cpus), &cpus); + #endif + */ +} diff --git a/components/common/cpp/src/cuda_common.cpp b/components/common/cpp/src/cuda_common.cpp index 571d6816b63413163471ef9006ae1d28031511fd..b29c1df08ba14d61a17d8b7afce290b353c25ce6 100644 --- a/components/common/cpp/src/cuda_common.cpp +++ b/components/common/cpp/src/cuda_common.cpp @@ -1,8 +1,106 @@ #include <ftl/cuda_common.hpp> -using ftl::cuda::TextureObject; +using ftl::cuda::TextureObjectBase; -template <> +static int dev_count = 0; +static std::vector<cudaDeviceProp> properties; + +bool ftl::cuda::initialise() { + // Do an initial CUDA check + cudaSafeCall(cudaGetDeviceCount(&dev_count)); + CHECK_GE(dev_count, 1) << "No CUDA devices found"; + + LOG(INFO) << "CUDA Devices (" << dev_count << "):"; + + properties.resize(dev_count); + for (int i=0; i<dev_count; i++) { + cudaSafeCall(cudaGetDeviceProperties(&properties[i], i)); + LOG(INFO) << " - " << properties[i].name; + } + + return true; +} + +bool ftl::cuda::hasCompute(int major, int minor) { + int dev = -1; + cudaSafeCall(cudaGetDevice(&dev)); + + if (dev > 0) { + return properties[dev].major > major || + (properties[dev].major == major && properties[dev].minor >= minor); + } + return false; +} + +int ftl::cuda::deviceCount() { + return dev_count; +} + +TextureObjectBase::~TextureObjectBase() { + free(); +} + +TextureObjectBase::TextureObjectBase(TextureObjectBase &&o) { + needsfree_ = o.needsfree_; + needsdestroy_ = o.needsdestroy_; + ptr_ = o.ptr_; + texobj_ = o.texobj_; + cvType_ = o.cvType_; + width_ = o.width_; + height_ = o.height_; + pitch_ = o.pitch_; + pitch2_ = o.pitch2_; + + o.ptr_ = nullptr; + o.needsfree_ = false; + o.texobj_ = 0; + o.needsdestroy_ = false; +} + +TextureObjectBase &TextureObjectBase::operator=(TextureObjectBase &&o) { + free(); + + needsfree_ = o.needsfree_; + needsdestroy_ = o.needsdestroy_; + ptr_ = o.ptr_; + texobj_ = o.texobj_; + cvType_ = o.cvType_; + width_ = o.width_; + height_ = o.height_; + pitch_ = o.pitch_; + pitch2_ = o.pitch2_; + + o.ptr_ = nullptr; + o.needsfree_ = false; + o.texobj_ = 0; + o.needsdestroy_ = false; + return *this; +} + +void TextureObjectBase::free() { + if (needsfree_) { + if (texobj_ != 0) cudaSafeCall( cudaDestroyTextureObject (texobj_) ); + if (ptr_) cudaFree(ptr_); + ptr_ = nullptr; + texobj_ = 0; + cvType_ = -1; + } else if (needsdestroy_) { + if (texobj_ != 0) cudaSafeCall( cudaDestroyTextureObject (texobj_) ); + texobj_ = 0; + cvType_ = -1; + } +} + +void TextureObjectBase::upload(const cv::Mat &m, cudaStream_t stream) { + cudaSafeCall(cudaMemcpy2DAsync(devicePtr(), pitch(), m.data, m.step, m.cols * m.elemSize(), m.rows, cudaMemcpyHostToDevice, stream)); +} + +void TextureObjectBase::download(cv::Mat &m, cudaStream_t stream) const { + m.create(height(), width(), cvType_); + cudaSafeCall(cudaMemcpy2DAsync(m.data, m.step, devicePtr(), pitch(), m.cols * m.elemSize(), m.rows, cudaMemcpyDeviceToHost, stream)); +} + +/*template <> void TextureObject<uchar4>::upload(const cv::Mat &m, cudaStream_t stream) { cudaSafeCall(cudaMemcpy2DAsync(devicePtr(), pitch(), m.data, m.step, m.cols * sizeof(uchar4), m.rows, cudaMemcpyHostToDevice, stream)); } @@ -56,4 +154,4 @@ template <> void TextureObject<uchar>::download(cv::Mat &m, cudaStream_t stream) const { m.create(height(), width(), CV_8UC1); cudaSafeCall(cudaMemcpy2DAsync(m.data, m.step, devicePtr(), pitch(), m.cols * sizeof(uchar), m.rows, cudaMemcpyDeviceToHost, stream)); -} +}*/ diff --git a/components/common/cpp/src/timer.cpp b/components/common/cpp/src/timer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..328006ab82041d9613ab9ef7847d74727e3aa7f2 --- /dev/null +++ b/components/common/cpp/src/timer.cpp @@ -0,0 +1,238 @@ +#include <ftl/timer.hpp> +#include <ftl/threads.hpp> +#include <loguru.hpp> + +#include <vector> +#include <list> +#include <chrono> + +#include <xmmintrin.h> + +using std::vector; +using std::list; +using std::function; +using std::chrono::time_point_cast; +using std::chrono::milliseconds; +using std::chrono::high_resolution_clock; +using std::this_thread::sleep_for; + +using namespace ftl::timer; + +static int64_t last_frame = 0; +static int64_t mspf = 50; +static int64_t clock_adjust = 0; +static bool active = false; +static std::atomic<int> active_jobs = 0; +static MUTEX mtx; +static int last_id = 0; + +struct TimerJob { + int id; + function<bool(int64_t)> job; + volatile bool active; + // TODO: (Nick) Implement richer forms of timer + //bool paused; + //int multiplier; + //int countdown; + std::string name; +}; + +static list<TimerJob> jobs[kTimerMAXLEVEL]; + +int64_t ftl::timer::get_time() { + return time_point_cast<milliseconds>(high_resolution_clock::now()).time_since_epoch().count()+clock_adjust; +} + +static void waitTimePoint() { + auto start = high_resolution_clock::now(); + int64_t now = get_time(); + int64_t target = now / mspf; + int64_t msdelay = mspf - (now % mspf); + int64_t sincelast = now - last_frame*mspf; + + if (sincelast > mspf) LOG(WARNING) << "Frame " << "(" << (target-last_frame) << ") dropped by " << sincelast << "ms"; + + // Use sleep_for for larger delays + + //LOG(INFO) << "Required Delay: " << (now / 40) << " = " << msdelay; + while (msdelay >= 35 && sincelast != mspf) { + sleep_for(milliseconds(10)); + now = get_time(); + msdelay = mspf - (now % mspf); + } + + // Still lots of time so do some idle jobs + if (msdelay >= 10 && sincelast != mspf) { + UNIQUE_LOCK(mtx, lk); + auto idle_job = jobs[kTimerIdle10].begin(); + while (idle_job != jobs[kTimerIdle10].end() && msdelay >= 10 && sincelast != mspf) { + (*idle_job).active = true; + bool doremove = !(*idle_job).job(now); + + if (doremove) { + idle_job = jobs[kTimerIdle10].erase(idle_job); + LOG(INFO) << "Timer job removed"; + } else { + (*idle_job++).active = false; + } + now = get_time(); + msdelay = mspf - (now % mspf); + } + } + + // Spin loop until exact grab time + //LOG(INFO) << "Spin Delay: " << (now / 40) << " = " << (40 - (now%40)); + + if (sincelast != mspf) { + target = now / mspf; + while ((now/mspf) == target) { + _mm_pause(); // SSE2 nano pause intrinsic + now = get_time(); + }; + } + last_frame = now/mspf; +} + +void ftl::timer::setInterval(int ms) { + mspf = ms; +} + +int ftl::timer::getInterval() { + return mspf; +} + +void ftl::timer::setClockAdjustment(int64_t ms) { + clock_adjust += ms; +} + +const TimerHandle ftl::timer::add(timerlevel_t l, const std::function<bool(int64_t ts)> &f) { + if (l < 0 || l >= kTimerMAXLEVEL) return {}; + + UNIQUE_LOCK(mtx, lk); + int newid = last_id++; + jobs[l].push_back({newid, f, false, "NoName"}); + return TimerHandle(newid); +} + +static void removeJob(int id) { + UNIQUE_LOCK(mtx, lk); + if (id < 0) return; + for (size_t j=0; j<kTimerMAXLEVEL; ++j) { + for (auto i=jobs[j].begin(); i!=jobs[j].end(); i++) { + if ((*i).id == id) { + while ((*i).active) { + sleep_for(milliseconds(10)); + } + + jobs[j].erase(i); + return; + } + } + } +} + +static void trigger_jobs() { + UNIQUE_LOCK(mtx, lk); + const int64_t ts = last_frame*mspf; + + // First do non-blocking high precision callbacks + const int64_t before = get_time(); + for (auto &j : jobs[kTimerHighPrecision]) { + j.job(ts); + } + const int64_t after = get_time(); + if (after - before > 0) LOG(WARNING) << "Precision jobs took too long (" << (after-before) << "ms)"; + + // Then do also non-blocking swap callbacks + for (auto &j : jobs[kTimerSwap]) { + j.job(ts); + } + + // Now use thread jobs to do more intensive callbacks + for (auto &j : jobs[kTimerMain]) { + if (j.active) { + //LOG(WARNING) << "Timer job too slow ... skipped for " << ts; + continue; + } + j.active = true; + active_jobs++; + ftl::pool.push([&j,ts](int id) { + bool doremove = !j.job(ts); + j.active = false; + active_jobs--; + if (doremove) removeJob(j.id); + }); + } +} + +namespace ftl { + extern bool running; +} + +void ftl::timer::start(bool block) { + if (active) return; + active = true; + + if (block) { + active_jobs++; + while (ftl::running && active) { + waitTimePoint(); + trigger_jobs(); + } + active_jobs--; + } else { + ftl::pool.push([](int id) { + active_jobs++; + while (ftl::running && active) { + waitTimePoint(); + trigger_jobs(); + } + active_jobs--; + }); + } +} + +void ftl::timer::stop(bool wait) { + active = false; + + if (wait) { + // All callbacks must complete before returning. + while (active_jobs > 0) { + sleep_for(milliseconds(10)); + } + } +} + +size_t ftl::timer::count(ftl::timer::timerlevel_t l) { + if (l < 0 || l >= kTimerMAXLEVEL) return 0; + return jobs[l].size(); +} + +void ftl::timer::reset() { + stop(true); + for (int i=0; i<ftl::timer::kTimerMAXLEVEL; i++) { + jobs[i].clear(); + } +} + +// ===== TimerHandle =========================================================== + +void ftl::timer::TimerHandle::cancel() const { + removeJob(id()); +} + +void ftl::timer::TimerHandle::pause() const { + +} + +void ftl::timer::TimerHandle::unpause() const { + +} + +void ftl::timer::TimerHandle::setMultiplier(unsigned int N) const { + +} + +void ftl::timer::TimerHandle::setName(const std::string &name) const { + +} diff --git a/components/common/cpp/src/uri.cpp b/components/common/cpp/src/uri.cpp index 0db7a4af505f28bd162d81eed763241ed6f5ff9d..90fb33522c1c2d701fac47b0a6d43a5a6afee3a7 100644 --- a/components/common/cpp/src/uri.cpp +++ b/components/common/cpp/src/uri.cpp @@ -41,8 +41,9 @@ void URI::_parse(uri_t puri) { // NOTE: Non-standard additions to allow for Unix style relative file names. if (suri[0] == '.') { char cwdbuf[1024]; - getcwd(cwdbuf, 1024); - suri = string("file://") + string(cwdbuf) + suri.substr(1); + if (getcwd(cwdbuf, 1024)) { + suri = string("file://") + string(cwdbuf) + suri.substr(1); + } } else if (suri[0] == '~') { #ifdef WIN32 suri = string("file://") + string(std::getenv("HOMEDRIVE")) + string(std::getenv("HOMEPATH")) + suri.substr(1); diff --git a/components/common/cpp/test/CMakeLists.txt b/components/common/cpp/test/CMakeLists.txt index e2a54abbf11716aa0077e21ba25e9ce58dfa521a..f9c1773b92718fc79eb1e26c1d63c7668f12fa53 100644 --- a/components/common/cpp/test/CMakeLists.txt +++ b/components/common/cpp/test/CMakeLists.txt @@ -6,12 +6,14 @@ add_executable(configurable_unit ../src/config.cpp ../src/configuration.cpp ../src/loguru.cpp + ../src/ctpl_stl.cpp + ../src/cuda_common.cpp ./configurable_unit.cpp ) target_include_directories(configurable_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include") target_link_libraries(configurable_unit ${URIPARSER_LIBRARIES} - Threads::Threads ${OS_LIBS}) + Threads::Threads ${OS_LIBS} ${OpenCV_LIBS} ${CUDA_LIBRARIES}) ### URI ######################################################################## add_executable(uri_unit @@ -24,9 +26,22 @@ target_link_libraries(uri_unit Threads::Threads ${OS_LIBS} ${URIPARSER_LIBRARIES}) +### Timer Unit ################################################################ +add_executable(timer_unit + ./tests.cpp + ../src/timer.cpp + ../src/config.cpp + ../src/loguru.cpp + ../src/ctpl_stl.cpp + ./timer_unit.cpp +) +target_include_directories(timer_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include") +target_link_libraries(timer_unit + Threads::Threads ${OS_LIBS}) add_test(ConfigurableUnitTest configurable_unit) add_test(URIUnitTest uri_unit) +# add_test(TimerUnitTest timer_unit) CI server can't achieve this diff --git a/components/common/cpp/test/configurable_unit.cpp b/components/common/cpp/test/configurable_unit.cpp index 428208f7465a85421eb1c7864f384a9dcc3f7a98..af44e026a552279da53986dde38079e7695a889b 100644 --- a/components/common/cpp/test/configurable_unit.cpp +++ b/components/common/cpp/test/configurable_unit.cpp @@ -6,6 +6,12 @@ using ftl::Configurable; using std::string; +namespace ftl { +namespace timer { +void setInterval(int i) {} +} +} + SCENARIO( "Configurable::get()" ) { GIVEN( "a non-existent property" ) { // cppcheck-suppress constStatement diff --git a/components/common/cpp/test/timer_unit.cpp b/components/common/cpp/test/timer_unit.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6cdea157e9228b920ce2220c0122c8dcad9cf76e --- /dev/null +++ b/components/common/cpp/test/timer_unit.cpp @@ -0,0 +1,249 @@ +#include "catch.hpp" +#define LOGURU_REPLACE_GLOG 1 +#include <loguru.hpp> +#include <ftl/timer.hpp> +#include <ftl/threads.hpp> + +ctpl::thread_pool ftl::pool(4); + +namespace ftl { + bool running = true; +} + +TEST_CASE( "Timer::add() High Precision Accuracy" ) { + SECTION( "An instantly returning callback" ) { + bool didrun = false; + + ftl::timer::reset(); + + auto rc = ftl::timer::add(ftl::timer::kTimerHighPrecision, [&didrun](int64_t ts) { + didrun = true; + ftl::timer::stop(false); + return true; + }); + + REQUIRE( (rc.id() >= 0) ); + + ftl::timer::start(true); + REQUIRE( didrun == true ); + } + + SECTION( "A slow returning callback" ) { + bool didrun = false; + + ftl::timer::reset(); + + auto rc = ftl::timer::add(ftl::timer::kTimerHighPrecision, [&didrun](int64_t ts) { + didrun = true; + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + ftl::timer::stop(false); + return true; + }); + + REQUIRE( (rc.id() >= 0) ); + + ftl::timer::start(true); + REQUIRE( didrun == true ); + } + + SECTION( "Multiple callback" ) { + bool didrun[3] = {false}; + + ftl::timer::reset(); + + auto rc = ftl::timer::add(ftl::timer::kTimerHighPrecision, [&didrun](int64_t ts) { + didrun[0] = true; + ftl::timer::stop(false); + return true; + }); + + REQUIRE( (rc.id() >= 0) ); + + ftl::timer::add(ftl::timer::kTimerHighPrecision, [&didrun](int64_t ts) { + didrun[1] = true; + ftl::timer::stop(false); + return true; + }); + + ftl::timer::add(ftl::timer::kTimerHighPrecision, [&didrun](int64_t ts) { + didrun[2] = true; + ftl::timer::stop(false); + return true; + }); + + ftl::timer::start(true); + REQUIRE( didrun[0] == true ); + REQUIRE( didrun[1] == true ); + REQUIRE( didrun[2] == true ); + } +} + +TEST_CASE( "Timer::add() Idle10 job" ) { + SECTION( "Quick idle job" ) { + bool didrun = false; + + ftl::timer::reset(); + + auto rc = ftl::timer::add(ftl::timer::kTimerIdle10, [&didrun](int64_t ts) { + didrun = true; + ftl::timer::stop(false); + return true; + }); + + REQUIRE( (rc.id() >= 0) ); + + ftl::timer::start(true); + REQUIRE( didrun == true ); + } + + SECTION( "Slow idle job" ) { + bool didrun = false; + + ftl::timer::reset(); + + auto rc = ftl::timer::add(ftl::timer::kTimerIdle10, [&didrun](int64_t ts) { + didrun = true; + std::this_thread::sleep_for(std::chrono::milliseconds(60)); + ftl::timer::stop(false); + return true; + }); + + REQUIRE( (rc.id() >= 0) ); + + ftl::timer::start(true); + REQUIRE( didrun == true ); + } + + SECTION( "Return remove idle job" ) { + bool didrun = false; + + ftl::timer::reset(); + + auto rc = ftl::timer::add(ftl::timer::kTimerIdle10, [&didrun](int64_t ts) { + didrun = true; + ftl::timer::stop(false); + return false; + }); + + REQUIRE( (rc.id() >= 0) ); + + ftl::timer::start(true); + REQUIRE( didrun == true ); + REQUIRE( ftl::timer::count(ftl::timer::kTimerIdle10) == 0 ); + } +} + +TEST_CASE( "Timer::add() Main job" ) { + SECTION( "Quick main job" ) { + bool didrun = false; + + ftl::timer::reset(); + + auto rc = ftl::timer::add(ftl::timer::kTimerMain, [&didrun](int64_t ts) { + didrun = true; + ftl::timer::stop(false); + return true; + }); + + REQUIRE( (rc.id() >= 0) ); + + ftl::timer::start(true); + REQUIRE( didrun == true ); + } + + SECTION( "Slow main job" ) { + bool didrun = false; + + ftl::timer::reset(); + + auto rc = ftl::timer::add(ftl::timer::kTimerMain, [&didrun](int64_t ts) { + didrun = true; + std::this_thread::sleep_for(std::chrono::milliseconds(60)); + ftl::timer::stop(false); + return true; + }); + + REQUIRE( (rc.id() >= 0) ); + + ftl::timer::start(true); + REQUIRE( didrun == true ); + } + + SECTION( "Slow and fast main jobs" ) { + int job1 = 0; + int job2 = 0; + + ftl::timer::reset(); + + auto rc = ftl::timer::add(ftl::timer::kTimerMain, [&job1](int64_t ts) { + job1++; + std::this_thread::sleep_for(std::chrono::milliseconds(60)); + ftl::timer::stop(false); + return true; + }); + + REQUIRE( (rc.id() >= 0) ); + + ftl::timer::add(ftl::timer::kTimerMain, [&job2](int64_t ts) { + job2++; + return true; + }); + + ftl::timer::start(true); + REQUIRE( (job1 == 1 && job2 == 2) ); + } + + SECTION( "Return remove main job" ) { + bool didrun = false; + + ftl::timer::reset(); + + auto rc = ftl::timer::add(ftl::timer::kTimerMain, [&didrun](int64_t ts) { + didrun = true; + ftl::timer::stop(false); + return false; + }); + + REQUIRE( (rc.id() >= 0) ); + + ftl::timer::start(true); + REQUIRE( didrun == true ); + REQUIRE( ftl::timer::count(ftl::timer::kTimerMain) == 0 ); + } +} + +TEST_CASE( "TimerHandle::cancel()" ) { + SECTION( "Invalid id" ) { + bool didjob = false; + ftl::timer::reset(); + + ftl::timer::add(ftl::timer::kTimerMain, [&didjob](int64_t ts) { + didjob = true; + ftl::timer::stop(false); + return true; + }); + + // Fake Handle + ftl::timer::TimerHandle h(44); + h.cancel(); + ftl::timer::start(true); + REQUIRE( didjob ); + } + + SECTION( "Valid id" ) { + bool didjob = false; + ftl::timer::reset(); + + auto id = ftl::timer::add(ftl::timer::kTimerMain, [&didjob](int64_t ts) { + didjob = true; + ftl::timer::stop(false); + return true; + }); + + id.cancel(); + ftl::timer::start(false); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + ftl::timer::stop(); + REQUIRE( !didjob ); + } +} diff --git a/components/net/README.md b/components/net/README.md index 4c3deb62e21b8ab25902c478cb9616411a6097fd..97ceed68e57e0e9441c68e1dbd24e1a18fb300d5 100644 --- a/components/net/README.md +++ b/components/net/README.md @@ -1,13 +1,27 @@ # FTL Network Library -FTL Net provides stream, RPC and Peer-2-Peer functionality for the FTL system. -The idea is to allow an efficient mapping to operating system sockets to -minimise userspace copy operations, whilst still allowing for data packing for -smaller RPC calls. The P2P component implements different rpc search strategies -to allow calls to find one, all, many or specific results across the network. - -Multiple protocols are supported, and it is intended that NAT traversal will be -included. However, security, whether encryption or identification, is not -considered presently. - -There are two supported languages: C++ and Javascript. See each respective -language folder for more details. +FTL Net provides an easy to use C++17 network library based around a mix of +streams and RPC. It is now highly optimised to minimise memory copies and +locking, whilst fully taking advantage of all CPU cores for processing +messages received over the network. Each message received is dispatched into +a thread pool. The optimisation works on the basis of a relatively low number +of socket connections but with a high bandwidth and low latency requirement +in sending and receiving on those sockets. Further work would be needed to +be efficient with large or massive numbers of sockets. + +The protocol is based on top of [MsgPack](https://github.com/msgpack/msgpack-c) +which works in both C++ and JavaScript. To work with JavaScript the protocol +works over TCP and TCP+Websockets. The library is also cross platform, +supporting Windows and Linux. + +It is a template library, allowing simple RPC calls and bindings using the +latest in C++ features such as optionals, lambdas and futures. + +## Universe +A [Universe class](cpp/include/ftl/net/universe.hpp) represents a network group +and is the primary means of interaction for the user of the library. It supports +`bind`, `connect`, `call`, `send`, `disconnect`, `asyncCall` and more. + +## Peer +A [Peer object](cpp/include/ftl/net/peer.hpp) is a fairly internal object that +wraps a socket connection and deals with all actual sending and receiving over +the network. diff --git a/components/net/cpp/include/ftl/net/common.hpp b/components/net/cpp/include/ftl/net/common.hpp index 78325d09b717d232751c21040be591f259fe7cf8..1ff4c5bbd73f5888b7141a7be988e162d7e279a6 100644 --- a/components/net/cpp/include/ftl/net/common.hpp +++ b/components/net/cpp/include/ftl/net/common.hpp @@ -3,6 +3,7 @@ #ifndef WIN32 #include <unistd.h> +#include <sys/poll.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> diff --git a/components/net/cpp/include/ftl/net/peer.hpp b/components/net/cpp/include/ftl/net/peer.hpp index 438fc171425b1f1b2cab12317d05eed2f95d8372..2c3e1fd636edd16772b3bf44ecb0e15fefa3b16b 100644 --- a/components/net/cpp/include/ftl/net/peer.hpp +++ b/components/net/cpp/include/ftl/net/peer.hpp @@ -168,6 +168,9 @@ class Peer { */ template <typename... ARGS> int send(const std::string &name, ARGS... args); + + template <typename... ARGS> + int try_send(const std::string &name, ARGS... args); /** * Bind a function to an RPC call name. Note: if an overriding dispatcher @@ -240,7 +243,7 @@ class Peer { ftl::net::Universe *universe_; // Origin net universe // Receive buffers - bool is_waiting_; + volatile bool is_waiting_; msgpack::unpacker recv_buf_; RECURSIVE_MUTEX recv_mtx_; bool ws_read_header_; @@ -277,6 +280,32 @@ int Peer::send(const std::string &s, ARGS... args) { return rc; } +template <typename... ARGS> +int Peer::try_send(const std::string &s, ARGS... args) { +#ifdef WIN32 + WSAPOLLFD fds; + fds.fd = sock_; + fds.events = POLLOUT; + int rc = WSAPoll(&fds, 1, 0); +#else + pollfd fds; + fds.fd = sock_; + fds.events = POLLOUT; + int rc = poll(&fds, 1, 0); +#endif + if (rc == SOCKET_ERROR) return -1; + if (rc == 0) return 0; + + UNIQUE_LOCK(send_mtx_, lk); + // Leave a blank entry for websocket header + if (scheme_ == ftl::URI::SCHEME_WS) send_buf_.append_ref(nullptr,0); + auto args_obj = std::make_tuple(args...); + auto call_obj = std::make_tuple(0,s,args_obj); + msgpack::pack(send_buf_, call_obj); + rc = _send(); + return (rc < 0) ? -1 : 1; +} + template <typename F> void Peer::bind(const std::string &name, F func) { disp_->bind(name, func, @@ -301,11 +330,11 @@ R Peer::call(const std::string &name, ARGS... args) { }, std::forward<ARGS>(args)...); // While waiting, do some other thread jobs... - std::function<void(int)> j; + /*std::function<void(int)> j; while (!hasreturned && (bool)(j=ftl::pool.pop())) { LOG(INFO) << "Doing job whilst waiting..."; j(-1); - } + }*/ { // Block thread until async callback notifies us std::unique_lock<std::mutex> lk(m); diff --git a/components/net/cpp/include/ftl/net/universe.hpp b/components/net/cpp/include/ftl/net/universe.hpp index b45163006d9755c3c3a713a23b1b9ccdf704cb8c..b4419b1f7fc713259b0d9f1a14f00ff12332dd6b 100644 --- a/components/net/cpp/include/ftl/net/universe.hpp +++ b/components/net/cpp/include/ftl/net/universe.hpp @@ -141,6 +141,9 @@ class Universe : public ftl::Configurable { template <typename... ARGS> bool send(const UUID &pid, const std::string &name, ARGS... args); + template <typename... ARGS> + int try_send(const UUID &pid, const std::string &name, ARGS... args); + template <typename R, typename... ARGS> std::optional<R> findOne(const std::string &name, ARGS... args); @@ -185,6 +188,9 @@ class Universe : public ftl::Configurable { ftl::net::callback_t onError(const std::function<void(ftl::net::Peer*, const ftl::net::Error &)>&); void removeCallback(ftl::net::callback_t cbid); + + size_t getSendBufferSize() const { return send_size_; } + size_t getRecvBufferSize() const { return recv_size_; } private: void _run(); @@ -203,7 +209,7 @@ class Universe : public ftl::Configurable { private: bool active_; ftl::UUID this_peer; - SHARED_MUTEX net_mutex_; + mutable SHARED_MUTEX net_mutex_; RECURSIVE_MUTEX handler_mutex_; fd_set sfderror_; fd_set sfdread_; @@ -214,11 +220,18 @@ class Universe : public ftl::Configurable { std::map<ftl::UUID, ftl::net::Peer*> peer_ids_; ftl::UUID id_; ftl::net::Dispatcher disp_; - std::thread thread_; std::list<ReconnectInfo> reconnects_; size_t phase_; std::list<ftl::net::Peer*> garbage_; + size_t send_size_; + size_t recv_size_; + double periodic_time_; + int reconnect_attempts_; + + // NOTE: Must always be last member + std::thread thread_; + struct ConnHandler { callback_t id; std::function<void(ftl::net::Peer*)> h; @@ -385,6 +398,17 @@ bool Universe::send(const ftl::UUID &pid, const std::string &name, ARGS... args) } +template <typename... ARGS> +int Universe::try_send(const ftl::UUID &pid, const std::string &name, ARGS... args) { + Peer *p = getPeer(pid); + if (p == nullptr) { + DLOG(WARNING) << "Attempting to call an unknown peer : " << pid.to_string(); + return false; + } + + return (p->isConnected()) ? p->try_send(name, args...) : -1; +} + /*template <typename... ARGS> void Universe::publish(const std::string &res, ARGS... args) { ftl::URI uri(res); diff --git a/components/net/cpp/src/dispatcher.cpp b/components/net/cpp/src/dispatcher.cpp index 34b01238accbe92e5b32c74171b429506a201552..3231b8ddc4604c7929a04c5c33a36385e1bd94ea 100644 --- a/components/net/cpp/src/dispatcher.cpp +++ b/components/net/cpp/src/dispatcher.cpp @@ -65,15 +65,15 @@ void ftl::net::Dispatcher::dispatch_call(Peer &s, const msgpack::object &msg) { // assert(type == 0); if (type == 1) { - DLOG(INFO) << "RPC return for " << id; + //DLOG(INFO) << "RPC return for " << id; s._dispatchResponse(id, args); } else if (type == 0) { - DLOG(INFO) << "RPC " << name << "() <- " << s.getURI(); + //DLOG(INFO) << "RPC " << name << "() <- " << s.getURI(); auto func = _locateHandler(name); if (func) { - DLOG(INFO) << "Found binding for " << name; + //DLOG(INFO) << "Found binding for " << name; try { auto result = (*func)(args); //->get(); s._sendResponse(id, result->get()); @@ -149,7 +149,7 @@ void ftl::net::Dispatcher::dispatch_notification(Peer &s, msgpack::object const void ftl::net::Dispatcher::enforce_arg_count(std::string const &func, std::size_t found, std::size_t expected) { if (found != expected) { - LOG(FATAL) << "RPC argument missmatch - " << found << " != " << expected; + LOG(FATAL) << "RPC argument missmatch for '" << func << "' - " << found << " != " << expected; throw -1; } } diff --git a/components/net/cpp/src/peer.cpp b/components/net/cpp/src/peer.cpp index 8adbeae8c42ed7b3bbd3215a561ae45aacffebf3..0335ca67f3a284294b4973c83aea62100d56a5c2 100644 --- a/components/net/cpp/src/peer.cpp +++ b/components/net/cpp/src/peer.cpp @@ -18,6 +18,11 @@ #pragma comment(lib, "Rpcrt4.lib") #endif +#ifndef WIN32 +#include <sys/ioctl.h> +#include <linux/sockios.h> +#endif + #include <ftl/uri.hpp> #include <ftl/net/peer.hpp> #include <ftl/net/ws_internal.hpp> @@ -43,8 +48,6 @@ using ftl::net::Universe; using ftl::net::callback_t; using std::vector; -#define TCP_BUFFER_SIZE (1024*1024*10) - /*static std::string hexStr(const std::string &s) { const char *data = s.data(); @@ -64,7 +67,7 @@ ftl::UUID ftl::net::this_peer; //static ctpl::thread_pool pool(5); // TODO:(nick) Move to tcp_internal.cpp -static SOCKET tcpConnect(URI &uri) { +static SOCKET tcpConnect(URI &uri, int ssize, int rsize) { int rc; //sockaddr_in destAddr; @@ -87,10 +90,11 @@ static SOCKET tcpConnect(URI &uri) { int flags =1; if (setsockopt(csocket, IPPROTO_TCP, TCP_NODELAY, (const char *)&flags, sizeof(flags))) { LOG(ERROR) << "ERROR: setsocketopt(), TCP_NODELAY"; }; - int a = TCP_BUFFER_SIZE; + int a = rsize; if (setsockopt(csocket, SOL_SOCKET, SO_RCVBUF, (const char *)&a, sizeof(int)) == -1) { fprintf(stderr, "Error setting socket opts: %s\n", strerror(errno)); } + a = ssize; if (setsockopt(csocket, SOL_SOCKET, SO_SNDBUF, (const char *)&a, sizeof(int)) == -1) { fprintf(stderr, "Error setting socket opts: %s\n", strerror(errno)); } @@ -178,10 +182,11 @@ Peer::Peer(SOCKET s, Universe *u, Dispatcher *d) : sock_(s), can_reconnect_(fals int flags =1; if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (const char *)&flags, sizeof(flags))) { LOG(ERROR) << "ERROR: setsocketopt(), TCP_NODELAY"; }; - int a = TCP_BUFFER_SIZE; + int a = u->getRecvBufferSize(); if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, (const char *)&a, sizeof(int)) == -1) { fprintf(stderr, "Error setting socket opts: %s\n", strerror(errno)); } + a = u->getSendBufferSize(); if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, (const char *)&a, sizeof(int)) == -1) { fprintf(stderr, "Error setting socket opts: %s\n", strerror(errno)); } @@ -234,12 +239,12 @@ Peer::Peer(const char *pUri, Universe *u, Dispatcher *d) : can_reconnect_(true), scheme_ = uri.getProtocol(); if (uri.getProtocol() == URI::SCHEME_TCP) { - sock_ = tcpConnect(uri); + sock_ = tcpConnect(uri, u->getSendBufferSize(), u->getRecvBufferSize()); if (sock_ != INVALID_SOCKET) status_ = kConnecting; else status_ = kReconnecting; } else if (uri.getProtocol() == URI::SCHEME_WS) { LOG(INFO) << "Websocket connect " << uri.getPath(); - sock_ = tcpConnect(uri); + sock_ = tcpConnect(uri, u->getSendBufferSize(), u->getRecvBufferSize()); if (sock_ != INVALID_SOCKET) { if (!ws_connect(sock_, uri)) { LOG(ERROR) << "Websocket connection failed"; @@ -302,7 +307,7 @@ bool Peer::reconnect() { LOG(INFO) << "Reconnecting to " << uri_ << " ..."; if (scheme_ == URI::SCHEME_TCP) { - sock_ = tcpConnect(uri); + sock_ = tcpConnect(uri, universe_->getSendBufferSize(), universe_->getRecvBufferSize()); if (sock_ != INVALID_SOCKET) { status_ = kConnecting; is_waiting_ = true; @@ -311,7 +316,7 @@ bool Peer::reconnect() { return false; } } else if (scheme_ == URI::SCHEME_WS) { - sock_ = tcpConnect(uri); + sock_ = tcpConnect(uri, universe_->getSendBufferSize(), universe_->getRecvBufferSize()); if (sock_ != INVALID_SOCKET) { if (!ws_connect(sock_, uri)) { return false; @@ -412,46 +417,65 @@ void Peer::error(int e) { void Peer::data() { { + //auto start = std::chrono::high_resolution_clock::now(); + //int64_t startts = std::chrono::time_point_cast<std::chrono::milliseconds>(start).time_since_epoch().count(); UNIQUE_LOCK(recv_mtx_,lk); + //LOG(INFO) << "Pool size: " << ftl::pool.q_size(); + int rc=0; - int c=0; - //do { - recv_buf_.reserve_buffer(kMaxMessage); + recv_buf_.reserve_buffer(kMaxMessage); - if (recv_buf_.buffer_capacity() < (kMaxMessage / 10)) { - LOG(WARNING) << "Net buffer at capacity"; - return; - } + if (recv_buf_.buffer_capacity() < (kMaxMessage / 10)) { + LOG(WARNING) << "Net buffer at capacity"; + return; + } - int cap = recv_buf_.buffer_capacity(); - rc = ftl::net::internal::recv(sock_, recv_buf_.buffer(), cap, 0); - //if (c > 0 && rc > 0) LOG(INFO) << "RECV: " << rc; + int cap = recv_buf_.buffer_capacity(); + auto buf = recv_buf_.buffer(); + lk.unlock(); - if (rc >= cap-1) { - LOG(WARNING) << "More than buffers worth of data received"; - } - if (cap < (kMaxMessage / 10)) LOG(WARNING) << "NO BUFFER"; - - if (rc == 0) { - close(); - return; - } else if (rc < 0 && c == 0) { - socketError(); - return; - } + /*#ifndef WIN32 + int n; + unsigned int m = sizeof(n); + getsockopt(sock_,SOL_SOCKET,SO_RCVBUF,(void *)&n, &m); - //if (rc == -1) break; - ++c; - - recv_buf_.buffer_consumed(rc); - //} while (rc > 0); - } + int pending; + ioctl(sock_, SIOCINQ, &pending); + if (pending > 100000) LOG(INFO) << "Buffer usage: " << float(pending) / float(n); + #endif*/ + rc = ftl::net::internal::recv(sock_, buf, cap, 0); - ftl::pool.push([this](int id) { - _data(); - }); + if (rc >= cap-1) { + LOG(WARNING) << "More than buffers worth of data received"; + } + if (cap < (kMaxMessage / 10)) LOG(WARNING) << "NO BUFFER"; + + if (rc == 0) { + close(); + return; + } else if (rc < 0) { + socketError(); + return; + } + + lk.lock(); + recv_buf_.buffer_consumed(rc); + + //auto end = std::chrono::high_resolution_clock::now(); + //int64_t endts = std::chrono::time_point_cast<std::chrono::milliseconds>(end).time_since_epoch().count(); + //if (endts - startts > 50) LOG(ERROR) << "Excessive delay"; + + if (is_waiting_) { + is_waiting_ = false; + lk.unlock(); + + ftl::pool.push([this](int id) { + _data(); + }); + } + } } bool Peer::_data() { @@ -460,26 +484,35 @@ bool Peer::_data() { UNIQUE_LOCK(recv_mtx_,lk); if (scheme_ == ftl::URI::SCHEME_WS && !ws_read_header_) { + //LOG(INFO) << "Reading WS Header"; wsheader_type ws; + ws.header_size = 0; if (ws_parse(recv_buf_, ws) < 0) { + //LOG(ERROR) << "Bad WS header " << ws.header_size; + is_waiting_ = true; return false; } ws_read_header_ = true; } - if (!recv_buf_.next(msg)) return false; - msgpack::object obj = msg.get(); + if (!recv_buf_.next(msg)) { + is_waiting_ = true; + return false; + } + ws_read_header_ = false; + lk.unlock(); + msgpack::object obj = msg.get(); ftl::pool.push([this](int id) { _data(); }); - if (status_ != kConnected) { + if (status_ == kConnecting) { // If not connected, must lock to make sure no other thread performs this step UNIQUE_LOCK(recv_mtx_,lk); // Verify still not connected after lock - if (status_ != kConnected) { + if (status_ == kConnecting) { // First message must be a handshake try { tuple<uint32_t, std::string, msgpack::object> hs; @@ -515,7 +548,7 @@ void Peer::_dispatchResponse(uint32_t id, msgpack::object &res) { // TODO: Handle error reporting... UNIQUE_LOCK(cb_mtx_,lk); if (callbacks_.count(id) > 0) { - DLOG(1) << "Received return RPC value"; + //DLOG(1) << "Received return RPC value"; // Allow for unlock before callback auto cb = std::move(callbacks_[id]); diff --git a/components/net/cpp/src/universe.cpp b/components/net/cpp/src/universe.cpp index 8c50709e2d6253f73849f5560c72eee9a6ff5b83..29cf1254f9a4926190d1753eb4ecc0836dfcbb99 100644 --- a/components/net/cpp/src/universe.cpp +++ b/components/net/cpp/src/universe.cpp @@ -1,4 +1,5 @@ #include <ftl/net/universe.hpp> +#include <ftl/timer.hpp> #include <chrono> #ifdef WIN32 @@ -22,16 +23,55 @@ using std::optional; using ftl::config::json_t; using ftl::net::callback_t; +#define TCP_SEND_BUFFER_SIZE (512*1024) +#define TCP_RECEIVE_BUFFER_SIZE (1024*1024*1) + callback_t ftl::net::Universe::cbid__ = 0; -Universe::Universe() : Configurable(), active_(true), this_peer(ftl::net::this_peer), thread_(Universe::__start, this), phase_(0) { +Universe::Universe() : + Configurable(), + active_(true), + this_peer(ftl::net::this_peer), + phase_(0), + send_size_(TCP_SEND_BUFFER_SIZE), + recv_size_(TCP_RECEIVE_BUFFER_SIZE), + periodic_time_(1.0), + reconnect_attempts_(50), + thread_(Universe::__start, this) { _installBindings(); + + LOG(WARNING) << "Deprecated Universe constructor"; } Universe::Universe(nlohmann::json &config) : - Configurable(config), active_(true), this_peer(ftl::net::this_peer), thread_(Universe::__start, this), phase_(0) { + Configurable(config), + active_(true), + this_peer(ftl::net::this_peer), + phase_(0), + send_size_(value("tcp_send_buffer",TCP_SEND_BUFFER_SIZE)), + recv_size_(value("tcp_recv_buffer",TCP_RECEIVE_BUFFER_SIZE)), + periodic_time_(value("periodics", 1.0)), + reconnect_attempts_(value("reconnect_attempts",50)), + thread_(Universe::__start, this) { _installBindings(); + + // Add an idle timer job to garbage collect peer objects + // Note: Important to be a timer job to ensure no other timer jobs are + // using the object. + ftl::timer::add(ftl::timer::kTimerIdle10, [this](int64_t ts) { + if (garbage_.size() > 0) { + UNIQUE_LOCK(net_mutex_,lk); + if (ftl::pool.n_idle() == ftl::pool.size()) { + if (garbage_.size() > 0) LOG(INFO) << "Garbage collection"; + while (garbage_.size() > 0) { + delete garbage_.front(); + garbage_.pop_front(); + } + } + } + return true; + }); } Universe::~Universe() { @@ -39,6 +79,11 @@ Universe::~Universe() { } void Universe::start() { + /*cpu_set_t cpus; + CPU_ZERO(&cpus); + CPU_SET(1, &cpus); + pthread_setaffinity_np(thread_.native_handle(), sizeof(cpus), &cpus);*/ + auto l = get<json_t>("listen"); if (l && (*l).is_array()) { @@ -138,9 +183,7 @@ int Universe::_setDescriptors() { n = s->_socket(); } - //if (s->isWaiting()) { - FD_SET(s->_socket(), &sfdread_); - //} + FD_SET(s->_socket(), &sfdread_); FD_SET(s->_socket(), &sfderror_); } } @@ -154,30 +197,11 @@ void Universe::_installBindings(Peer *p) { } void Universe::_installBindings() { - /*bind("__subscribe__", [this](const UUID &id, const string &uri) -> bool { - LOG(INFO) << "Subscription to " << uri << " by " << id.to_string(); - unique_lock<shared_mutex> lk(net_mutex_); - subscribers_[ftl::URI(uri).to_string()].push_back(id); - return true; - }); - - bind("__owner__", [this](const std::string &res) -> optional<UUID> { - if (owned_.count(res) > 0) return this_peer; - else return {}; - });*/ + } // Note: should be called inside a net lock void Universe::_cleanupPeers() { - - if (ftl::pool.n_idle() == ftl::pool.size()) { - if (garbage_.size() > 0) LOG(INFO) << "Garbage collection"; - while (garbage_.size() > 0) { - delete garbage_.front(); - garbage_.pop_front(); - } - } - auto i = peers_.begin(); while (i != peers_.end()) { if (!(*i)->isValid()) { @@ -191,7 +215,7 @@ void Universe::_cleanupPeers() { i = peers_.erase(i); if (p->status() == ftl::net::Peer::kReconnecting) { - reconnects_.push_back({50, 1.0f, p}); + reconnects_.push_back({reconnect_attempts_, 1.0f, p}); } else { //delete p; garbage_.push_back(p); @@ -203,6 +227,7 @@ void Universe::_cleanupPeers() { } Peer *Universe::getPeer(const UUID &id) const { + SHARED_LOCK(net_mutex_,lk); auto ix = peer_ids_.find(id); if (ix == peer_ids_.end()) return nullptr; else return ix->second; @@ -255,7 +280,7 @@ void Universe::_run() { // Do periodics auto now = std::chrono::high_resolution_clock::now(); std::chrono::duration<double> elapsed = now - start; - if (elapsed.count() >= 1.0) { + if (elapsed.count() >= periodic_time_) { start = now; _periodic(); } @@ -287,8 +312,8 @@ void Universe::_run() { continue; } - // CHECK Could this mutex be the problem!? { + // TODO:(Nick) Shared lock unless connection is made UNIQUE_LOCK(net_mutex_,lk); //If connection request is waiting @@ -304,7 +329,7 @@ void Universe::_run() { if (csock != INVALID_SOCKET) { auto p = new Peer(csock, this, &disp_); peers_.push_back(p); - _installBindings(p); + //_installBindings(p); } } } diff --git a/components/net/cpp/test/peer_unit.cpp b/components/net/cpp/test/peer_unit.cpp index 09eb4bef9e70171b84e57f1e954e833aa9768566..c4ace71797063eef78bafee6e3784f0f5e4fa124 100644 --- a/components/net/cpp/test/peer_unit.cpp +++ b/components/net/cpp/test/peer_unit.cpp @@ -50,6 +50,9 @@ class Universe { callback_t onConnect(const std::function<void(Peer*)> &f) { return 0; } callback_t onDisconnect(const std::function<void(Peer*)> &f) { return 0; } + + size_t getSendBufferSize() const { return 10*1024; } + size_t getRecvBufferSize() const { return 10*1024; } }; } } diff --git a/components/renderers/cpp/CMakeLists.txt b/components/renderers/cpp/CMakeLists.txt index 33f910ca0342096bb551b430374776a86de89b2f..b575721587262e2e468d6cb48cf8c44c6771e6fc 100644 --- a/components/renderers/cpp/CMakeLists.txt +++ b/components/renderers/cpp/CMakeLists.txt @@ -1,6 +1,7 @@ add_library(ftlrender - src/display.cpp - src/rgbd_display.cpp + src/splat_render.cpp + src/splatter.cu + src/points.cu ) # These cause errors in CI build and are being removed from PCL in newer versions @@ -11,6 +12,6 @@ target_include_directories(ftlrender PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include> PRIVATE src) -target_link_libraries(ftlrender ftlrgbd ftlcommon ftlnet Eigen3::Eigen Threads::Threads glog::glog ${OpenCV_LIBS} ${PCL_LIBRARIES}) +target_link_libraries(ftlrender ftlrgbd ftlcommon Eigen3::Eigen Threads::Threads ${OpenCV_LIBS}) #ADD_SUBDIRECTORY(test) diff --git a/components/renderers/cpp/include/ftl/cuda/intersections.hpp b/components/renderers/cpp/include/ftl/cuda/intersections.hpp new file mode 100644 index 0000000000000000000000000000000000000000..9cfdbc2544d9c1bd32c9f5e12a0a161f45c50d54 --- /dev/null +++ b/components/renderers/cpp/include/ftl/cuda/intersections.hpp @@ -0,0 +1,88 @@ +#ifndef _FTL_CUDA_INTERSECTIONS_HPP_ +#define _FTL_CUDA_INTERSECTIONS_HPP_ + +#ifndef PINF +#define PINF __int_as_float(0x7f800000) +#endif + +namespace ftl { +namespace cuda { + +__device__ inline bool intersectPlane(const float3 &n, const float3 &p0, const float3 &l0, const float3 &l, float &t) { + // assuming vectors are all normalized + float denom = dot(n, l); + if (denom > 1e-6) { + t = dot(p0 - l0, n) / denom; + return (t >= 0); + } + + return false; +} + +__device__ inline bool intersectPlane(const float3 &n, const float3 &p0, const float3 &l, float &t) { + // assuming vectors are all normalized + float denom = dot(n, l); + if (denom > 1e-6) { + t = dot(p0, n) / denom; + return (t >= 0); + } + return false; +} + +__device__ inline bool intersectDisk(const float3 &n, const float3 &p0, float radius, const float3 &l0, const float3 &l) { + float t = 0; + if (intersectPlane(n, p0, l0, l, t)) { + float3 p = l0 + l * t; + float3 v = p - p0; + float d2 = dot(v, v); + return (sqrt(d2) <= radius); + // or you can use the following optimisation (and precompute radius^2) + // return d2 <= radius2; // where radius2 = radius * radius + } + return false; +} + +/** + * Get the radius of a ray intersection with a disk. + * @param n Normalised normal of disk. + * @param p0 Centre of disk in camera space + * @param l Normalised ray direction in camera space + * @return Radius from centre of disk where intersection occurred. + */ +__device__ inline float intersectDistance(const float3 &n, const float3 &p0, const float3 &l0, const float3 &l) { + float t = 0; + if (intersectPlane(n, p0, l0, l, t)) { + const float3 p = l0 + l * t; + const float3 v = p - p0; + const float d2 = dot(v, v); + return sqrt(d2); + // or you can use the following optimisation (and precompute radius^2) + // return d2 <= radius2; // where radius2 = radius * radius + } + return PINF; +} + +/** + * Get the radius of a ray intersection with a disk. + * @param n Normalised normal of disk. + * @param p0 Centre of disk in camera space + * @param l Normalised ray direction in camera space + * @return Radius from centre of disk where intersection occurred. + */ +__device__ inline float intersectDistance(const float3 &n, const float3 &p0, const float3 &l) { + float t = 0; + if (intersectPlane(n, p0, l, t)) { + const float3 p = l * t; + const float3 v = p - p0; + const float d2 = dot(v, v); + return sqrt(d2); + // or you can use the following optimisation (and precompute radius^2) + // return d2 <= radius2; // where radius2 = radius * radius + } + return PINF; +} + +} +} + +#endif // _FTL_CUDA_INTERSECTIONS_HPP_ diff --git a/components/renderers/cpp/include/ftl/cuda/points.hpp b/components/renderers/cpp/include/ftl/cuda/points.hpp new file mode 100644 index 0000000000000000000000000000000000000000..deffe32777789e2b58a96aef2106975ad37e0cdd --- /dev/null +++ b/components/renderers/cpp/include/ftl/cuda/points.hpp @@ -0,0 +1,16 @@ +#ifndef _FTL_CUDA_POINTS_HPP_ +#define _FTL_CUDA_POINTS_HPP_ + +#include <ftl/cuda_common.hpp> +#include <ftl/rgbd/camera.hpp> +#include <ftl/cuda_matrix_util.hpp> + +namespace ftl { +namespace cuda { + +void point_cloud(ftl::cuda::TextureObject<float4> &output, ftl::cuda::TextureObject<float> &depth, const ftl::rgbd::Camera ¶ms, const float4x4 &pose, cudaStream_t stream); + +} +} + +#endif // _FTL_CUDA_POINTS_HPP_ diff --git a/components/renderers/cpp/include/ftl/cuda/weighting.hpp b/components/renderers/cpp/include/ftl/cuda/weighting.hpp new file mode 100644 index 0000000000000000000000000000000000000000..9498d0508605087306db2658b2a1ae1943cde536 --- /dev/null +++ b/components/renderers/cpp/include/ftl/cuda/weighting.hpp @@ -0,0 +1,23 @@ +#ifndef _FTL_CUDA_WEIGHTING_HPP_ +#define _FTL_CUDA_WEIGHTING_HPP_ + +namespace ftl { +namespace cuda { + +/* + * Guennebaud, G.; Gross, M. Algebraic point set surfaces. ACMTransactions on Graphics Vol. 26, No. 3, Article No. 23, 2007. + * Used in: FusionMLS: Highly dynamic 3D reconstruction with consumer-grade RGB-D cameras + * r = distance between points + * h = smoothing parameter in meters (default 4cm) + */ +__device__ inline float spatialWeighting(float r, float h) { + if (r >= h) return 0.0f; + float rh = r / h; + rh = 1.0f - rh*rh; + return rh*rh*rh*rh; +} + +} +} + +#endif // _FTL_CUDA_WEIGHTING_HPP_ diff --git a/components/renderers/cpp/include/ftl/display.hpp b/components/renderers/cpp/include/ftl/display.hpp deleted file mode 100644 index 05ae0bf11e5ebc3a5b8d54339bc85166effbb4a9..0000000000000000000000000000000000000000 --- a/components/renderers/cpp/include/ftl/display.hpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2019 Nicolas Pope - */ - -#ifndef _FTL_DISPLAY_HPP_ -#define _FTL_DISPLAY_HPP_ - -#include <ftl/config.h> -#include <ftl/configurable.hpp> -#include "../../../rgbd-sources/include/ftl/rgbd/camera.hpp" - -#include <nlohmann/json.hpp> -#include <opencv2/opencv.hpp> -#include "opencv2/highgui.hpp" - -#if defined HAVE_PCL -#include <pcl/common/common_headers.h> -#include <pcl/visualization/pcl_visualizer.h> -#endif // HAVE_PCL - -namespace ftl { - -/** - * Multiple local display options for disparity or point clouds. - */ -class Display : public ftl::Configurable { - private: - std::string name_; - public: - enum style_t { - STYLE_NORMAL, STYLE_DISPARITY, STYLE_DEPTH - }; - - public: - explicit Display(nlohmann::json &config, std::string name); - ~Display(); - - bool render(const cv::Mat &rgb, const cv::Mat &depth, const ftl::rgbd::Camera &p); - -#if defined HAVE_PCL - bool render(pcl::PointCloud<pcl::PointXYZRGB>::ConstPtr); -#endif // HAVE_PCL - bool render(const cv::Mat &img, style_t s=STYLE_NORMAL); - - bool active() const; - - bool hasDisplays(); - - void wait(int ms); - - void onKey(const std::function<void(int)> &h) { key_handlers_.push_back(h); } - - private: -#if defined HAVE_VIZ - cv::viz::Viz3d *window_; -#endif // HAVE_VIZ - -#if defined HAVE_PCL - pcl::visualization::PCLVisualizer::Ptr pclviz_; -#endif // HAVE_PCL - - bool active_; - std::vector<std::function<void(int)>> key_handlers_; -}; -}; - -#endif // _FTL_DISPLAY_HPP_ - diff --git a/components/renderers/cpp/include/ftl/render/renderer.hpp b/components/renderers/cpp/include/ftl/render/renderer.hpp new file mode 100644 index 0000000000000000000000000000000000000000..1871b9f9f2a8e1fda0766e1c2e74d2169f47f3fa --- /dev/null +++ b/components/renderers/cpp/include/ftl/render/renderer.hpp @@ -0,0 +1,35 @@ +#ifndef _FTL_RENDER_RENDERER_HPP_ +#define _FTL_RENDER_RENDERER_HPP_ + +#include <ftl/configurable.hpp> +#include <ftl/rgbd/virtual.hpp> +#include <ftl/cuda_common.hpp> + +namespace ftl { +namespace render { + +/** + * Abstract class for all renderers. A renderer takes some 3D scene and + * generates a virtual camera perspective of that scene. The scene might be + * based upon a point cloud, or an entirely virtual mesh or procedural scene. + * It is intended that multiple scenes can be rendered into a single virtual + * view using a compositing renderer, such a renderer accepting any kind of + * renderer for compositing and hence relying on this base class. + */ +class Renderer : public ftl::Configurable { + public: + explicit Renderer(nlohmann::json &config) : Configurable(config) {}; + virtual ~Renderer() {}; + + /** + * Generate a single virtual camera frame. The frame takes its pose from + * the virtual camera object passed, and writes the result into the + * virtual camera. + */ + virtual bool render(ftl::rgbd::VirtualSource *, ftl::rgbd::Frame &, cudaStream_t)=0; +}; + +} +} + +#endif // _FTL_RENDER_RENDERER_HPP_ diff --git a/components/renderers/cpp/include/ftl/render/splat_params.hpp b/components/renderers/cpp/include/ftl/render/splat_params.hpp new file mode 100644 index 0000000000000000000000000000000000000000..4f9c8882b161d7774388e8d9fff7337cb1d6e685 --- /dev/null +++ b/components/renderers/cpp/include/ftl/render/splat_params.hpp @@ -0,0 +1,30 @@ +#ifndef _FTL_RENDER_SPLAT_PARAMS_HPP_ +#define _FTL_RENDER_SPLAT_PARAMS_HPP_ + +#include <ftl/cuda_util.hpp> +#include <ftl/cuda_matrix_util.hpp> +#include <ftl/rgbd/camera.hpp> + +namespace ftl { +namespace render { + +static const uint kShowBlockBorders = 0x00000001; // Deprecated: from voxels system +static const uint kNoSplatting = 0x00000002; +static const uint kNoUpsampling = 0x00000004; +static const uint kNoTexturing = 0x00000008; + +struct __align__(16) SplatParams { + float4x4 m_viewMatrix; + float4x4 m_viewMatrixInverse; + + uint m_flags; + //float voxelSize; + float depthThreshold; + + ftl::rgbd::Camera camera; +}; + +} +} + +#endif diff --git a/applications/reconstruct/src/splat_render.hpp b/components/renderers/cpp/include/ftl/render/splat_render.hpp similarity index 57% rename from applications/reconstruct/src/splat_render.hpp rename to components/renderers/cpp/include/ftl/render/splat_render.hpp index 7737bb5d903d725a55b81d75812b99b52e7ace66..55522c483c25f58d843543ee8d5ba42aae9c32c8 100644 --- a/applications/reconstruct/src/splat_render.hpp +++ b/components/renderers/cpp/include/ftl/render/splat_render.hpp @@ -1,14 +1,9 @@ #ifndef _FTL_RECONSTRUCTION_SPLAT_HPP_ #define _FTL_RECONSTRUCTION_SPLAT_HPP_ -#include <ftl/configurable.hpp> -#include <ftl/rgbd/source.hpp> -#include <ftl/depth_camera.hpp> -#include <ftl/voxel_scene.hpp> -//#include <ftl/ray_cast_util.hpp> -#include <ftl/cuda_common.hpp> - -#include "splat_params.hpp" +#include <ftl/render/renderer.hpp> +#include <ftl/rgbd/frameset.hpp> +#include <ftl/render/splat_params.hpp> namespace ftl { namespace render { @@ -21,23 +16,28 @@ namespace render { * on a separate machine or at a later time, the advantage being to save local * processing resources and that the first pass result may compress better. */ -class Splatter { +class Splatter : public ftl::render::Renderer { public: - explicit Splatter(ftl::voxhash::SceneRep *scene); + explicit Splatter(nlohmann::json &config, ftl::rgbd::FrameSet *fs); ~Splatter(); - void render(ftl::rgbd::Source *src, cudaStream_t stream=0); + bool render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cudaStream_t stream=0) override; - void setOutputDevice(int); + //void setOutputDevice(int); private: int device_; - ftl::cuda::TextureObject<int> depth1_; + /*ftl::cuda::TextureObject<int> depth1_; + ftl::cuda::TextureObject<int> depth3_; ftl::cuda::TextureObject<uchar4> colour1_; + ftl::cuda::TextureObject<float4> colour_tmp_; ftl::cuda::TextureObject<float> depth2_; ftl::cuda::TextureObject<uchar4> colour2_; - SplatParams params_; - ftl::voxhash::SceneRep *scene_; + ftl::cuda::TextureObject<float4> normal1_;*/ + //SplatParams params_; + + ftl::rgbd::Frame temp_; + ftl::rgbd::FrameSet *scene_; }; } diff --git a/components/renderers/cpp/include/ftl/rgbd_display.hpp b/components/renderers/cpp/include/ftl/rgbd_display.hpp deleted file mode 100644 index 9c1be76fb5f38a584d3032d50b6ef046f0d0b605..0000000000000000000000000000000000000000 --- a/components/renderers/cpp/include/ftl/rgbd_display.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef _FTL_RGBD_DISPLAY_HPP_ -#define _FTL_RGBD_DISPLAY_HPP_ - -#include <nlohmann/json.hpp> -#include <ftl/rgbd/source.hpp> - -using MouseAction = std::function<void(int, int, int, int)>; - -namespace ftl { -namespace rgbd { - -class Display : public ftl::Configurable { - public: - explicit Display(nlohmann::json &); - Display(nlohmann::json &, Source *); - ~Display(); - - void setSource(Source *src) { source_ = src; } - void update(); - - bool active() const { return active_; } - - void onKey(const std::function<void(int)> &h) { key_handlers_.push_back(h); } - - void wait(int ms); - - private: - Source *source_; - std::string name_; - std::vector<std::function<void(int)>> key_handlers_; - Eigen::Vector3d eye_; - Eigen::Vector3d centre_; - Eigen::Vector3d up_; - Eigen::Vector3d lookPoint_; - float lerpSpeed_; - bool active_; - MouseAction mouseaction_; - - static int viewcount__; - - void init(); -}; - -} -} - -#endif // _FTL_RGBD_DISPLAY_HPP_ diff --git a/applications/reconstruct/include/ftl/matrix_conversion.hpp b/components/renderers/cpp/include/ftl/utility/matrix_conversion.hpp similarity index 99% rename from applications/reconstruct/include/ftl/matrix_conversion.hpp rename to components/renderers/cpp/include/ftl/utility/matrix_conversion.hpp index ac0bff14da8ad6be553943db2c04f6b9ef22844f..2888e928ebbe13c54585837882e81ad9c32cdad9 100644 --- a/applications/reconstruct/include/ftl/matrix_conversion.hpp +++ b/components/renderers/cpp/include/ftl/utility/matrix_conversion.hpp @@ -2,7 +2,7 @@ #pragma once -#include <eigen3/Eigen/Eigen> +#include <Eigen/Eigen> // #include "d3dx9math.h" #include <ftl/cuda_matrix_util.hpp> diff --git a/components/renderers/cpp/src/dibr.cu b/components/renderers/cpp/src/dibr.cu new file mode 100644 index 0000000000000000000000000000000000000000..66428ce3086e42b6a3e81c667ae126314d910c98 --- /dev/null +++ b/components/renderers/cpp/src/dibr.cu @@ -0,0 +1,781 @@ +#include "splat_render_cuda.hpp" +#include "depth_camera_cuda.hpp" +//#include <cuda_runtime.h> + +#include <ftl/cuda_matrix_util.hpp> + +#include "splat_params.hpp" +#include "mls_cuda.hpp" +#include <ftl/depth_camera.hpp> + +#define T_PER_BLOCK 8 +#define UPSAMPLE_FACTOR 1.8f +#define WARP_SIZE 32 +#define DEPTH_THRESHOLD 0.05f +#define UPSAMPLE_MAX 60 +#define MAX_ITERATIONS 32 // Note: Must be multiple of 32 +#define SPATIAL_SMOOTHING 0.005f + +using ftl::cuda::TextureObject; +using ftl::render::SplatParams; + +extern __constant__ ftl::voxhash::DepthCameraCUDA c_cameras[MAX_CAMERAS]; + +__global__ void clearColourKernel(TextureObject<uchar4> colour) { + const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x; + const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y; + + if (x < colour.width() && y < colour.height()) { + //depth(x,y) = 0x7f800000; //PINF; + colour(x,y) = make_uchar4(76,76,82,0); + } +} + +__device__ inline bool isStable(const float3 &previous, const float3 &estimate, const SplatParams ¶ms, float d) { + const float psize = 2.0f * d / params.camera.fx; + //printf("PSIZE %f\n", psize); + return fabs(previous.x - estimate.x) <= psize && + fabs(previous.y - estimate.y) <= psize && + fabs(previous.z - estimate.z) <= psize; +} + +// ===== PASS 1 : Gather & Upsample (Depth) ==================================== + +/* + * Pass 1: Directly render raw points from all cameras, but upsample the points + * if their spacing is within smoothing threshold but greater than their pixel + * size in the original image. + */ + __global__ void dibr_merge_upsample_kernel(TextureObject<int> depth, int cam, SplatParams params) { + const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[cam]; + + const int x = (blockIdx.x*blockDim.x + threadIdx.x) / WARP_SIZE; + const int y = blockIdx.y*blockDim.y + threadIdx.y; + + const float3 worldPos = make_float3(tex2D<float4>(camera.points, x, y)); + //const float3 normal = make_float3(tex2D<float4>(camera.normal, x, y)); + if (worldPos.x == MINF) return; + const float r = (camera.poseInverse * worldPos).z / camera.params.fx; + + // Get virtual camera ray for splat centre and backface cull if possible + //const float3 rayOrigin = params.m_viewMatrixInverse * make_float3(0.0f,0.0f,0.0f); + //const float3 rayDir = normalize(params.m_viewMatrixInverse * params.camera.kinectDepthToSkeleton(x,y,1.0f) - rayOrigin); + //if (dot(rayDir, normal) > 0.0f) return; + + // Find the virtual screen position of current point + const float3 camPos = params.m_viewMatrix * worldPos; + if (camPos.z < params.camera.m_sensorDepthWorldMin) return; + if (camPos.z > params.camera.m_sensorDepthWorldMax) return; + + // TODO: Don't upsample so much that only minimum depth makes it through + // Consider also using some SDF style approach to accumulate and smooth a + // depth value between points + const int upsample = min(UPSAMPLE_MAX-2, int(0.01 * params.camera.fx / camPos.z))+3; + const float interval = 1.0f / float(upsample / 2); + + + // TODO:(Nick) Check depth buffer and don't do anything if already hidden? + + // Each thread in warp takes an upsample point and updates corresponding depth buffer. + const int lane = threadIdx.x % WARP_SIZE; + for (int i=lane; i<upsample*upsample; i+=WARP_SIZE) { + const float u = (i % upsample) - (upsample / 2); + const float v = (i / upsample) - (upsample / 2); + + // Make an initial estimate of the points location + // Use centroid depth as estimate...? + const float3 point = params.m_viewMatrix * ftl::cuda::upsampled_point(camera.points, make_float2(float(x)+float(u)*interval, float(y)+float(v)*interval)); + const float d = point.z; + + const uint2 screenPos = params.camera.cameraToKinectScreen(point); + const unsigned int cx = screenPos.x;//+u; + const unsigned int cy = screenPos.y;//+v; + if (d > params.camera.m_sensorDepthWorldMin && d < params.camera.m_sensorDepthWorldMax && cx < depth.width() && cy < depth.height()) { + // Transform estimated point to virtual cam space and output z + atomicMin(&depth(cx,cy), d * 1000.0f); + } + } +} + +/* + * Pass 1: Directly render each camera into virtual view but with no upsampling + * for sparse points. + */ + __global__ void dibr_merge_kernel(TextureObject<int> depth, int cam, SplatParams params) { + const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[cam]; + + const int x = blockIdx.x*blockDim.x + threadIdx.x; + const int y = blockIdx.y*blockDim.y + threadIdx.y; + + const float3 worldPos = make_float3(tex2D<float4>(camera.points, x, y)); + if (worldPos.x == MINF) return; + + // Find the virtual screen position of current point + const float3 camPos = params.m_viewMatrix * worldPos; + if (camPos.z < params.camera.m_sensorDepthWorldMin) return; + if (camPos.z > params.camera.m_sensorDepthWorldMax) return; + + const float d = camPos.z; + + const uint2 screenPos = params.camera.cameraToKinectScreen(camPos); + const unsigned int cx = screenPos.x; + const unsigned int cy = screenPos.y; + if (d > params.camera.m_sensorDepthWorldMin && d < params.camera.m_sensorDepthWorldMax && cx < depth.width() && cy < depth.height()) { + // Transform estimated point to virtual cam space and output z + atomicMin(&depth(cx,cy), d * 1000.0f); + } +} + +// ===== PASS 2 : Splat Visible Surface ======================================== + +/* + * Pass 2: Determine depth buffer with enough accuracy for a visibility test in pass 2. + * These values are also used as the actual surface estimate during rendering so should + * at least be plane or sphere fitted if not MLS smoothed onto the actual surface. + */ +__global__ void OLD_dibr_visibility_kernel(TextureObject<int> depth, int cam, SplatParams params) { + const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[cam]; + + const int x = (blockIdx.x*blockDim.x + threadIdx.x) / WARP_SIZE; + const int y = blockIdx.y*blockDim.y + threadIdx.y; + + const float3 worldPos = make_float3(tex2D<float4>(camera.points, x, y)); + const float3 normal = make_float3(tex2D<float4>(camera.normal, x, y)); + if (worldPos.x == MINF) return; + const float r = (camera.poseInverse * worldPos).z / camera.params.fx; + + // Get virtual camera ray for splat centre and backface cull if possible + //const float3 rayOrigin = params.m_viewMatrixInverse * make_float3(0.0f,0.0f,0.0f); + //const float3 rayDir = normalize(params.m_viewMatrixInverse * params.camera.kinectDepthToSkeleton(x,y,1.0f) - rayOrigin); + //if (dot(rayDir, normal) > 0.0f) return; + + // Find the virtual screen position of current point + const float3 camPos = params.m_viewMatrix * worldPos; + if (camPos.z < params.camera.m_sensorDepthWorldMin) return; + if (camPos.z > params.camera.m_sensorDepthWorldMax) return; + const uint2 screenPos = params.camera.cameraToKinectScreen(camPos); + + const int upsample = min(UPSAMPLE_MAX, int((r) * params.camera.fx / camPos.z)); + + // Not on screen so stop now... + if (screenPos.x - upsample >= depth.width() || screenPos.y - upsample >= depth.height()) return; + + // TODO:(Nick) Check depth buffer and don't do anything if already hidden? + + // Each thread in warp takes an upsample point and updates corresponding depth buffer. + const int lane = threadIdx.x % WARP_SIZE; + for (int i=lane; i<upsample*upsample; i+=WARP_SIZE) { + const float u = (i % upsample) - (upsample / 2); + const float v = (i / upsample) - (upsample / 2); + + // Make an initial estimate of the points location + // Use centroid depth as estimate...? + float3 nearest = ftl::cuda::screen_centroid<1>(camera.points, make_float2(screenPos.x+u, screenPos.y+v), make_int2(x,y), params, upsample); + + // Use current points z as estimate + //float3 nearest = params.camera.kinectDepthToSkeleton(screenPos.x+u,screenPos.y+v,camPos.z); + + // Or calculate upper and lower bounds for depth and do gradient + // descent until the gradient change is too small or max iter is reached + // and depth remains within the bounds. + // How to find min and max depths? + + //float ld = nearest.z; + + // TODO: (Nick) Estimate depth using points plane, but needs better normals. + //float t; + //if (ftl::cuda::intersectPlane(normal, worldPos, rayOrigin, rayDir, t)) { + // Plane based estimate of surface at this pixel + //const float3 nearest = rayOrigin + rayDir * camPos.z; + float3 output; + + // Use MLS of camera neighbor points to get more exact estimate + // Iterate until pixel is stable on the surface. + for (int k=0; k<MAX_ITERATIONS; ++k) { + + // TODO:(Nick) Should perhaps use points from all cameras? + // Instead of doing each camera separately... + // If the depth already is close then it has already been done and can skip this point + if (ftl::cuda::mls_point_surface<1>(camera.points, make_int2(x,y), params.m_viewMatrixInverse * nearest, output, SPATIAL_SMOOTHING) <= 0.0f) { + /*const unsigned int cx = screenPos.x; + const unsigned int cy = screenPos.y; + if (cx < depth.width() && cy < depth.height()) { + atomicMax(&depth(cx,cy), 10000.0f); + }*/ + break; + } + + //ftl::cuda::render_depth(depth, params, output); + + output = params.m_viewMatrix * output; + + // This is essentially the SDF function f(x), only the normal should be estimated also from the weights + //const float d = nearest.z + (normal.x*output.x + normal.y*output.y + normal.z*output.z); + + const float d = nearest.z + copysignf(0.5f*length(output - nearest), output.z - nearest.z); + nearest = params.camera.kinectDepthToSkeleton(screenPos.x+u,screenPos.y+v,d); + + const float2 sp = params.camera.cameraToKinectScreenFloat(output); + + //if (isStable(nearest, output, params, d)) { + //if (fabs(sp.x - float(screenPos.x+u)) < 2.0f && fabs(sp.y - float(screenPos.y+v)) < 2.0f) { + if (length(output - nearest) <= 2.0f * params.camera.fx / camPos.z) { + const unsigned int cx = screenPos.x+u; + const unsigned int cy = screenPos.y+v; + + if (d > params.camera.m_sensorDepthWorldMin && d < params.camera.m_sensorDepthWorldMax && cx < depth.width() && cy < depth.height()) { + // Transform estimated point to virtual cam space and output z + atomicMin(&depth(cx,cy), d * 1000.0f); + } + break; + } + + /*if (k >= MAX_ITERATIONS-1 && length(output - nearest) <= SPATIAL_SMOOTHING) { + const unsigned int cx = screenPos.x+u; + const unsigned int cy = screenPos.y+v; + if (d > params.camera.m_sensorDepthWorldMin && d < params.camera.m_sensorDepthWorldMax && cx < depth.width() && cy < depth.height()) { + //atomicMin(&depth(cx,cy), d * 1000.0f); + printf("ERR = %f, %f\n", fabs(sp.x - float(screenPos.x+u)), fabs(sp.y - float(screenPos.y+v))); + } + }*/ + + //nearest = params.camera.kinectDepthToSkeleton(screenPos.x+u,screenPos.y+v,d); // ld + (d - ld)*0.8f + //ld = d; + } + //} + } +} + +// ------ Alternative for pass 2: principle surfaces --------------------------- + +#define NEIGHBOR_RADIUS 1 +#define MAX_NEIGHBORS ((NEIGHBOR_RADIUS*2+1)*(NEIGHBOR_RADIUS*2+1)) + +/* + * Pass 2: Determine depth buffer with enough accuracy for a visibility test in pass 2. + * These values are also used as the actual surface estimate during rendering so should + * at least be plane or sphere fitted if not MLS smoothed onto the actual surface. + */ + __global__ void dibr_visibility_principal_kernel(TextureObject<int> depth, int cam, SplatParams params) { + __shared__ float3 neighborhood_cache[2*T_PER_BLOCK][MAX_NEIGHBORS]; + __shared__ int minimum[2*T_PER_BLOCK]; + __shared__ int maximum[2*T_PER_BLOCK]; + + const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[cam]; + + const int warp = threadIdx.x / WARP_SIZE + threadIdx.y*2; + const int x = (blockIdx.x*blockDim.x + threadIdx.x) / WARP_SIZE; + const int y = blockIdx.y*blockDim.y + threadIdx.y; + + const float3 worldPos = make_float3(tex2D<float4>(camera.points, x, y)); + //const float3 normal = make_float3(tex2D<float4>(camera.normal, x, y)); + if (worldPos.x == MINF) return; + const float r = (camera.poseInverse * worldPos).z / camera.params.fx; + + // Get virtual camera ray for splat centre and backface cull if possible + //const float3 rayOrigin = params.m_viewMatrixInverse * make_float3(0.0f,0.0f,0.0f); + //const float3 rayDir = normalize(params.m_viewMatrixInverse * params.camera.kinectDepthToSkeleton(x,y,1.0f) - rayOrigin); + //if (dot(rayDir, normal) > 0.0f) return; + + // Find the virtual screen position of current point + const float3 camPos = params.m_viewMatrix * worldPos; + if (camPos.z < params.camera.m_sensorDepthWorldMin) return; + if (camPos.z > params.camera.m_sensorDepthWorldMax) return; + const uint2 screenPos = params.camera.cameraToKinectScreen(camPos); + + const int upsample = min(UPSAMPLE_MAX, int((4.0f*r) * params.camera.fx / camPos.z)); + + // Not on screen so stop now... + if (screenPos.x - upsample >= depth.width() || screenPos.y - upsample >= depth.height()) return; + + // TODO:(Nick) Check depth buffer and don't do anything if already hidden? + + // TODO:(Nick) Preload point neighbors and transform to eye + const int lane = threadIdx.x % WARP_SIZE; + if (lane == 0) { + minimum[warp] = 100000000; + maximum[warp] = -100000000; + } + + __syncwarp(); + + for (int i=lane; i<MAX_NEIGHBORS; i+=WARP_SIZE) { + const int u = (i % (2*NEIGHBOR_RADIUS+1)) - NEIGHBOR_RADIUS; + const int v = (i / (2*NEIGHBOR_RADIUS+1)) - NEIGHBOR_RADIUS; + const float3 point = params.m_viewMatrix * make_float3(tex2D<float4>(camera.points, x+u, y+v)); + neighborhood_cache[warp][i] = point; + + if (length(point - camPos) <= 0.04f) { + atomicMin(&minimum[warp], point.z*1000.0f); + atomicMax(&maximum[warp], point.z*1000.0f); + } + } + + __syncwarp(); + + const float interval = (float(maximum[warp])/1000.0f - float(minimum[warp]) / 1000.0f) / float(MAX_ITERATIONS); + //if (y == 200) printf("interval: %f\n", interval); + + // TODO:(Nick) Find min and max depths of neighbors to estimate z bounds + + // Each thread in warp takes an upsample point and updates corresponding depth buffer. + for (int i=lane; i<upsample*upsample; i+=WARP_SIZE) { + const float u = (i % upsample) - (upsample / 2); + const float v = (i / upsample) - (upsample / 2); + + // Make an initial estimate of the points location + // Use centroid depth as estimate...? + //float3 nearest = ftl::cuda::screen_centroid<1>(camera.points, make_float2(screenPos.x+u, screenPos.y+v), make_int2(x,y), params, upsample); + + // Use current points z as estimate + // TODO: Use min point as estimate + float3 nearest = params.camera.kinectDepthToSkeleton(screenPos.x+u,screenPos.y+v,float(minimum[warp])/1000.0f); + + // Or calculate upper and lower bounds for depth and do gradient + // descent until the gradient change is too small or max iter is reached + // and depth remains within the bounds. + // How to find min and max depths? + + // TODO: (Nick) Estimate depth using points plane, but needs better normals. + //float t; + //if (ftl::cuda::intersectPlane(normal, worldPos, rayOrigin, rayDir, t)) { + // Plane based estimate of surface at this pixel + //const float3 nearest = rayOrigin + rayDir * camPos.z; + + // Use MLS of camera neighbor points to get more exact estimate + // Iterate until pixel is stable on the surface. + for (int k=0; k<MAX_ITERATIONS; ++k) { + + // TODO:(Nick) Should perhaps use points from all cameras? + // Instead of doing each camera separately... + // If the depth already is close then it has already been done and can skip this point + const float energy = ftl::cuda::mls_point_energy<MAX_NEIGHBORS>(neighborhood_cache[warp], nearest, SPATIAL_SMOOTHING); + + if (energy <= 0.0f) break; + + //ftl::cuda::render_depth(depth, params, output); + + // This is essentially the SDF function f(x), only the normal should be estimated also from the weights + //const float d = nearest.z + (normal.x*output.x + normal.y*output.y + normal.z*output.z); + + const float d = nearest.z; + nearest = params.camera.kinectDepthToSkeleton(screenPos.x+u,screenPos.y+v,d+interval); + + if (energy >= 0.1f) { + const unsigned int cx = screenPos.x+u; + const unsigned int cy = screenPos.y+v; + if (d > params.camera.m_sensorDepthWorldMin && d < params.camera.m_sensorDepthWorldMax && cx < depth.width() && cy < depth.height()) { + // Transform estimated point to virtual cam space and output z + atomicMin(&depth(cx,cy), d * 1000.0f); + } + break; + } + } + //} + } +} + +#define NEIGHBOR_RADIUS_2 3 +#define NEIGHBOR_WINDOW ((NEIGHBOR_RADIUS_2*2+1)*(NEIGHBOR_RADIUS_2*2+1)) +#define MAX_NEIGHBORS_2 32 + +#define FULL_MASK 0xffffffff + +__device__ inline float warpMax(float e) { + for (int i = WARP_SIZE/2; i > 0; i /= 2) { + const float other = __shfl_xor_sync(FULL_MASK, e, i, WARP_SIZE); + e = max(e, other); + } + return e; +} + +__device__ inline float warpMin(float e) { + for (int i = WARP_SIZE/2; i > 0; i /= 2) { + const float other = __shfl_xor_sync(FULL_MASK, e, i, WARP_SIZE); + e = min(e, other); + } + return e; +} + +#define ENERGY_THRESHOLD 0.1f +#define SMOOTHING_MULTIPLIER_A 10.0f // For surface search +#define SMOOTHING_MULTIPLIER_B 4.0f // For z contribution +#define SMOOTHING_MULTIPLIER_C 4.0f // For colour contribution + + +/* + * Pass 2: Determine depth buffer with enough accuracy for a visibility test in pass 2. + * These values are also used as the actual surface estimate during rendering so should + * at least be plane or sphere fitted if not MLS smoothed onto the actual surface. + * + * This version uses a previous point render as neighbour source. + */ + __global__ void dibr_visibility_principal_kernel2(TextureObject<int> point_in, TextureObject<int> depth, SplatParams params) { + __shared__ float3 neighborhood_cache[2*T_PER_BLOCK][MAX_NEIGHBORS_2]; + __shared__ int minimum[2*T_PER_BLOCK]; + __shared__ int maximum[2*T_PER_BLOCK]; + __shared__ unsigned int nidx[2*T_PER_BLOCK]; + + const int tid = (threadIdx.x + threadIdx.y * blockDim.x); + const int warp = tid / WARP_SIZE; //threadIdx.x / WARP_SIZE + threadIdx.y*2; + const int x = (blockIdx.x*blockDim.x + threadIdx.x) / WARP_SIZE; + const int y = blockIdx.y*blockDim.y + threadIdx.y; + + // Starting point for surface minimum + float clusterBase = params.camera.m_sensorDepthWorldMin; + + // Loop to a deeper surface if not on the first one selected... + while (clusterBase < params.camera.m_sensorDepthWorldMax) { + + const int lane = tid % WARP_SIZE; + if (lane == 0) { + minimum[warp] = 100000000; + maximum[warp] = -100000000; + nidx[warp] = 0; + } + + __syncwarp(); + + // Search for a valid minimum neighbour + // TODO: Should this really be minimum or the median of a depth cluster? + // cluster median seems very hard to calculate... + for (int i=lane; i<NEIGHBOR_WINDOW; i+=WARP_SIZE) { + const int u = (i % (2*NEIGHBOR_RADIUS_2+1)) - NEIGHBOR_RADIUS_2; + const int v = (i / (2*NEIGHBOR_RADIUS_2+1)) - NEIGHBOR_RADIUS_2; + const float3 point = params.camera.kinectDepthToSkeleton(x+u, y+v, float(point_in.tex2D(x+u, y+v)) / 1000.0f); + const float3 camPos = params.camera.kinectDepthToSkeleton(x, y, point.z); + + // If it is close enough... + // TODO: smoothing / strength should be determined by a number of factors including: + // 1) Depth from original source + // 2) Colour contrast in underlying RGB + // 3) Estimated noise levels in depth values + if (point.z > clusterBase && point.z < params.camera.m_sensorDepthWorldMax && length(point - camPos) <= SMOOTHING_MULTIPLIER_A*(point.z / params.camera.fx)) { + atomicMin(&minimum[warp], point.z*1000.0f); + } + } + + __syncwarp(); + + const float minDepth = float(minimum[warp])/1000.0f; + + // Preload valid neighbour points from within a window. A point is valid + // if it is within a specific distance of the minimum. + // Also calculate the maximum at the same time. + // TODO: Could here do a small search in each camera? This would allow all + // points to be considered, even those masked in our depth input. + const float3 minPos = params.camera.kinectDepthToSkeleton(x, y, minDepth); + + for (int i=lane; i<NEIGHBOR_WINDOW; i+=WARP_SIZE) { + const int u = (i % (2*NEIGHBOR_RADIUS_2+1)) - NEIGHBOR_RADIUS_2; + const int v = (i / (2*NEIGHBOR_RADIUS_2+1)) - NEIGHBOR_RADIUS_2; + const float3 point = params.camera.kinectDepthToSkeleton(x+u, y+v, float(point_in.tex2D(x+u, y+v)) / 1000.0f); + + // If it is close enough... + if (point.z > params.camera.m_sensorDepthWorldMin && point.z < params.camera.m_sensorDepthWorldMax && length(point - minPos) <= SMOOTHING_MULTIPLIER_A*(point.z / params.camera.fx)) { + // Append to neighbour list + //unsigned int idx = atomicInc(&nidx[warp], MAX_NEIGHBORS_2-1); + unsigned int idx = atomicAdd(&nidx[warp], 1); + if (idx >= MAX_NEIGHBORS_2) break; + neighborhood_cache[warp][idx] = point; + atomicMax(&maximum[warp], point.z*1000.0f); + } + } + + __syncwarp(); + + const float maxDepth = float(maximum[warp])/1000.0f; + const float interval = (maxDepth - minDepth) / float(MAX_ITERATIONS); + + if (minDepth >= params.camera.m_sensorDepthWorldMax) return; + if (maxDepth <= params.camera.m_sensorDepthWorldMin) return; + //if (y == 200) printf("interval: %f\n", maxDepth); + + // If all samples say same depth, then agree and return + // TODO: Check this is valid, since small energies should be removed... + /*if (fabs(minDepth - maxDepth) < 0.0001f) { + if (lane == 0) { + const unsigned int cx = x; + const unsigned int cy = y; + if (minDepth < params.camera.m_sensorDepthWorldMax && cx < depth.width() && cy < depth.height()) { + // Transform estimated point to virtual cam space and output z + atomicMin(&depth(cx,cy), minDepth * 1000.0f); + } + } + return; + }*/ + + + float maxenergy = -1.0f; + float bestdepth = 0.0f; + + // Search for best or threshold energy + for (int k=lane; k<MAX_ITERATIONS; k+=WARP_SIZE) { + const float3 nearest = params.camera.kinectDepthToSkeleton(x,y,minDepth+float(k)*interval); + const float myenergy = ftl::cuda::mls_point_energy<MAX_NEIGHBORS_2>(neighborhood_cache[warp], nearest, min(nidx[warp], MAX_NEIGHBORS_2), SMOOTHING_MULTIPLIER_B*(nearest.z/params.camera.fx)); + const float newenergy = warpMax(max(myenergy, maxenergy)); + bestdepth = (myenergy == newenergy) ? nearest.z : (newenergy > maxenergy) ? 0.0f : bestdepth; + maxenergy = newenergy; + } + + // If enough energy was found and this thread was the one that found the best + // then output the depth that this energy occured at. + if (bestdepth > 0.0f && maxenergy >= ENERGY_THRESHOLD) { + //printf("E D %f %f\n", maxenergy, bestdepth); + const unsigned int cx = x; + const unsigned int cy = y; + if (bestdepth > params.camera.m_sensorDepthWorldMin && bestdepth < params.camera.m_sensorDepthWorldMax && cx < depth.width() && cy < depth.height()) { + // Transform estimated point to virtual cam space and output z + atomicMin(&depth(cx,cy), bestdepth * 1000.0f); + //depth(cx,cy) = bestdepth * 1000.0f; + } + } + + // TODO: Could the threshold depend upon the number of points? Fewer points + // due to distance is incorrect since really there may not be fewer points + // Perhaps the best option is to make it depend on depth ... really close + // and really far both has lower thresholds due to point densities. Other + // option is smoothing factor and surface distances alter with distance to + // vary the number of points used ... smoothing factor could be a multiple + // of pixel size at given distance. Density from original source is also + // an influencer of smoothing factor and thresholds. Colour contrast also + // has a weighting influence, high contrast is high certainty in the + // disparity so such points should have a high influence over choice of + // surface location. + // + // Magnitude vs dispersion factor in the energy function ... + // * Mag is certainty of surface location + // * Dispersion is how far to propagate that certainty, + if (maxenergy >= ENERGY_THRESHOLD) return; + + // Move to next possible surface... + clusterBase = minDepth + SMOOTHING_MULTIPLIER_B*(minDepth / params.camera.fx); + + }; +} + +// ===== Pass 2 and 3 : Attribute contributions ================================ + +__device__ inline float4 make_float4(const uchar4 &c) { + return make_float4(c.x,c.y,c.z,c.w); +} + +/* + * Pass 2: Accumulate attribute contributions if the points pass a visibility test. + */ +__global__ void dibr_attribute_contrib_kernel( + TextureObject<int> depth_in, + TextureObject<float4> colour_out, + TextureObject<float4> normal_out, + TextureObject<float> contrib_out, int cam, + SplatParams params) { + + const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[cam]; + + const int tid = (threadIdx.x + threadIdx.y * blockDim.x); + //const int warp = tid / WARP_SIZE; + const int x = (blockIdx.x*blockDim.x + threadIdx.x) / WARP_SIZE; + const int y = blockIdx.y*blockDim.y + threadIdx.y; + + const float3 worldPos = make_float3(tex2D<float4>(camera.points, x, y)); + //const float3 normal = make_float3(tex2D<float4>(camera.normal, x, y)); + if (worldPos.x == MINF) return; + const float r = (camera.poseInverse * worldPos).z / camera.params.fx; + + const float3 camPos = params.m_viewMatrix * worldPos; + if (camPos.z < params.camera.m_sensorDepthWorldMin) return; + if (camPos.z > params.camera.m_sensorDepthWorldMax) return; + const uint2 screenPos = params.camera.cameraToKinectScreen(camPos); + + const int upsample = 8; //min(UPSAMPLE_MAX, int((5.0f*r) * params.camera.fx / camPos.z)); + + // Not on screen so stop now... + if (screenPos.x >= depth_in.width() || screenPos.y >= depth_in.height()) return; + + // Is this point near the actual surface and therefore a contributor? + const float d = ((float)depth_in.tex2D((int)screenPos.x, (int)screenPos.y)/1000.0f); + //if (abs(d - camPos.z) > DEPTH_THRESHOLD) return; + + // TODO:(Nick) Should just one thread load these to shared mem? + const float4 colour = make_float4(tex2D<uchar4>(camera.colour, x, y)); + const float4 normal = tex2D<float4>(camera.normal, x, y); + + // Each thread in warp takes an upsample point and updates corresponding depth buffer. + const int lane = tid % WARP_SIZE; + for (int i=lane; i<upsample*upsample; i+=WARP_SIZE) { + const float u = (i % upsample) - (upsample / 2); + const float v = (i / upsample) - (upsample / 2); + + // Use the depth buffer to determine this pixels 3D position in camera space + const float d = ((float)depth_in.tex2D(screenPos.x+u, screenPos.y+v)/1000.0f); + const float3 nearest = params.camera.kinectDepthToSkeleton((int)(screenPos.x+u),(int)(screenPos.y+v),d); + + // What is contribution of our current point at this pixel? + const float weight = ftl::cuda::spatialWeighting(length(nearest - camPos), SMOOTHING_MULTIPLIER_C*(nearest.z/params.camera.fx)); + if (screenPos.x+u < colour_out.width() && screenPos.y+v < colour_out.height() && weight > 0.0f) { // TODO: Use confidence threshold here + const float4 wcolour = colour * weight; + const float4 wnormal = normal * weight; + + //printf("Z %f\n", d); + + // Add this points contribution to the pixel buffer + atomicAdd((float*)&colour_out(screenPos.x+u, screenPos.y+v), wcolour.x); + atomicAdd((float*)&colour_out(screenPos.x+u, screenPos.y+v)+1, wcolour.y); + atomicAdd((float*)&colour_out(screenPos.x+u, screenPos.y+v)+2, wcolour.z); + atomicAdd((float*)&colour_out(screenPos.x+u, screenPos.y+v)+3, wcolour.w); + atomicAdd((float*)&normal_out(screenPos.x+u, screenPos.y+v), wnormal.x); + atomicAdd((float*)&normal_out(screenPos.x+u, screenPos.y+v)+1, wnormal.y); + atomicAdd((float*)&normal_out(screenPos.x+u, screenPos.y+v)+2, wnormal.z); + atomicAdd((float*)&normal_out(screenPos.x+u, screenPos.y+v)+3, wnormal.w); + atomicAdd(&contrib_out(screenPos.x+u, screenPos.y+v), weight); + } + } +} + +/* + * Pass 2: Accumulate attribute contributions if the points pass a visibility test. + */ +/*__global__ void dibr_attribute_contrib_kernel( + TextureObject<int> depth_in, + TextureObject<uchar4> colour_out, + TextureObject<float4> normal_out, int numcams, SplatParams params) { + + const int i = threadIdx.y*blockDim.y + threadIdx.x; + const int bx = blockIdx.x*blockDim.x; + const int by = blockIdx.y*blockDim.y; + const int x = bx + threadIdx.x; + const int y = by + threadIdx.y; + + for (int j=0; j<numcams; ++j) { + const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[j]; + + float3 worldPos = make_float3(tex2D<float4>(camera.points, x, y)); + float r = (camera.poseInverse * worldPos).z; + //if (ftl::cuda::mls_point_surface<3>(camera.points, make_int2(x,y), worldPos, 0.02f) < 0.001f) continue; + if (worldPos.x == MINF) continue; + + const float3 camPos = params.m_viewMatrix * worldPos; + + // Estimate upsample factor using ratio of source depth and output depth + + const int upsample = min(15, (int)(UPSAMPLE_FACTOR * (r / camPos.z))+1); + const float upfactor = 2.0f / (float)(upsample); + + for (int v=0; v<upsample; ++v) { + for (int u=0; u<upsample; ++u) { + float3 point; + const ftl::cuda::fragment nearest = ftl::cuda::upsampled_point(camera.points, camera.normal, camera.colour, + make_float2((float)x-1.0f+u*upfactor,(float)y-1.0f+v*upfactor)); + //if (ftl::cuda::mls_point_surface<3>(camera.points, make_int2(x,y), nearest, point, 0.02f) < 0.001f) continue; + ftl::cuda::render_fragment(depth_in, normal_out, colour_out, params, nearest); + } + } + } +}*/ + + + +__global__ void dibr_normalise_kernel( + TextureObject<float4> colour_in, + TextureObject<uchar4> colour_out, + TextureObject<float4> normals, + TextureObject<float> contribs) { + const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x; + const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y; + + if (x < colour_in.width() && y < colour_in.height()) { + const float4 colour = colour_in.tex2D((int)x,(int)y); + const float4 normal = normals.tex2D((int)x,(int)y); + const float contrib = contribs.tex2D((int)x,(int)y); + + if (contrib > 0.0f) { + colour_out(x,y) = make_uchar4(colour.x / contrib, colour.y / contrib, colour.z / contrib, 0); + normals(x,y) = normal / contrib; + } + } +} + +void ftl::cuda::dibr(const TextureObject<int> &depth_out, + const TextureObject<uchar4> &colour_out, + const TextureObject<float4> &normal_out, + const TextureObject<float> &confidence_out, + const TextureObject<float4> &tmp_colour, + const TextureObject<int> &tmp_depth, + int numcams, + const SplatParams ¶ms, + cudaStream_t stream) { + + const dim3 sgridSize((depth_out.width() + 2 - 1)/2, (depth_out.height() + T_PER_BLOCK - 1)/T_PER_BLOCK); + const dim3 sblockSize(2*WARP_SIZE, T_PER_BLOCK); + const dim3 gridSize((depth_out.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (depth_out.height() + T_PER_BLOCK - 1)/T_PER_BLOCK); + const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK); + + clearColourKernel<<<gridSize, blockSize, 0, stream>>>(colour_out); + ftl::cuda::clear_to_zero(confidence_out, stream); + ftl::cuda::clear_colour(tmp_colour, stream); + ftl::cuda::clear_colour(normal_out, stream); + +#ifdef _DEBUG + cudaSafeCall(cudaDeviceSynchronize()); +#endif + + //int i=3; + + bool noSplatting = params.m_flags & ftl::render::kNoSplatting; + + // Pass 1, gather and upsample depth maps + if (params.m_flags & ftl::render::kNoUpsampling) { + for (int i=0; i<numcams; ++i) + dibr_merge_kernel<<<gridSize, blockSize, 0, stream>>>((noSplatting) ? depth_out : tmp_depth, i, params); + } else { + for (int i=0; i<numcams; ++i) + dibr_merge_upsample_kernel<<<sgridSize, sblockSize, 0, stream>>>((noSplatting) ? depth_out : tmp_depth, i, params); + } + + if (noSplatting) { + // Pass 3, accumulate all point contributions to pixels + for (int i=0; i<numcams; ++i) + dibr_attribute_contrib_kernel<<<sgridSize, sblockSize, 0, stream>>>(depth_out, tmp_colour, normal_out, confidence_out, i, params); + } else { + // Pass 2 + dibr_visibility_principal_kernel2<<<sgridSize, sblockSize, 0, stream>>>(tmp_depth, depth_out, params); + + // Pass 3, accumulate all point contributions to pixels + for (int i=0; i<numcams; ++i) + dibr_attribute_contrib_kernel<<<sgridSize, sblockSize, 0, stream>>>(depth_out, tmp_colour, normal_out, confidence_out, i, params); + } + // Pass 2 + //dibr_visibility_principal_kernel2<<<sgridSize, sblockSize, 0, stream>>>(tmp_depth, depth_out, params); + + // Pass 2, merge a depth map from each camera. + //for (int i=0; i<numcams; ++i) + // dibr_visibility_principal_kernel<<<sgridSize, sblockSize, 0, stream>>>(depth_out, i, params); + + // Pass 4, normalise contributions + dibr_normalise_kernel<<<gridSize, blockSize, 0, stream>>>(tmp_colour, colour_out, normal_out, confidence_out); + + cudaSafeCall( cudaGetLastError() ); + +#ifdef _DEBUG + cudaSafeCall(cudaDeviceSynchronize()); +#endif +} + +void ftl::cuda::dibr_raw(const TextureObject<int> &depth_out, + int numcams, const SplatParams ¶ms, cudaStream_t stream) { + + const dim3 gridSize((depth_out.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (depth_out.height() + T_PER_BLOCK - 1)/T_PER_BLOCK); + const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK); + +#ifdef _DEBUG + cudaSafeCall(cudaDeviceSynchronize()); +#endif + + //dibr_depthmap_direct_kernel<<<gridSize, blockSize, 0, stream>>>(depth_out, numcams, params); + cudaSafeCall( cudaGetLastError() ); + +#ifdef _DEBUG + cudaSafeCall(cudaDeviceSynchronize()); +#endif +} + diff --git a/components/renderers/cpp/src/display.cpp b/components/renderers/cpp/src/display.cpp deleted file mode 100644 index 0f838df751d0c0a315f4d0acca9798d2d7741066..0000000000000000000000000000000000000000 --- a/components/renderers/cpp/src/display.cpp +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright 2019 Nicolas Pope - */ - -#include <loguru.hpp> - -#include <ftl/display.hpp> -#include <ftl/utility/opencv_to_pcl.hpp> - -using ftl::Display; -using cv::Mat; -using cv::Vec3f; - -Display::Display(nlohmann::json &config, std::string name) : ftl::Configurable(config) { - name_ = name; -#if defined HAVE_VIZ - window_ = new cv::viz::Viz3d("FTL: " + name); - window_->setBackgroundColor(cv::viz::Color::white()); -#endif // HAVE_VIZ - - //cv::namedWindow("Image", cv::WINDOW_KEEPRATIO); - -#if defined HAVE_PCL - if (value("points", false)) { - pclviz_ = pcl::visualization::PCLVisualizer::Ptr(new pcl::visualization::PCLVisualizer ("FTL Cloud: " + name)); - pclviz_->setBackgroundColor (255, 255, 255); - pclviz_->addCoordinateSystem (1.0); - pclviz_->setShowFPS(true); - pclviz_->initCameraParameters (); - - pclviz_->registerPointPickingCallback( - [](const pcl::visualization::PointPickingEvent& event, void* viewer_void) { - if (event.getPointIndex () == -1) return; - float x, y, z; - event.getPoint(x, y, z); - LOG(INFO) << "( " << x << ", " << y << ", " << z << ")"; - }, (void*) &pclviz_); - - pclviz_->registerKeyboardCallback ( - [](const pcl::visualization::KeyboardEvent &event, void* viewer_void) { - auto viewer = *static_cast<pcl::visualization::PCLVisualizer::Ptr*>(viewer_void); - pcl::visualization::Camera cam; - viewer->getCameraParameters(cam); - - Eigen::Vector3f pos(cam.pos[0], cam.pos[1], cam.pos[2]); - Eigen::Vector3f focal(cam.focal[0], cam.focal[1], cam.focal[2]); - Eigen::Vector3f dir = focal - pos; //.normalize(); - dir.normalize(); - - const float speed = 40.0f; - - if (event.getKeySym() == "Up") { - pos += speed*dir; - focal += speed*dir; - } else if (event.getKeySym() == "Down") { - pos -= speed*dir; - focal -= speed*dir; - } else if (event.getKeySym() == "Left") { - Eigen::Matrix3f m = Eigen::AngleAxisf(-0.5f*M_PI, Eigen::Vector3f::UnitY()).toRotationMatrix(); - dir = m*dir; - pos += speed*dir; - focal += speed*dir; - } else if (event.getKeySym() == "Right") { - Eigen::Matrix3f m = Eigen::AngleAxisf(0.5f*M_PI, Eigen::Vector3f::UnitY()).toRotationMatrix(); - dir = m*dir; - pos += speed*dir; - focal += speed*dir; - } - - - cam.pos[0] = pos[0]; - cam.pos[1] = pos[1]; - cam.pos[2] = pos[2]; - cam.focal[0] = focal[0]; - cam.focal[1] = focal[1]; - cam.focal[2] = focal[2]; - viewer->setCameraParameters(cam); - - }, (void*)&pclviz_); - } -#endif // HAVE_PCL - - active_ = true; -} - -Display::~Display() { - #if defined HAVE_VIZ - delete window_; - #endif // HAVE_VIZ -} - -#ifdef HAVE_PCL -/** - * Convert an OpenCV RGB and Depth Mats to a PCL XYZRGB point cloud. - */ -static pcl::PointCloud<pcl::PointXYZRGB>::Ptr rgbdToPointXYZ(const cv::Mat &rgb, const cv::Mat &depth, const ftl::rgbd::Camera &p) { - const double CX = p.cx; - const double CY = p.cy; - const double FX = p.fx; - const double FY = p.fy; - - pcl::PointCloud<pcl::PointXYZRGB>::Ptr point_cloud_ptr(new pcl::PointCloud<pcl::PointXYZRGB>); - point_cloud_ptr->width = rgb.cols * rgb.rows; - point_cloud_ptr->height = 1; - - for(int i=0;i<rgb.rows;i++) { - const float *sptr = depth.ptr<float>(i); - for(int j=0;j<rgb.cols;j++) { - float d = sptr[j] * 1000.0f; - - pcl::PointXYZRGB point; - point.x = (((double)j + CX) / FX) * d; - point.y = (((double)i + CY) / FY) * d; - point.z = d; - - if (point.x == INFINITY || point.y == INFINITY || point.z > 20000.0f || point.z < 0.04f) { - point.x = 0.0f; point.y = 0.0f; point.z = 0.0f; - } - - cv::Point3_<uchar> prgb = rgb.at<cv::Point3_<uchar>>(i, j); - uint32_t rgb = (static_cast<uint32_t>(prgb.z) << 16 | static_cast<uint32_t>(prgb.y) << 8 | static_cast<uint32_t>(prgb.x)); - point.rgb = *reinterpret_cast<float*>(&rgb); - - point_cloud_ptr -> points.push_back(point); - } - } - - return point_cloud_ptr; -} -#endif // HAVE_PCL - -bool Display::render(const cv::Mat &rgb, const cv::Mat &depth, const ftl::rgbd::Camera &p) { - Mat idepth; - - if (value("points", false) && rgb.rows != 0) { -#if defined HAVE_PCL - auto pc = rgbdToPointXYZ(rgb, depth, p); - - pcl::visualization::PointCloudColorHandlerRGBField<pcl::PointXYZRGB> rgb(pc); - if (!pclviz_->updatePointCloud<pcl::PointXYZRGB> (pc, rgb, "reconstruction")) { - pclviz_->addPointCloud<pcl::PointXYZRGB> (pc, rgb, "reconstruction"); - pclviz_->setCameraPosition(-878.0, -71.0, -2315.0, -0.1, -0.99, 0.068, 0.0, -1.0, 0.0); - pclviz_->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "reconstruction"); - } -#elif defined HAVE_VIZ - //cv::Mat Q_32F; - //calibrate_.getQ().convertTo(Q_32F, CV_32F); - /*cv::Mat_<cv::Vec3f> XYZ(depth.rows, depth.cols); // Output point cloud - reprojectImageTo3D(depth+20.0f, XYZ, q, true); - - // Remove all invalid pixels from point cloud - XYZ.setTo(Vec3f(NAN, NAN, NAN), depth == 0.0f); - - cv::viz::WCloud cloud_widget = cv::viz::WCloud(XYZ, rgb); - cloud_widget.setRenderingProperty(cv::viz::POINT_SIZE, 2); - - window_->showWidget("coosys", cv::viz::WCoordinateSystem()); - window_->showWidget("Depth", cloud_widget); - - //window_->spinOnce(40, true);*/ - -#else // HAVE_VIZ - - LOG(ERROR) << "Need OpenCV Viz module to display points"; - -#endif // HAVE_VIZ - } - - if (value("left", false)) { - if (value("crosshair", false)) { - cv::line(rgb, cv::Point(0, rgb.rows/2), cv::Point(rgb.cols-1, rgb.rows/2), cv::Scalar(0,0,255), 1); - cv::line(rgb, cv::Point(rgb.cols/2, 0), cv::Point(rgb.cols/2, rgb.rows-1), cv::Scalar(0,0,255), 1); - } - cv::namedWindow("Left: " + name_, cv::WINDOW_KEEPRATIO); - cv::imshow("Left: " + name_, rgb); - } - if (value("right", false)) { - /*if (config_["crosshair"]) { - cv::line(rgbr, cv::Point(0, rgbr.rows/2), cv::Point(rgbr.cols-1, rgbr.rows/2), cv::Scalar(0,0,255), 1); - cv::line(rgbr, cv::Point(rgbr.cols/2, 0), cv::Point(rgbr.cols/2, rgbr.rows-1), cv::Scalar(0,0,255), 1); - } - cv::namedWindow("Right: " + name_, cv::WINDOW_KEEPRATIO); - cv::imshow("Right: " + name_, rgbr);*/ - } - - if (value("disparity", false)) { - /*Mat depth32F = (focal * (float)l.cols * base_line) / depth; - normalize(depth32F, depth32F, 0, 255, NORM_MINMAX, CV_8U); - cv::imshow("Depth", depth32F); - if(cv::waitKey(10) == 27){ - //exit if ESC is pressed - active_ = false; - }*/ - } else if (value("depth", false)) { - if (value("flip_vert", false)) { - cv::flip(depth, idepth, 0); - } else { - idepth = depth; - } - - idepth.convertTo(idepth, CV_8U, 255.0f / 10.0f); // TODO(nick) - - applyColorMap(idepth, idepth, cv::COLORMAP_JET); - cv::imshow("Depth: " + name_, idepth); - //if(cv::waitKey(40) == 27) { - // exit if ESC is pressed - // active_ = false; - //} - } - - return true; -} - -#if defined HAVE_PCL -bool Display::render(pcl::PointCloud<pcl::PointXYZRGB>::ConstPtr pc) { - pcl::visualization::PointCloudColorHandlerRGBField<pcl::PointXYZRGB> rgb(pc); - if (pclviz_ && !pclviz_->updatePointCloud<pcl::PointXYZRGB> (pc, rgb, "reconstruction")) { - pclviz_->addPointCloud<pcl::PointXYZRGB> (pc, rgb, "reconstruction"); - pclviz_->setCameraPosition(-878.0, -71.0, -2315.0, -0.1, -0.99, 0.068, 0.0, -1.0, 0.0); - pclviz_->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "reconstruction"); - } - return true; -} -#endif // HAVE_PCL -bool Display::render(const cv::Mat &img, style_t s) { - if (s == STYLE_NORMAL) { - cv::imshow("Image", img); - } else if (s == STYLE_DISPARITY) { - Mat idepth; - - if (value("flip_vert", false)) { - cv::flip(img, idepth, 0); - } else { - idepth = img; - } - - idepth.convertTo(idepth, CV_8U, 255.0f / 256.0f); - - applyColorMap(idepth, idepth, cv::COLORMAP_JET); - cv::imshow("Disparity", idepth); - } - - return true; -} - -bool Display::hasDisplays() { - return value("depth", false) || value("left", false) || value("right", false) || value("points", false); -} - -void Display::wait(int ms) { - if (value("points", false)) { - #if defined HAVE_PCL - if (pclviz_) pclviz_->spinOnce(20); - #elif defined HAVE_VIZ - window_->spinOnce(1, true); - #endif // HAVE_VIZ - } - - if (value("depth", false) || value("left", false) || value("right", false)) { - while (true) { - int key = cv::waitKey(ms); - - if(key == 27) { - // exit if ESC is pressed - active_ = false; - } else if (key == -1) { - return; - } else { - ms = 1; - for (auto &h : key_handlers_) { - h(key); - } - } - } - } -} - -bool Display::active() const { - #if defined HAVE_PCL - return active_ && (!pclviz_ || !pclviz_->wasStopped()); - #elif defined HAVE_VIZ - return active_ && !window_->wasStopped(); - #else - return active_; - #endif -} - diff --git a/components/renderers/cpp/src/mls_cuda.hpp b/components/renderers/cpp/src/mls_cuda.hpp new file mode 100644 index 0000000000000000000000000000000000000000..7f2cf1d88d92e319ceef96274626477d6e2b839c --- /dev/null +++ b/components/renderers/cpp/src/mls_cuda.hpp @@ -0,0 +1,399 @@ +#ifndef _FTL_MLS_CUDA_HPP_ +#define _FTL_MLS_CUDA_HPP_ + +#include <ftl/cuda_util.hpp> +#include <ftl/cuda_common.hpp> +#include <ftl/cuda_matrix_util.hpp> +#include "splat_params.hpp" + +__device__ inline float3 make_float3(const uchar4 &c) { + return make_float3((float)c.x,(float)c.y,(float)c.z); +} + +__device__ inline uchar4 make_uchar4(const float3 &c) { + return make_uchar4(c.x,c.y,c.z,255); +} + +namespace ftl { +namespace cuda { + +/* + * Guennebaud, G.; Gross, M. Algebraic point set surfaces. ACMTransactions on Graphics Vol. 26, No. 3, Article No. 23, 2007. + * Used in: FusionMLS: Highly dynamic 3D reconstruction with consumer-grade RGB-D cameras + * r = distance between points + * h = smoothing parameter in meters (default 4cm) + */ +__device__ inline float spatialWeighting(float r, float h) { + if (r >= h) return 0.0f; + float rh = r / h; + rh = 1.0f - rh*rh; + return rh*rh*rh*rh; +} + +__device__ float colourWeighting(float c); + +struct fragment { + float3 point; + float3 normal; + uchar4 colour; +}; + +__device__ inline float3 upsampled_point(cudaTextureObject_t pointset, const float2 &uv) { + float3 R = make_float3(0.0f, 0.0f, 0.0f); + const float3 P1 = make_float3(tex2D<float4>(pointset, int(uv.x), int(uv.y))); + const float D1 = 1.0f - length(uv - make_float2(int(uv.x), int(uv.y))); + R += D1 * P1; + + const float3 P2 = make_float3(tex2D<float4>(pointset, int(uv.x), int(uv.y+1.0f))); + const float D2 = 1.0f - length(uv - make_float2(int(uv.x), int(uv.y+1.0f))); + R += D2 * P2; + + const float3 P3 = make_float3(tex2D<float4>(pointset, int(uv.x+1.0f), int(uv.y))); + const float D3 = 1.0f - length(uv - make_float2(int(uv.x+1.0f), int(uv.y))); + R += D3 * P3; + + const float3 P4 = make_float3(tex2D<float4>(pointset, int(uv.x+1.0f), int(uv.y+1.0f))); + const float D4 = 1.0f - length(uv - make_float2(int(uv.x+1.0f), int(uv.y+1.0f))); + R += D4 * P4; + + // R is the centroid of surrounding points. + R /= (D1+D2+D3+D4); + + // FIXME: Should not use centroid but instead sample the surface at this point + // Use plane estimate at point to get "centroid" and then do the spatial weighted sample? + return R; +} + +__device__ inline fragment upsampled_point(cudaTextureObject_t pointset, + cudaTextureObject_t normals, cudaTextureObject_t colours, const float2 &uv) { + float3 R = make_float3(0.0f, 0.0f, 0.0f); + float3 N = make_float3(0.0f, 0.0f, 0.0f); + float3 C = make_float3(0.0f, 0.0f, 0.0f); + + // TODO:(Nick) Don't upsample points if distance is too great + + const float3 P1 = make_float3(tex2D<float4>(pointset, int(uv.x), int(uv.y))); + const float D1 = 1.0f - length(uv - make_float2(int(uv.x), int(uv.y))); + R += D1 * P1; + N += D1 * make_float3(tex2D<float4>(normals, int(uv.x), int(uv.y))); + C += D1 * make_float3(tex2D<uchar4>(colours, int(uv.x), int(uv.y))); + + const float3 P2 = make_float3(tex2D<float4>(pointset, int(uv.x), int(uv.y+1.0f))); + const float D2 = 1.0f - length(uv - make_float2(int(uv.x), int(uv.y+1.0f))); + R += D2 * P2; + N += D2 * make_float3(tex2D<float4>(normals, int(uv.x), int(uv.y+1.0f))); + C += D2 * make_float3(tex2D<uchar4>(colours, int(uv.x), int(uv.y+1.0f))); + + const float3 P3 = make_float3(tex2D<float4>(pointset, int(uv.x+1.0f), int(uv.y))); + const float D3 = 1.0f - length(uv - make_float2(int(uv.x+1.0f), int(uv.y))); + R += D3 * P3; + N += D3 * make_float3(tex2D<float4>(normals, int(uv.x+1.0f), int(uv.y))); + C += D3 * make_float3(tex2D<uchar4>(colours, int(uv.x+1.0f), int(uv.y))); + + const float3 P4 = make_float3(tex2D<float4>(pointset, int(uv.x+1.0f), int(uv.y+1.0f))); + const float D4 = 1.0f - length(uv - make_float2(int(uv.x+1.0f), int(uv.y+1.0f))); + R += D4 * P4; + N += D4 * make_float3(tex2D<float4>(normals, int(uv.x+1.0f), int(uv.y+1.0f))); + C += D4 * make_float3(tex2D<uchar4>(colours, int(uv.x+1.0f), int(uv.y+1.0f))); + + return {R / (D1+D2+D3+D4), N / (D1+D2+D3+D4), make_uchar4(C / (D1+D2+D3+D4))}; +} + +__device__ inline void render_depth(ftl::cuda::TextureObject<int> &depth, ftl::render::SplatParams ¶ms, const float3 &worldPos) { + const float3 camPos = params.m_viewMatrix * worldPos; + const float d = camPos.z; + if (d < params.camera.m_sensorDepthWorldMin) return; + + const float2 screenPosf = params.camera.cameraToKinectScreenFloat(camPos); + const uint2 screenPos = make_uint2(make_int2(screenPosf)); + + const unsigned int cx = screenPos.x; + const unsigned int cy = screenPos.y; + + if (cx < depth.width() && cy < depth.height()) { + atomicMin(&depth(cx,cy), d * 1000.0f); + } +} + +__device__ inline void render_fragment( + ftl::cuda::TextureObject<int> &depth_in, + ftl::cuda::TextureObject<float4> &normal_out, + ftl::cuda::TextureObject<uchar4> &colour_out, + ftl::render::SplatParams ¶ms, const fragment &frag) { + const float3 camPos = params.m_viewMatrix * frag.point; + const float d = camPos.z; + if (d < params.camera.m_sensorDepthWorldMin) return; + + const float2 screenPosf = params.camera.cameraToKinectScreenFloat(camPos); + const uint2 screenPos = make_uint2(make_int2(screenPosf)); + + const unsigned int cx = screenPos.x; + const unsigned int cy = screenPos.y; + + if (cx < depth_in.width() && cy < depth_in.height()) { + if (depth_in(cx,cy) == (int)(d * 1000.0f)) { + colour_out(cx,cy) = frag.colour; + normal_out(cx,cy) = make_float4(frag.normal, 0.0f); + } + } +} + +/** + * Estimate the point set surface location near to a given point. + */ +template <int R> +__device__ float3 screen_centroid( + cudaTextureObject_t pointset, + const float2 &suv, + const int2 &uv, + const ftl::render::SplatParams ¶ms, + float smoothing) { + + float3 pos = make_float3(0.0f, 0.0f, 0.0f); + float weights = 0.0f; + + //#pragma unroll + for (int v=-R; v<=R; ++v) { + for (int u=-R; u<=R; ++u) { + //if (uv.x+u >= 0 && uv.x+u < pointset.width() && uv.y+v >= 0 && uv.y+v < pointset.height()) { + const float3 samplePoint = params.m_viewMatrix * make_float3(tex2D<float4>(pointset, uv.x+u, uv.y+v)); + const float weight = ftl::cuda::spatialWeighting(length(suv - params.camera.cameraToKinectScreenFloat(samplePoint)), smoothing); + pos += weight*samplePoint; + weights += weight; + //} + } + } + + if (weights > 0.0f) pos = pos / weights; + return pos; +} + +/** + * Estimate a point set surface point from an existing point in the set. + */ +template <int R> +__device__ float mls_point_surface( + cudaTextureObject_t pointset, + const int2 &uv, + float3 &estPoint, + float smoothing) { + + float3 pos = make_float3(0.0f, 0.0f, 0.0f); + float weights = 0.0f; + const float3 nearPoint = make_float3(tex2D<float4>(pointset, uv.x, uv.y)); + + //#pragma unroll + for (int v=-R; v<=R; ++v) { + for (int u=-R; u<=R; ++u) { + //if (uv.x+u >= 0 && uv.x+u < pointset.width() && uv.y+v >= 0 && uv.y+v < pointset.height()) { + const float3 samplePoint = make_float3(tex2D<float4>(pointset, uv.x+u, uv.y+v)); + const float weight = ftl::cuda::spatialWeighting(length(nearPoint - samplePoint), smoothing); + pos += weight*samplePoint; + weights += weight; + //} + } + } + + if (weights > 0.0f) estPoint = pos / weights; + return weights; +}; + +/** + * Estimate the point set surface location near to a given point. + */ +template <int R> +__device__ float mls_point_surface( + cudaTextureObject_t pointset, + const int2 &uv, + const float3 &nearPoint, + float3 &estPoint, + float smoothing) { + + float3 pos = make_float3(0.0f, 0.0f, 0.0f); + float weights = 0.0f; + + //#pragma unroll + for (int v=-R; v<=R; ++v) { + for (int u=-R; u<=R; ++u) { + //if (uv.x+u >= 0 && uv.x+u < pointset.width() && uv.y+v >= 0 && uv.y+v < pointset.height()) { + const float3 samplePoint = make_float3(tex2D<float4>(pointset, uv.x+u, uv.y+v)); + const float weight = ftl::cuda::spatialWeighting(length(nearPoint - samplePoint), smoothing); + pos += weight*samplePoint; + weights += weight; + //} + } + } + + if (weights > 0.0f) estPoint = pos / weights; + return weights; +} + +/** + * Calculate the point sample energy. + */ +template <int R> +__device__ float mls_point_energy( + cudaTextureObject_t pointset, + const int2 &uv, + const float3 &nearPoint, + float smoothing) { + + float weights = 0.0f; + + //#pragma unroll + for (int v=-R; v<=R; ++v) { + for (int u=-R; u<=R; ++u) { + //if (uv.x+u >= 0 && uv.x+u < pointset.width() && uv.y+v >= 0 && uv.y+v < pointset.height()) { + const float3 samplePoint = make_float3(tex2D<float4>(pointset, uv.x+u, uv.y+v)); + const float weight = ftl::cuda::spatialWeighting(length(nearPoint - samplePoint), smoothing); + weights += weight; + //} + } + } + + return weights; +} + +/** + * Calculate the point sample energy. + */ +template <int M> +__device__ float mls_point_energy( + const float3 (&pointset)[M], + const float3 &nearPoint, + float smoothing) { + + float weights = 0.0f; + + //#pragma unroll + for (int i=0; i<M; ++i) { + const float3 samplePoint = pointset[i]; + const float weight = ftl::cuda::spatialWeighting(length(nearPoint - samplePoint), smoothing); + weights += weight; + } + + return weights; +} + +/** + * Calculate the point sample energy. + */ +template <int M> +__device__ float mls_point_energy( + const float3 (&pointset)[M], + const float3 &nearPoint, + unsigned int N, + float smoothing) { + + float weights = 0.0f; + + //#pragma unroll + for (int i=0; i<N; ++i) { + const float3 samplePoint = pointset[i]; + const float weight = ftl::cuda::spatialWeighting(length(nearPoint - samplePoint), smoothing); + weights += weight; + } + + return weights; +} + +/** + * Estimate a point set surface location near an existing and return also + * an estimate of the normal and colour of that point. + */ +template <int R> +__device__ float mls_point_surface( + cudaTextureObject_t pointset, + cudaTextureObject_t normalset, + cudaTextureObject_t colourset, + const int2 &uv, + float3 &estPoint, + float3 &estNormal, + uchar4 &estColour, + float smoothing) { + + float3 pos = make_float3(0.0f, 0.0f, 0.0f); + float3 normal = make_float3(0.0f, 0.0f, 0.0f); + float3 colour = make_float3(0.0f, 0.0f, 0.0f); + float weights = 0.0f; + const float3 nearPoint = make_float3(tex2D<float4>(pointset, uv.x, uv.y)); + + //#pragma unroll + for (int v=-R; v<=R; ++v) { + for (int u=-R; u<=R; ++u) { + //if (uv.x+u >= 0 && uv.x+u < pointset.width() && uv.y+v >= 0 && uv.y+v < pointset.height()) { + const float3 samplePoint = make_float3(tex2D<float4>(pointset, uv.x+u, uv.y+v)); + const float weight = spatialWeighting(length(nearPoint - samplePoint), smoothing); + + if (weight > 0.0f) { + pos += weight*samplePoint; + weights += weight; + + normal += weight * make_float3(tex2D<float4>(normalset, uv.x+u, uv.y+v)); + const uchar4 c = tex2D<uchar4>(colourset, uv.x+u, uv.y+v); + colour += weight * make_float3(c.x, c.y, c.z); + } + //} + } + } + + if (weights > 0.0f) { + estPoint = pos / weights; + estNormal = normal / weights; + estColour = make_uchar4(colour.x / weights, colour.y / weights, colour.z / weights, 255); + } + return weights; +} + +/** + * Estimate a point set surface location near a given point and return also + * an estimate of the normal and colour of that point. + */ +template <int R> +__device__ float mls_point_surface( + cudaTextureObject_t pointset, + cudaTextureObject_t normalset, + cudaTextureObject_t colourset, + const int2 &uv, + const float3 &nearPoint, + float3 &estPoint, + float3 &estNormal, + uchar4 &estColour, + float smoothing) { + + float3 pos = make_float3(0.0f, 0.0f, 0.0f); + float3 normal = make_float3(0.0f, 0.0f, 0.0f); + float3 colour = make_float3(0.0f, 0.0f, 0.0f); + float weights = 0.0f; + + //#pragma unroll + for (int v=-R; v<=R; ++v) { + for (int u=-R; u<=R; ++u) { + //if (uv.x+u >= 0 && uv.x+u < pointset.width() && uv.y+v >= 0 && uv.y+v < pointset.height()) { + const float3 samplePoint = make_float3(tex2D<float4>(pointset, uv.x+u, uv.y+v)); + const float weight = spatialWeighting(length(nearPoint - samplePoint), smoothing); + + if (weight > 0.0f) { + pos += weight*samplePoint; + weights += weight; + + normal += weight * make_float3(tex2D<float4>(normalset, uv.x+u, uv.y+v)); + const uchar4 c = tex2D<uchar4>(colourset, uv.x+u, uv.y+v); + colour += weight * make_float3(c.x, c.y, c.z); + } + //} + } + } + + if (weights > 0.0f) { + estPoint = pos / weights; + estNormal = normal / weights; + estColour = make_uchar4(colour.x / weights, colour.y / weights, colour.z / weights, 255); + } + return weights; +} + +} +} + +#endif // _FTL_MLS_CUDA_HPP_ diff --git a/components/renderers/cpp/src/points.cu b/components/renderers/cpp/src/points.cu new file mode 100644 index 0000000000000000000000000000000000000000..39764e4c8aba523caf2758262d9f41f8782ac9dc --- /dev/null +++ b/components/renderers/cpp/src/points.cu @@ -0,0 +1,28 @@ +#include <ftl/cuda/points.hpp> + +#define T_PER_BLOCK 8 + +__global__ void point_cloud_kernel(ftl::cuda::TextureObject<float4> output, ftl::cuda::TextureObject<float> depth, ftl::rgbd::Camera params, float4x4 pose) +{ + const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x; + const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y; + + if (x < params.width && y < params.height) { + float d = depth.tex2D((int)x, (int)y); + + output(x,y) = (d >= params.minDepth && d <= params.maxDepth) ? + make_float4(pose * params.screenToCam(x, y, d), 0.0f) : + make_float4(MINF, MINF, MINF, MINF); + } +} + +void ftl::cuda::point_cloud(ftl::cuda::TextureObject<float4> &output, ftl::cuda::TextureObject<float> &depth, const ftl::rgbd::Camera ¶ms, const float4x4 &pose, cudaStream_t stream) { + const dim3 gridSize((params.width + T_PER_BLOCK - 1)/T_PER_BLOCK, (params.height + T_PER_BLOCK - 1)/T_PER_BLOCK); + const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK); + + point_cloud_kernel<<<gridSize, blockSize, 0, stream>>>(output, depth, params, pose); + +#ifdef _DEBUG + cudaSafeCall(cudaDeviceSynchronize()); +#endif +} diff --git a/components/renderers/cpp/src/rgbd_display.cpp b/components/renderers/cpp/src/rgbd_display.cpp deleted file mode 100644 index a0d79f8aeeb42b0a0a1fd281c5f3e9065b43780c..0000000000000000000000000000000000000000 --- a/components/renderers/cpp/src/rgbd_display.cpp +++ /dev/null @@ -1,129 +0,0 @@ -#include <ftl/rgbd_display.hpp> -#include <opencv2/opencv.hpp> - -using ftl::rgbd::Source; -using ftl::rgbd::Display; -using std::string; -using cv::Mat; - -int Display::viewcount__ = 0; - -template<class T> -Eigen::Matrix<T,4,4> lookAt -( - Eigen::Matrix<T,3,1> const & eye, - Eigen::Matrix<T,3,1> const & center, - Eigen::Matrix<T,3,1> const & up -) -{ - typedef Eigen::Matrix<T,4,4> Matrix4; - typedef Eigen::Matrix<T,3,1> Vector3; - - Vector3 f = (center - eye).normalized(); - Vector3 u = up.normalized(); - Vector3 s = f.cross(u).normalized(); - u = s.cross(f); - - Matrix4 res; - res << s.x(),s.y(),s.z(),-s.dot(eye), - u.x(),u.y(),u.z(),-u.dot(eye), - -f.x(),-f.y(),-f.z(),f.dot(eye), - 0,0,0,1; - - return res; -} - -static void setMouseAction(const std::string& winName, const MouseAction &action) -{ - cv::setMouseCallback(winName, - [] (int event, int x, int y, int flags, void* userdata) { - (*(MouseAction*)userdata)(event, x, y, flags); - }, (void*)&action); -} - -Display::Display(nlohmann::json &config) : ftl::Configurable(config) { - name_ = value("name", string("View [")+std::to_string(viewcount__)+string("]")); - viewcount__++; - - init(); -} - -Display::Display(nlohmann::json &config, Source *source) - : ftl::Configurable(config) { - name_ = value("name", string("View [")+std::to_string(viewcount__)+string("]")); - viewcount__++; - init(); -} - -Display::~Display() { - -} - -void Display::init() { - active_ = true; - source_ = nullptr; - cv::namedWindow(name_, cv::WINDOW_KEEPRATIO); - - eye_ = Eigen::Vector3d(0.0, 0.0, 0.0); - centre_ = Eigen::Vector3d(0.0, 0.0, -4.0); - up_ = Eigen::Vector3d(0,1.0,0); - lookPoint_ = Eigen::Vector3d(0.0,0.0,-4.0); - lerpSpeed_ = 0.4f; - - // Keyboard camera controls - onKey([this](int key) { - //LOG(INFO) << "Key = " << key; - if (key == 81 || key == 83) { - Eigen::Quaternion<double> q; q = Eigen::AngleAxis<double>((key == 81) ? 0.01 : -0.01, up_); - eye_ = (q * (eye_ - centre_)) + centre_; - } else if (key == 84 || key == 82) { - double scalar = (key == 84) ? 0.99 : 1.01; - eye_ = ((eye_ - centre_) * scalar) + centre_; - } - }); - - // TODO(Nick) Calculate "camera" properties of viewport. - mouseaction_ = [this]( int event, int ux, int uy, int) { - //LOG(INFO) << "Mouse " << ux << "," << uy; - if (event == 1 && source_) { // click - Eigen::Vector4d camPos = source_->point(ux,uy); - camPos *= -1.0f; - Eigen::Vector4d worldPos = source_->getPose() * camPos; - lookPoint_ = Eigen::Vector3d(worldPos[0],worldPos[1],worldPos[2]); - LOG(INFO) << "Depth at click = " << -camPos[2]; - } - }; - ::setMouseAction(name_, mouseaction_); -} - -void Display::wait(int ms) { - while (true) { - int key = cv::waitKey(ms); - - if(key == 27) { - // exit if ESC is pressed - active_ = false; - } else if (key == -1) { - return; - } else { - ms = 1; - for (auto &h : key_handlers_) { - h(key); - } - } - } -} - -void Display::update() { - if (!source_) return; - - centre_ += (lookPoint_ - centre_) * (lerpSpeed_ * 0.1f); - Eigen::Matrix4d viewPose = lookAt<double>(eye_,centre_,up_).inverse(); - source_->setPose(viewPose); - - Mat rgb, depth; - source_->grab(); - source_->getFrames(rgb, depth); - if (rgb.rows > 0) cv::imshow(name_, rgb); - wait(1); -} diff --git a/components/renderers/cpp/src/splat_render.cpp b/components/renderers/cpp/src/splat_render.cpp new file mode 100644 index 0000000000000000000000000000000000000000..daf9f5f64c019d24ae9afb9f2e4540c59b722922 --- /dev/null +++ b/components/renderers/cpp/src/splat_render.cpp @@ -0,0 +1,172 @@ +#include <ftl/render/splat_render.hpp> +#include <ftl/utility/matrix_conversion.hpp> +#include "splatter_cuda.hpp" +#include <ftl/cuda/points.hpp> + +#include <opencv2/core/cuda_stream_accessor.hpp> + +using ftl::render::Splatter; +using ftl::rgbd::Channel; +using ftl::rgbd::Channels; +using ftl::rgbd::Format; +using cv::cuda::GpuMat; + +Splatter::Splatter(nlohmann::json &config, ftl::rgbd::FrameSet *fs) : ftl::render::Renderer(config), scene_(fs) { + +} + +Splatter::~Splatter() { + +} + +bool Splatter::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cudaStream_t stream) { + SHARED_LOCK(scene_->mtx, lk); + if (!src->isReady()) return false; + + const auto &camera = src->parameters(); + + //cudaSafeCall(cudaSetDevice(scene_->getCUDADevice())); + + // Create all the required channels + out.create<GpuMat>(Channel::Depth, Format<float>(camera.width, camera.height)); + out.create<GpuMat>(Channel::Colour, Format<uchar4>(camera.width, camera.height)); + + // FIXME: Use source resolutions, not virtual resolution + temp_.create<GpuMat>(Channel::Colour, Format<float4>(camera.width, camera.height)); + temp_.create<GpuMat>(Channel::Colour2, Format<uchar4>(camera.width, camera.height)); + temp_.create<GpuMat>(Channel::Contribution, Format<float>(camera.width, camera.height)); + temp_.create<GpuMat>(Channel::Depth, Format<int>(camera.width, camera.height)); + temp_.create<GpuMat>(Channel::Depth2, Format<int>(camera.width, camera.height)); + temp_.create<GpuMat>(Channel::Normals, Format<float4>(camera.width, camera.height)); + + cv::cuda::Stream cvstream = cv::cuda::StreamAccessor::wrapStream(stream); + + // Create buffers if they don't exist + /*if ((unsigned int)depth1_.width() != camera.width || (unsigned int)depth1_.height() != camera.height) { + depth1_ = ftl::cuda::TextureObject<int>(camera.width, camera.height); + } + if ((unsigned int)depth3_.width() != camera.width || (unsigned int)depth3_.height() != camera.height) { + depth3_ = ftl::cuda::TextureObject<int>(camera.width, camera.height); + } + if ((unsigned int)colour1_.width() != camera.width || (unsigned int)colour1_.height() != camera.height) { + colour1_ = ftl::cuda::TextureObject<uchar4>(camera.width, camera.height); + } + if ((unsigned int)colour_tmp_.width() != camera.width || (unsigned int)colour_tmp_.height() != camera.height) { + colour_tmp_ = ftl::cuda::TextureObject<float4>(camera.width, camera.height); + } + if ((unsigned int)normal1_.width() != camera.width || (unsigned int)normal1_.height() != camera.height) { + normal1_ = ftl::cuda::TextureObject<float4>(camera.width, camera.height); + } + if ((unsigned int)depth2_.width() != camera.width || (unsigned int)depth2_.height() != camera.height) { + depth2_ = ftl::cuda::TextureObject<float>(camera.width, camera.height); + } + if ((unsigned int)colour2_.width() != camera.width || (unsigned int)colour2_.height() != camera.height) { + colour2_ = ftl::cuda::TextureObject<uchar4>(camera.width, camera.height); + }*/ + + // Parameters object to pass to CUDA describing the camera + SplatParams params; + params.m_flags = 0; + if (src->value("splatting", true) == false) params.m_flags |= ftl::render::kNoSplatting; + if (src->value("upsampling", true) == false) params.m_flags |= ftl::render::kNoUpsampling; + if (src->value("texturing", true) == false) params.m_flags |= ftl::render::kNoTexturing; + params.m_viewMatrix = MatrixConversion::toCUDA(src->getPose().cast<float>().inverse()); + params.m_viewMatrixInverse = MatrixConversion::toCUDA(src->getPose().cast<float>()); + params.camera = camera; + + // Clear all channels to 0 or max depth + temp_.get<GpuMat>(Channel::Depth).setTo(cv::Scalar(0x7FFFFFFF), cvstream); + temp_.get<GpuMat>(Channel::Depth2).setTo(cv::Scalar(0x7FFFFFFF), cvstream); + temp_.get<GpuMat>(Channel::Colour).setTo(cv::Scalar(0.0f,0.0f,0.0f,0.0f), cvstream); + temp_.get<GpuMat>(Channel::Contribution).setTo(cv::Scalar(0.0f), cvstream); + out.get<GpuMat>(Channel::Depth).setTo(cv::Scalar(1000.0f), cvstream); + out.get<GpuMat>(Channel::Colour).setTo(cv::Scalar(76,76,76), cvstream); + + //LOG(INFO) << "Render ready: " << camera.width << "," << camera.height; + + temp_.createTexture<int>(Channel::Depth); + + // Render each camera into virtual view + for (size_t i=0; i<scene_->frames.size(); ++i) { + auto &f = scene_->frames[i]; + auto *s = scene_->sources[i]; + + if (f.empty(Channel::Depth + Channel::Colour)) { + LOG(ERROR) << "Missing required channel"; + continue; + } + + // Needs to create points channel first? + if (!f.hasChannel(Channel::Points)) { + //LOG(INFO) << "Creating points... " << s->parameters().width; + + auto &t = f.createTexture<float4>(Channel::Points, Format<float4>(f.get<GpuMat>(Channel::Colour).size())); + auto pose = MatrixConversion::toCUDA(s->getPose().cast<float>()); //.inverse()); + ftl::cuda::point_cloud(t, f.createTexture<float>(Channel::Depth), s->parameters(), pose, stream); + + //LOG(INFO) << "POINTS Added"; + } + + ftl::cuda::dibr_merge( + f.createTexture<float4>(Channel::Points), + temp_.getTexture<int>(Channel::Depth), + params, stream + ); + + //LOG(INFO) << "DIBR DONE"; + } + + // TODO: Add the depth splatting step.. + + temp_.createTexture<float4>(Channel::Colour); + temp_.createTexture<float>(Channel::Contribution); + + // Accumulate attribute contributions for each pixel + for (auto &f : scene_->frames) { + // Convert colour from BGR to BGRA if needed + if (f.get<GpuMat>(Channel::Colour).type() == CV_8UC3) { + // Convert to 4 channel colour + auto &col = f.get<GpuMat>(Channel::Colour); + GpuMat tmp(col.size(), CV_8UC4); + cv::cuda::swap(col, tmp); + cv::cuda::cvtColor(tmp,col, cv::COLOR_BGR2BGRA); + } + + ftl::cuda::dibr_attribute( + f.createTexture<uchar4>(Channel::Colour), + f.createTexture<float4>(Channel::Points), + temp_.getTexture<int>(Channel::Depth), + temp_.getTexture<float4>(Channel::Colour), + temp_.getTexture<float>(Channel::Contribution), + params, stream + ); + } + + // Normalise attribute contributions + ftl::cuda::dibr_normalise( + temp_.createTexture<float4>(Channel::Colour), + out.createTexture<uchar4>(Channel::Colour), + temp_.createTexture<float>(Channel::Contribution), + stream + ); + + Channel chan = src->getChannel(); + if (chan == Channel::Depth) { + temp_.get<GpuMat>(Channel::Depth).convertTo(out.get<GpuMat>(Channel::Depth), CV_32F, 1.0f / 1000.0f, cvstream); + } else if (chan == Channel::Energy) { + cv::cuda::swap(temp_.get<GpuMat>(Channel::Energy), out.create<GpuMat>(Channel::Energy)); + } else if (chan == Channel::Right) { + Eigen::Affine3f transform(Eigen::Translation3f(camera.baseline,0.0f,0.0f)); + Eigen::Matrix4f matrix = src->getPose().cast<float>() * transform.matrix(); + params.m_viewMatrix = MatrixConversion::toCUDA(matrix.inverse()); + params.m_viewMatrixInverse = MatrixConversion::toCUDA(matrix); + + // TODO: Repeat rendering process... + } + + return true; +} + +//void Splatter::setOutputDevice(int device) { +// device_ = device; +//} diff --git a/components/renderers/cpp/src/splatter.cu b/components/renderers/cpp/src/splatter.cu new file mode 100644 index 0000000000000000000000000000000000000000..c1b46fc1d5768dc15fe54bcf6e37f4655a076b56 --- /dev/null +++ b/components/renderers/cpp/src/splatter.cu @@ -0,0 +1,190 @@ +#include <ftl/render/splat_params.hpp> +#include "splatter_cuda.hpp" +#include <ftl/rgbd/camera.hpp> +#include <ftl/cuda_common.hpp> + +#include <ftl/cuda/weighting.hpp> + +#define T_PER_BLOCK 8 +#define UPSAMPLE_FACTOR 1.8f +#define WARP_SIZE 32 +#define DEPTH_THRESHOLD 0.05f +#define UPSAMPLE_MAX 60 +#define MAX_ITERATIONS 32 // Note: Must be multiple of 32 +#define SPATIAL_SMOOTHING 0.005f + +using ftl::cuda::TextureObject; +using ftl::render::SplatParams; + +/* + * Pass 1: Directly render each camera into virtual view but with no upsampling + * for sparse points. + */ + __global__ void dibr_merge_kernel(TextureObject<float4> points, TextureObject<int> depth, SplatParams params) { + const int x = blockIdx.x*blockDim.x + threadIdx.x; + const int y = blockIdx.y*blockDim.y + threadIdx.y; + + const float3 worldPos = make_float3(points.tex2D(x, y)); + if (worldPos.x == MINF) return; + + // Find the virtual screen position of current point + const float3 camPos = params.m_viewMatrix * worldPos; + if (camPos.z < params.camera.minDepth) return; + if (camPos.z > params.camera.maxDepth) return; + + const float d = camPos.z; + + const uint2 screenPos = params.camera.camToScreen<uint2>(camPos); + const unsigned int cx = screenPos.x; + const unsigned int cy = screenPos.y; + if (d > params.camera.minDepth && d < params.camera.maxDepth && cx < depth.width() && cy < depth.height()) { + // Transform estimated point to virtual cam space and output z + atomicMin(&depth(cx,cy), d * 1000.0f); + } +} + +void ftl::cuda::dibr_merge(TextureObject<float4> &points, TextureObject<int> &depth, SplatParams params, cudaStream_t stream) { + const dim3 gridSize((depth.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (depth.height() + T_PER_BLOCK - 1)/T_PER_BLOCK); + const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK); + + dibr_merge_kernel<<<gridSize, blockSize, 0, stream>>>(points, depth, params); + cudaSafeCall( cudaGetLastError() ); +} + +//============================================================================== + +__device__ inline float4 make_float4(const uchar4 &c) { + return make_float4(c.x,c.y,c.z,c.w); +} + + +#define ENERGY_THRESHOLD 0.1f +#define SMOOTHING_MULTIPLIER_A 10.0f // For surface search +#define SMOOTHING_MULTIPLIER_B 4.0f // For z contribution +#define SMOOTHING_MULTIPLIER_C 4.0f // For colour contribution + +/* + * Pass 2: Accumulate attribute contributions if the points pass a visibility test. + */ +__global__ void dibr_attribute_contrib_kernel( + TextureObject<uchar4> colour_in, // Original colour image + TextureObject<float4> points, // Original 3D points + TextureObject<int> depth_in, // Virtual depth map + TextureObject<float4> colour_out, // Accumulated output + //TextureObject<float4> normal_out, + TextureObject<float> contrib_out, + SplatParams params) { + + //const ftl::voxhash::DepthCameraCUDA &camera = c_cameras[cam]; + + const int tid = (threadIdx.x + threadIdx.y * blockDim.x); + //const int warp = tid / WARP_SIZE; + const int x = (blockIdx.x*blockDim.x + threadIdx.x) / WARP_SIZE; + const int y = blockIdx.y*blockDim.y + threadIdx.y; + + const float3 worldPos = make_float3(points.tex2D(x, y)); + //const float3 normal = make_float3(tex2D<float4>(camera.normal, x, y)); + if (worldPos.x == MINF) return; + //const float r = (camera.poseInverse * worldPos).z / camera.params.fx; + + const float3 camPos = params.m_viewMatrix * worldPos; + if (camPos.z < params.camera.minDepth) return; + if (camPos.z > params.camera.maxDepth) return; + const uint2 screenPos = params.camera.camToScreen<uint2>(camPos); + + const int upsample = 8; //min(UPSAMPLE_MAX, int((5.0f*r) * params.camera.fx / camPos.z)); + + // Not on screen so stop now... + if (screenPos.x >= depth_in.width() || screenPos.y >= depth_in.height()) return; + + // Is this point near the actual surface and therefore a contributor? + const float d = ((float)depth_in.tex2D((int)screenPos.x, (int)screenPos.y)/1000.0f); + //if (abs(d - camPos.z) > DEPTH_THRESHOLD) return; + + // TODO:(Nick) Should just one thread load these to shared mem? + const float4 colour = make_float4(colour_in.tex2D(x, y)); + //const float4 normal = tex2D<float4>(camera.normal, x, y); + + // Each thread in warp takes an upsample point and updates corresponding depth buffer. + const int lane = tid % WARP_SIZE; + for (int i=lane; i<upsample*upsample; i+=WARP_SIZE) { + const float u = (i % upsample) - (upsample / 2); + const float v = (i / upsample) - (upsample / 2); + + // Use the depth buffer to determine this pixels 3D position in camera space + const float d = ((float)depth_in.tex2D(screenPos.x+u, screenPos.y+v)/1000.0f); + const float3 nearest = params.camera.screenToCam((int)(screenPos.x+u),(int)(screenPos.y+v),d); + + // What is contribution of our current point at this pixel? + const float weight = ftl::cuda::spatialWeighting(length(nearest - camPos), SMOOTHING_MULTIPLIER_C*(nearest.z/params.camera.fx)); + if (screenPos.x+u < colour_out.width() && screenPos.y+v < colour_out.height() && weight > 0.0f) { // TODO: Use confidence threshold here + const float4 wcolour = colour * weight; + //const float4 wnormal = normal * weight; + + //printf("Z %f\n", d); + + // Add this points contribution to the pixel buffer + atomicAdd((float*)&colour_out(screenPos.x+u, screenPos.y+v), wcolour.x); + atomicAdd((float*)&colour_out(screenPos.x+u, screenPos.y+v)+1, wcolour.y); + atomicAdd((float*)&colour_out(screenPos.x+u, screenPos.y+v)+2, wcolour.z); + atomicAdd((float*)&colour_out(screenPos.x+u, screenPos.y+v)+3, wcolour.w); + //atomicAdd((float*)&normal_out(screenPos.x+u, screenPos.y+v), wnormal.x); + //atomicAdd((float*)&normal_out(screenPos.x+u, screenPos.y+v)+1, wnormal.y); + //atomicAdd((float*)&normal_out(screenPos.x+u, screenPos.y+v)+2, wnormal.z); + //atomicAdd((float*)&normal_out(screenPos.x+u, screenPos.y+v)+3, wnormal.w); + atomicAdd(&contrib_out(screenPos.x+u, screenPos.y+v), weight); + } + } +} + +void ftl::cuda::dibr_attribute( + TextureObject<uchar4> &colour_in, // Original colour image + TextureObject<float4> &points, // Original 3D points + TextureObject<int> &depth_in, // Virtual depth map + TextureObject<float4> &colour_out, // Accumulated output + //TextureObject<float4> normal_out, + TextureObject<float> &contrib_out, + SplatParams ¶ms, cudaStream_t stream) { + const dim3 gridSize((depth_in.width() + 2 - 1)/2, (depth_in.height() + T_PER_BLOCK - 1)/T_PER_BLOCK); + const dim3 blockSize(2*WARP_SIZE, T_PER_BLOCK); + + dibr_attribute_contrib_kernel<<<gridSize, blockSize, 0, stream>>>( + colour_in, + points, + depth_in, + colour_out, + contrib_out, + params + ); + cudaSafeCall( cudaGetLastError() ); +} + +//============================================================================== + +__global__ void dibr_normalise_kernel( + TextureObject<float4> colour_in, + TextureObject<uchar4> colour_out, + //TextureObject<float4> normals, + TextureObject<float> contribs) { + const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x; + const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y; + + if (x < colour_in.width() && y < colour_in.height()) { + const float4 colour = colour_in.tex2D((int)x,(int)y); + //const float4 normal = normals.tex2D((int)x,(int)y); + const float contrib = contribs.tex2D((int)x,(int)y); + + if (contrib > 0.0f) { + colour_out(x,y) = make_uchar4(colour.x / contrib, colour.y / contrib, colour.z / contrib, 0); + //normals(x,y) = normal / contrib; + } + } +} + +void ftl::cuda::dibr_normalise(TextureObject<float4> &colour_in, TextureObject<uchar4> &colour_out, TextureObject<float> &contribs, cudaStream_t stream) { + const dim3 gridSize((colour_in.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (colour_in.height() + T_PER_BLOCK - 1)/T_PER_BLOCK); + const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK); + + dibr_normalise_kernel<<<gridSize, blockSize, 0, stream>>>(colour_in, colour_out, contribs); + cudaSafeCall( cudaGetLastError() ); +} diff --git a/components/renderers/cpp/src/splatter_cuda.hpp b/components/renderers/cpp/src/splatter_cuda.hpp new file mode 100644 index 0000000000000000000000000000000000000000..8f6557b7f3e9d9ce99bceed615946c19b1afbec2 --- /dev/null +++ b/components/renderers/cpp/src/splatter_cuda.hpp @@ -0,0 +1,28 @@ +#ifndef _FTL_RECONSTRUCTION_SPLAT_CUDA_HPP_ +#define _FTL_RECONSTRUCTION_SPLAT_CUDA_HPP_ + +#include <ftl/cuda_common.hpp> +#include <ftl/render/splat_params.hpp> + +namespace ftl { +namespace cuda { + void dibr_merge(ftl::cuda::TextureObject<float4> &points, ftl::cuda::TextureObject<int> &depth, ftl::render::SplatParams params, cudaStream_t stream); + + void dibr_attribute( + ftl::cuda::TextureObject<uchar4> &colour_in, // Original colour image + ftl::cuda::TextureObject<float4> &points, // Original 3D points + ftl::cuda::TextureObject<int> &depth_in, // Virtual depth map + ftl::cuda::TextureObject<float4> &colour_out, // Accumulated output + //TextureObject<float4> normal_out, + ftl::cuda::TextureObject<float> &contrib_out, + ftl::render::SplatParams ¶ms, cudaStream_t stream); + + void dibr_normalise( + ftl::cuda::TextureObject<float4> &colour_in, + ftl::cuda::TextureObject<uchar4> &colour_out, + ftl::cuda::TextureObject<float> &contribs, + cudaStream_t stream); +} +} + +#endif // _FTL_RECONSTRUCTION_SPLAT_CUDA_HPP_ diff --git a/components/rgbd-sources/CMakeLists.txt b/components/rgbd-sources/CMakeLists.txt index 5983d1d940b8b420aa5fb8b6653da65f7f904b0a..2b056d009a73dd7ee82adaedbf03bb98194d636f 100644 --- a/components/rgbd-sources/CMakeLists.txt +++ b/components/rgbd-sources/CMakeLists.txt @@ -3,6 +3,8 @@ set(RGBDSRC src/local.cpp src/disparity.cpp src/source.cpp + src/frame.cpp + src/frameset.cpp src/stereovideo.cpp src/middlebury_source.cpp src/net.cpp @@ -14,6 +16,9 @@ set(RGBDSRC # src/algorithms/opencv_sgbm.cpp # src/algorithms/opencv_bm.cpp src/cb_segmentation.cpp + src/abr.cpp + src/offilter.cpp + src/virtual.cpp ) if (HAVE_REALSENSE) @@ -34,6 +39,7 @@ endif (LIBSGM_FOUND) if (CUDA_FOUND) list(APPEND RGBDSRC src/algorithms/disp2depth.cu + src/algorithms/offilter.cu # "src/algorithms/opencv_cuda_bm.cpp" # "src/algorithms/opencv_cuda_bp.cpp" # "src/algorithms/rtcensus.cu" @@ -61,7 +67,7 @@ set_property(TARGET ftlrgbd PROPERTY CUDA_SEPARABLE_COMPILATION OFF) endif() #target_include_directories(cv-node PUBLIC ${PROJECT_SOURCE_DIR}/include) -target_link_libraries(ftlrgbd ftlcommon ${OpenCV_LIBS} ${LIBSGM_LIBRARIES} ${CUDA_LIBRARIES} Eigen3::Eigen ${REALSENSE_LIBRARY} ftlnet ${LibArchive_LIBRARIES}) +target_link_libraries(ftlrgbd ftlcommon ${OpenCV_LIBS} ${LIBSGM_LIBRARIES} ${CUDA_LIBRARIES} Eigen3::Eigen ${REALSENSE_LIBRARY} ftlnet ${LibArchive_LIBRARIES} ftlcodecs) add_subdirectory(test) diff --git a/components/rgbd-sources/README.md b/components/rgbd-sources/README.md index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..90d1d3554c9c4b13044aa3fadc8d3637baa35205 100644 --- a/components/rgbd-sources/README.md +++ b/components/rgbd-sources/README.md @@ -0,0 +1,51 @@ +# RGB-Depth Sources + +This component provides a variety of sources for colour and depth images. These +include the following, but do not include virtual cameras: +* [Intel Realsense depth camera](src/realsense_source.hpp) +* [Stereo video](src/stereovideo.hpp) from two video capture cards using a disparity algorithm +* [Snapshots](src/snapshot_source.hpp) that were previously captured and saved to disk +* [Middlebury](src/middlebury_source.hpp) test datasets as available online +* [Streamed network sources](include/ftl/rgbd/streamer.hpp) from other nodes + +An RGB-D source is represented as a two image channel object that is generated +through a pipeline of processes usually consisting of the following (but not +all sources have all steps): +1. Frame capture from hardware +2. Internal buffer swapping if double-buffering is used +3. Retrieval, an IO blocking process of downloading images from devices +4. Computation of, for example, disparity and depth maps from colour images + +## Groups +A collection of sources may form a group that must be synchronised accurately +for reconstruction to take place. A [group class](include/ftl/rgbd/group.hpp) +coordinates the above 4 steps across all sources such that millisecond accurate +frames with timestamps can be buffered and collected together to be passed on to +the next stage. A [high precision timer](../common/cpp/include/ftl/timer.hpp) +is used to manage the pipeline. + +## Streaming +One possible use for a group of sources is to stream them over a network +where they may be re-grouped. A [streamer](include/ftl/rgbd/streamer.hpp) object +will receive sets of frames from a group object and then divide each image into +a number of chunks, each of which is compressed on a CPU core and sent to every +client who asks for them. Each client may ask for a different bitrate and +resolution so the streamer will also take care of this. The streamer class uses +the [ftl net library](../net/) for network communication. + +## Calibration +Some sources require a camera calibration step. Lens corrections an stereo +camera configurations are applied by the [calibrate class](src/calibrate.hpp). +Only stereo video sources currently need this step and the correction matrices +are calculated using a separate +[calibration app](../../application/calibration-multi/). There is also some +basic [colour correction](src/colour.hpp) that can be applied. + +## Disparity Algorithms +A few algorithms are included with the RGB-D sources for converting two +colour images into one colour and one depth image based upon the pixel shift +observed between the two images. [LibSGM](https://github.com/fixstars/libSGM) +is our algorithm of choice currently. Further pre and post filtering and +smoothing steps are applied, in particular an optical flow based temporal +smoothing across a number of frames to reduce flickering effects. This uses +[NVIDIA's Optical Flow SDK](https://developer.nvidia.com/opticalflow-sdk). \ No newline at end of file diff --git a/components/rgbd-sources/include/ftl/offilter.hpp b/components/rgbd-sources/include/ftl/offilter.hpp new file mode 100644 index 0000000000000000000000000000000000000000..6aece39aab241dfc7605dfb208d7d30ba6135509 --- /dev/null +++ b/components/rgbd-sources/include/ftl/offilter.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include <ftl/config.h> +#include <ftl/rgbd/frame.hpp> + +#ifdef HAVE_OPTFLOW +#include <ftl/cuda_util.hpp> +#include <opencv2/core.hpp> +#include <opencv2/core/cuda.hpp> +#include <opencv2/cudaoptflow.hpp> + +namespace ftl { +namespace rgbd { + +class OFDisparityFilter { +public: + OFDisparityFilter() : n_max_(0), threshold_(0.0) {} + OFDisparityFilter(cv::Size size, int n_frames, float threshold); + void filter(ftl::rgbd::Frame &frame, cv::cuda::Stream &stream); + +private: + int n_max_; + float threshold_; + + cv::cuda::GpuMat disp_old_; +}; + +} +} + +#endif // HAVE_OPTFLOW diff --git a/components/rgbd-sources/include/ftl/rgbd/camera.hpp b/components/rgbd-sources/include/ftl/rgbd/camera.hpp index 8acad9c41d6c4950398cde7d9f44ab8ae0bc08b6..e245be1ea44f3e7e8503f585bf2c387fef821a38 100644 --- a/components/rgbd-sources/include/ftl/rgbd/camera.hpp +++ b/components/rgbd-sources/include/ftl/rgbd/camera.hpp @@ -2,23 +2,69 @@ #ifndef _FTL_RGBD_CAMERA_PARAMS_HPP_ #define _FTL_RGBD_CAMERA_PARAMS_HPP_ +#include <vector_types.h> +#include <cuda_runtime.h> +#include <ftl/cuda_util.hpp> + namespace ftl{ namespace rgbd { -struct Camera { - double fx; - double fy; - double cx; - double cy; - unsigned int width; - unsigned int height; - double minDepth; - double maxDepth; - double baseline; - double doffs; +/** + * All properties associated with cameras. This structure is designed to + * operate on CPU and GPU. + */ +struct __align__(16) Camera { + double fx; // Focal length X + double fy; // Focal length Y (usually same as fx) + double cx; // Principle point Y + double cy; // Principle point Y + unsigned int width; // Pixel width + unsigned int height; // Pixel height + double minDepth; // Near clip in meters + double maxDepth; // Far clip in meters + double baseline; // For stereo pair + double doffs; // Disparity offset + + /** + * Convert camera coordinates into screen coordinates. + */ + template <typename T> __device__ T camToScreen(const float3 &pos) const; + + /** + * Convert screen plus depth into camera coordinates. + */ + __device__ float3 screenToCam(uint ux, uint uy, float depth) const; }; }; }; +// ---- IMPLEMENTATIONS -------------------------------------------------------- + +template <> __device__ +inline float2 ftl::rgbd::Camera::camToScreen<float2>(const float3 &pos) const { + return make_float2( + pos.x*fx/pos.z - cx, + pos.y*fy/pos.z - cy); +} + +template <> __device__ +inline int2 ftl::rgbd::Camera::camToScreen<int2>(const float3 &pos) const { + float2 pImage = camToScreen<float2>(pos); + return make_int2(pImage + make_float2(0.5f, 0.5f)); +} + +template <> __device__ +inline uint2 ftl::rgbd::Camera::camToScreen<uint2>(const float3 &pos) const { + int2 p = camToScreen<int2>(pos); + return make_uint2(p.x, p.y); +} + +__device__ +inline float3 ftl::rgbd::Camera::screenToCam(uint ux, uint uy, float depth) const { + const float x = ((float)ux+cx) / fx; + const float y = ((float)uy+cy) / fy; + return make_float3(depth*x, depth*y, depth); +} + #endif diff --git a/components/rgbd-sources/include/ftl/rgbd/channels.hpp b/components/rgbd-sources/include/ftl/rgbd/channels.hpp new file mode 100644 index 0000000000000000000000000000000000000000..9bf731a5319fa47c501a91e09f1e2acc48c5a4a8 --- /dev/null +++ b/components/rgbd-sources/include/ftl/rgbd/channels.hpp @@ -0,0 +1,124 @@ +#ifndef _FTL_RGBD_CHANNELS_HPP_ +#define _FTL_RGBD_CHANNELS_HPP_ + +#include <bitset> +#include <msgpack.hpp> + +namespace ftl { +namespace rgbd { + +enum struct Channel : int { + None = -1, + Colour = 0, // 8UC3 or 8UC4 + Left = 0, + Depth = 1, // 32S or 32F + Right = 2, // 8UC3 or 8UC4 + Colour2 = 2, + Disparity = 3, + Depth2 = 3, + Deviation = 4, + Normals = 5, // 32FC4 + Points = 6, // 32FC4 + Confidence = 7, // 32F + Contribution = 7, // 32F + EnergyVector, // 32FC4 + Flow, // 32F + Energy, // 32F + LeftGray, + RightGray, + Overlay1 +}; + +class Channels { + public: + + class iterator { + public: + iterator(const Channels &c, unsigned int ix) : channels_(c), ix_(ix) { } + iterator operator++(); + iterator operator++(int junk); + inline ftl::rgbd::Channel operator*() { return static_cast<Channel>(static_cast<int>(ix_)); } + //ftl::rgbd::Channel operator->() { return ptr_; } + inline bool operator==(const iterator& rhs) { return ix_ == rhs.ix_; } + inline bool operator!=(const iterator& rhs) { return ix_ != rhs.ix_; } + private: + const Channels &channels_; + unsigned int ix_; + }; + + inline Channels() { mask = 0; } + inline explicit Channels(unsigned int m) { mask = m; } + inline explicit Channels(Channel c) { mask = (c == Channel::None) ? 0 : 0x1 << static_cast<unsigned int>(c); } + inline Channels &operator=(Channel c) { mask = (c == Channel::None) ? 0 : 0x1 << static_cast<unsigned int>(c); return *this; } + inline Channels operator|(Channel c) const { return (c == Channel::None) ? Channels(mask) : Channels(mask | (0x1 << static_cast<unsigned int>(c))); } + inline Channels operator+(Channel c) const { return (c == Channel::None) ? Channels(mask) : Channels(mask | (0x1 << static_cast<unsigned int>(c))); } + inline Channels &operator|=(Channel c) { mask |= (c == Channel::None) ? 0 : (0x1 << static_cast<unsigned int>(c)); return *this; } + inline Channels &operator+=(Channel c) { mask |= (c == Channel::None) ? 0 : (0x1 << static_cast<unsigned int>(c)); return *this; } + inline Channels &operator-=(Channel c) { mask &= ~((c == Channel::None) ? 0 : (0x1 << static_cast<unsigned int>(c))); return *this; } + inline Channels &operator+=(unsigned int c) { mask |= (0x1 << c); return *this; } + inline Channels &operator-=(unsigned int c) { mask &= ~(0x1 << c); return *this; } + + inline bool has(Channel c) const { + return (c == Channel::None) ? true : mask & (0x1 << static_cast<unsigned int>(c)); + } + + inline bool has(unsigned int c) const { + return mask & (0x1 << c); + } + + inline iterator begin() { return iterator(*this, 0); } + inline iterator end() { return iterator(*this, 32); } + + inline operator unsigned int() { return mask; } + inline operator bool() { return mask > 0; } + inline operator Channel() { + if (mask == 0) return Channel::None; + int ix = 0; + int tmask = mask; + while (!(tmask & 0x1) && ++ix < 32) tmask >>= 1; + return static_cast<Channel>(ix); + } + + inline size_t count() { return std::bitset<32>(mask).count(); } + inline void clear() { mask = 0; } + + static const size_t kMax = 32; + + static Channels All(); + + private: + unsigned int mask; +}; + +inline Channels::iterator Channels::iterator::operator++() { Channels::iterator i = *this; while (++ix_ < 32 && !channels_.has(ix_)); return i; } +inline Channels::iterator Channels::iterator::operator++(int junk) { while (++ix_ < 32 && !channels_.has(ix_)); return *this; } + +inline Channels Channels::All() { + return Channels(0xFFFFFFFFu); +} + +static const Channels kNoChannels; +static const Channels kAllChannels(0xFFFFFFFFu); + +inline bool isFloatChannel(ftl::rgbd::Channel chan) { + switch (chan) { + case Channel::Depth : + case Channel::Energy : return true; + default : return false; + } +} + +} +} + +MSGPACK_ADD_ENUM(ftl::rgbd::Channel); + +inline ftl::rgbd::Channels operator|(ftl::rgbd::Channel a, ftl::rgbd::Channel b) { + return ftl::rgbd::Channels(a) | b; +} + +inline ftl::rgbd::Channels operator+(ftl::rgbd::Channel a, ftl::rgbd::Channel b) { + return ftl::rgbd::Channels(a) | b; +} + +#endif // _FTL_RGBD_CHANNELS_HPP_ diff --git a/components/rgbd-sources/include/ftl/rgbd/detail/abr.hpp b/components/rgbd-sources/include/ftl/rgbd/detail/abr.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b3d809784abdf9a3f1bdb362c2dcc3a88b1a9e3e --- /dev/null +++ b/components/rgbd-sources/include/ftl/rgbd/detail/abr.hpp @@ -0,0 +1,121 @@ +#ifndef _FTL_RGBD_ABR_HPP_ +#define _FTL_RGBD_ABR_HPP_ + +#include <ftl/rgbd/detail/netframe.hpp> +#include <cstdint> + +namespace ftl { +namespace rgbd { +namespace detail { + +static const float kAspectRatio = 1.777778f; + +enum codec_t { + kCodecJPG = 0, + kCodecPNG +}; + +struct BitrateSetting { + int colour_res; + int depth_res; + int colour_qual; + int depth_qual; + codec_t colour_codec; + codec_t depth_codec; + int block_count_x; + + /*int width; + int height; + int jpg_quality; + int png_compression; + codec_t colour_codec; + codec_t depth_codec; + int chunking;*/ +}; + +static const BitrateSetting bitrate_settings[] = { + 1080, 1080, 95, 1, kCodecJPG, kCodecPNG, 4, + 1080, 720, 95, 1, kCodecJPG, kCodecPNG, 4, + 720, 720, 95, 1, kCodecJPG, kCodecPNG, 4, + 720, 576, 95, 5, kCodecJPG, kCodecPNG, 4, + 576, 576, 95, 5, kCodecJPG, kCodecPNG, 4, + 576, 480, 95, 5, kCodecJPG, kCodecPNG, 2, + 480, 480, 95, 5, kCodecJPG, kCodecPNG, 2, + 480, 360, 95, 9, kCodecJPG, kCodecPNG, 2, + 360, 360, 95, 9, kCodecJPG, kCodecPNG, 2, + 360, 360, 50, 9, kCodecJPG, kCodecPNG, 2 +}; + +/*static const BitrateSetting bitrate_settings[] = { + 1920, 1080, 95, 1, kCodecJPG, kCodecPNG, 4, // ? + 1280, 720, 95, 1, kCodecJPG, kCodecPNG, 4, // ~96Mbps + 1024, 576, 95, 5, kCodecJPG, kCodecPNG, 3, // ~62Mbps + 854, 480, 95, 5, kCodecJPG, kCodecPNG, 3, // ~48Mbps + 640, 360, 95, 9, kCodecJPG, kCodecPNG, 2, // ~31Mbps + 640, 360, 75, 9, kCodecJPG, kCodecPNG, 2, // ~25Mbps + 640, 360, 65, 9, kCodecJPG, kCodecPNG, 2, // ~24Mbps + 640, 360, 50, 9, kCodecJPG, kCodecPNG, 2, // ~23Mbps + 320, 160, 95, 9, kCodecJPG, kCodecPNG, 2, // ~10Mbps + 320, 160, 75, 9, kCodecJPG, kCodecPNG, 2 // ~8Mbps +};*/ + +typedef unsigned int bitrate_t; + +static const bitrate_t kBitrateBest = 0; +static const bitrate_t kBitrateWorst = 9; + +/** + * Adaptive Bitrate Controller to monitor and decide on a client streams + * bitrate. The basics of our approach are that if transmission latency exceeds + * some proportion of the frame time then mark it as a slow frame. Similarly if + * transmission latency falls below a proportion of frame time then mark it as + * a fast frame. If the net frame status is slow (thresholded) then reduce + * bitrate, if the net status is fast then increase bitrate. + */ +class ABRController { + public: + ABRController(); + ~ABRController(); + + /** + * From a received frame, select a bitrate based upon actual and required + * bitrate as well as past frames. + */ + bitrate_t selectBitrate(const ftl::rgbd::detail::NetFrame &); + + /** + * Called to tell the controller the new bitrate is now in use by the stream + */ + void notifyChanged(); + + void setMaximumBitrate(bitrate_t); + void setMinimumBitrate(bitrate_t); + + static const ftl::rgbd::detail::BitrateSetting &getBitrateInfo(bitrate_t b); + static int getColourWidth(bitrate_t b); + static int getDepthWidth(bitrate_t b); + static int getColourHeight(bitrate_t b); + static int getDepthHeight(bitrate_t b); + static int getBlockCountX(bitrate_t b); + static int getBlockCountY(bitrate_t b); + static int getBlockCount(bitrate_t b); + static int getColourQuality(bitrate_t b); + static int getDepthQuality(bitrate_t b); + + private: + unsigned int down_log_; // Bit log of delayed frames + unsigned int up_log_; // Bit log of fast frames + int64_t last_br_change_; // Time of last adaptive change + float down_threshold_; // Proportion of min bitrate before reduction + float up_threshold_; // Proportion of min bitrate before increase + bitrate_t bitrate_; + bool enabled_; + bitrate_t max_; + bitrate_t min_; +}; + +} +} +} + +#endif // _FTL_RGBD_ABR_HPP_ diff --git a/components/rgbd-sources/include/ftl/rgbd/detail/netframe.hpp b/components/rgbd-sources/include/ftl/rgbd/detail/netframe.hpp new file mode 100644 index 0000000000000000000000000000000000000000..f01154e12c876292f724f072fbaa69812e1b29bb --- /dev/null +++ b/components/rgbd-sources/include/ftl/rgbd/detail/netframe.hpp @@ -0,0 +1,50 @@ +#ifndef _FTL_RGBD_NETFRAME_HPP_ +#define _FTL_RGBD_NETFRAME_HPP_ + +#include <cstdint> +#include <vector> +#include <ftl/rgbd/source.hpp> + +namespace ftl { +namespace rgbd { +namespace detail { + +/** + * Buffers for a single frame as it is being received over the network. + * Also maintains statistics about the frame transmission for later analysis. + */ +struct NetFrame { + cv::Mat channel1; + cv::Mat channel2; + volatile int64_t timestamp; + std::atomic<int> chunk_count; + int chunk_total; + std::atomic<int> tx_size; + int64_t tx_latency; + MUTEX mtx; +}; + +/** + * Manage multiple frames with their timestamp as an identifier. Once a frame + * is completed it should be freed immediately from the queue for reuse. It + * is not the job of this queue to buffer frames for longer periods, see Group + * for this functionality. This queue is only to manage chunk ordering problems. + */ +class NetFrameQueue { + public: + explicit NetFrameQueue(int size=2); + ~NetFrameQueue(); + + NetFrame &getFrame(int64_t ts, const cv::Size &, int c1type, int c2type); + void freeFrame(NetFrame &); + + private: + std::vector<NetFrame> frames_; + MUTEX mtx_; +}; + +} +} +} + +#endif // _FTL_RGBD_NETFRAME_HPP_ diff --git a/components/rgbd-sources/include/ftl/rgbd/detail/source.hpp b/components/rgbd-sources/include/ftl/rgbd/detail/source.hpp index 7557fecb3da30af7a818823904a9b71bba3bc057..e98ff38aacd4cf0731ef96b67ecc85732d4c0c7f 100644 --- a/components/rgbd-sources/include/ftl/rgbd/detail/source.hpp +++ b/components/rgbd-sources/include/ftl/rgbd/detail/source.hpp @@ -2,33 +2,16 @@ #define _FTL_RGBD_DETAIL_SOURCE_HPP_ #include <Eigen/Eigen> +#include <ftl/cuda_util.hpp> #include <opencv2/opencv.hpp> #include <ftl/rgbd/camera.hpp> +#include <ftl/rgbd/frame.hpp> namespace ftl{ namespace rgbd { class Source; -typedef unsigned int channel_t; - -static const channel_t kChanNone = 0; -static const channel_t kChanLeft = 0x0001; -static const channel_t kChanDepth = 0x0002; -static const channel_t kChanRight = 0x0004; -static const channel_t kChanDisparity = 0x0008; -static const channel_t kChanDeviation = 0x0010; -static const channel_t kChanNormals = 0x0020; -static const channel_t kChanConfidence = 0x0040; -static const channel_t kChanFlow = 0x0080; - -static const channel_t kChanOverlay1 = 0x1000; - -inline bool isFloatChannel(ftl::rgbd::channel_t chan) { - return (chan == ftl::rgbd::kChanDepth); -} - - typedef unsigned int capability_t; static const capability_t kCapMovable = 0x0001; // A movable virtual cam @@ -49,14 +32,31 @@ class Source { virtual ~Source() {} /** + * Perform hardware data capture. + */ + virtual bool capture(int64_t ts)=0; + + /** + * Perform IO operation to get the data. + */ + virtual bool retrieve()=0; + + /** + * Do any processing from previously captured frames... * @param n Number of frames to request in batch. Default -1 means automatic (10) * @param b Bit rate setting. -1 = automatic, 0 = best quality, 9 = lowest quality */ - virtual bool grab(int n, int b)=0; + virtual bool compute(int n, int b)=0; + + /** + * Between frames, or before next frame, do any buffer swapping operations. + */ + virtual void swap() {} + virtual bool isReady() { return false; }; virtual void setPose(const Eigen::Matrix4d &pose) { }; - virtual Camera parameters(channel_t) { return params_; }; + virtual Camera parameters(ftl::rgbd::Channel) { return params_; }; protected: capability_t capabilities_; diff --git a/components/rgbd-sources/include/ftl/rgbd/format.hpp b/components/rgbd-sources/include/ftl/rgbd/format.hpp new file mode 100644 index 0000000000000000000000000000000000000000..2e86aaf96a5f64d13065385ddee360fd80ca9f8c --- /dev/null +++ b/components/rgbd-sources/include/ftl/rgbd/format.hpp @@ -0,0 +1,52 @@ +#ifndef _FTL_RGBD_FORMAT_HPP_ +#define _FTL_RGBD_FORMAT_HPP_ + +#include <opencv2/core.hpp> +#include <opencv2/core/cuda.hpp> +#include <ftl/cuda_util.hpp> +#include <ftl/codecs/bitrates.hpp> +#include <ftl/traits.hpp> + +namespace ftl { +namespace rgbd { + +struct FormatBase { + FormatBase(size_t w, size_t h, int t) : width(w), height(h), cvType(t) {} + + size_t width; // Width in pixels + size_t height; // Height in pixels + int cvType; // OpenCV Mat type + + inline bool empty() const { return width == 0 || height == 0; } + inline cv::Size size() const { return cv::Size(width, height); } +}; + +template <typename T> +struct Format : public ftl::rgbd::FormatBase { + Format() : FormatBase(0,0,0) {} + + Format(size_t w, size_t h) : FormatBase( + w, h, ftl::traits::OpenCVType<T>::value) {} + + explicit Format(ftl::codecs::definition_t d) : FormatBase( + ftl::codecs::getWidth(d), + ftl::codecs::getHeight(d), + ftl::traits::OpenCVType<T>::value) {} + + explicit Format(const cv::Size &s) : FormatBase( + s.width, + s.height, + ftl::traits::OpenCVType<T>::value) {} + + explicit Format(const cv::InputArray &a) : FormatBase( + a.cols, + a.rows, + ftl::traits::OpenCVType<T>::value) { + CHECK(cvType == a.type()); + } +}; + +} +} + +#endif // _FTL_RGBD_FORMAT_HPP_ diff --git a/components/rgbd-sources/include/ftl/rgbd/frame.hpp b/components/rgbd-sources/include/ftl/rgbd/frame.hpp new file mode 100644 index 0000000000000000000000000000000000000000..252ff271de36336938d51d616cdd5a9e87d52187 --- /dev/null +++ b/components/rgbd-sources/include/ftl/rgbd/frame.hpp @@ -0,0 +1,257 @@ +#pragma once +#ifndef _FTL_RGBD_FRAME_HPP_ +#define _FTL_RGBD_FRAME_HPP_ + +#include <ftl/configuration.hpp> +#include <ftl/exception.hpp> +#include <opencv2/core.hpp> +#include <opencv2/core/cuda.hpp> +#include <opencv2/core/cuda_stream_accessor.hpp> + +#include <ftl/rgbd/channels.hpp> +#include <ftl/rgbd/format.hpp> +#include <ftl/codecs/bitrates.hpp> + +#include <ftl/cuda_common.hpp> + +#include <type_traits> +#include <array> + +namespace ftl { +namespace rgbd { + +// TODO: interpolation for scaling depends on channel type; +// NN for depth/disparity/optflow, linear/cubic/etc. for RGB + +class Frame; +class Source; + +/** + * Manage a set of image channels corresponding to a single camera frame. + */ +class Frame { +public: + Frame() : src_(nullptr) {} + explicit Frame(ftl::rgbd::Source *src) : src_(src) {} + + inline ftl::rgbd::Source *source() const { return src_; } + + // Prevent frame copy, instead use a move. + //Frame(const Frame &)=delete; + //Frame &operator=(const Frame &)=delete; + + void download(ftl::rgbd::Channel c, cv::cuda::Stream stream); + void upload(ftl::rgbd::Channel c, cv::cuda::Stream stream); + void download(ftl::rgbd::Channels c, cv::cuda::Stream stream); + void upload(ftl::rgbd::Channels c, cv::cuda::Stream stream); + + inline void download(ftl::rgbd::Channel c, cudaStream_t stream=0) { download(c, cv::cuda::StreamAccessor::wrapStream(stream)); }; + inline void upload(ftl::rgbd::Channel c, cudaStream_t stream=0) { upload(c, cv::cuda::StreamAccessor::wrapStream(stream)); }; + inline void download(ftl::rgbd::Channels c, cudaStream_t stream=0) { download(c, cv::cuda::StreamAccessor::wrapStream(stream)); }; + inline void upload(ftl::rgbd::Channels c, cudaStream_t stream=0) { upload(c, cv::cuda::StreamAccessor::wrapStream(stream)); }; + + /** + * Perform a buffer swap of the selected channels. This is intended to be + * a copy from `this` to the passed frame object but by buffer swap + * instead of memory copy, meaning `this` may become invalid afterwards. + */ + void swapTo(ftl::rgbd::Channels, Frame &); + + /** + * Create a channel with a given format. This will discard any existing + * data associated with the channel and ensure all data structures and + * memory allocations match the new format. + */ + template <typename T> T &create(ftl::rgbd::Channel c, const ftl::rgbd::FormatBase &f); + + /** + * Create a channel but without any format. + */ + template <typename T> T &create(ftl::rgbd::Channel c); + + /** + * Create a CUDA texture object for a channel. This version takes a format + * argument to also create (or recreate) the associated GpuMat. + */ + template <typename T> + ftl::cuda::TextureObject<T> &createTexture(ftl::rgbd::Channel c, const ftl::rgbd::Format<T> &f); + + /** + * Create a CUDA texture object for a channel. With this version the GpuMat + * must already exist and be of the correct type. + */ + template <typename T> + ftl::cuda::TextureObject<T> &createTexture(ftl::rgbd::Channel c); + + /** + * Reset all channels without releasing memory. + */ + void reset(); + + bool empty(ftl::rgbd::Channels c); + + inline bool empty(ftl::rgbd::Channel c) { + auto &m = _get(c); + return !hasChannel(c) || (m.host.empty() && m.gpu.empty()); + } + + /** + * Is there valid data in channel (either host or gpu). + */ + inline bool hasChannel(ftl::rgbd::Channel channel) const { + return channels_.has(channel); + } + + inline ftl::rgbd::Channels getChannels() const { return channels_; } + + /** + * Is the channel data currently located on GPU. This also returns false if + * the channel does not exist. + */ + inline bool isGPU(ftl::rgbd::Channel channel) const { + return channels_.has(channel) && gpu_.has(channel); + } + + /** + * Is the channel data currently located on CPU memory. This also returns + * false if the channel does not exist. + */ + inline bool isCPU(ftl::rgbd::Channel channel) const { + return channels_.has(channel) && !gpu_.has(channel); + } + + /** + * Method to get reference to the channel content. + * @param Channel type + * @return Const reference to channel data + * + * Result is valid only if hasChannel() is true. Host/Gpu transfer is + * performed, if necessary, but with a warning since an explicit upload or + * download should be used. + */ + template <typename T> const T& get(ftl::rgbd::Channel channel) const; + + /** + * Method to get reference to the channel content. + * @param Channel type + * @return Reference to channel data + * + * Result is valid only if hasChannel() is true. Host/Gpu transfer is + * performed, if necessary, but with a warning since an explicit upload or + * download should be used. + */ + template <typename T> T& get(ftl::rgbd::Channel channel); + + template <typename T> const ftl::cuda::TextureObject<T> &getTexture(ftl::rgbd::Channel) const; + template <typename T> ftl::cuda::TextureObject<T> &getTexture(ftl::rgbd::Channel); + +private: + struct ChannelData { + cv::Mat host; + cv::cuda::GpuMat gpu; + ftl::cuda::TextureObjectBase tex; + }; + + std::array<ChannelData, Channels::kMax> data_; + + ftl::rgbd::Channels channels_; // Does it have a channel + ftl::rgbd::Channels gpu_; // Is the channel on a GPU + + ftl::rgbd::Source *src_; + + inline ChannelData &_get(ftl::rgbd::Channel c) { return data_[static_cast<unsigned int>(c)]; } + inline const ChannelData &_get(ftl::rgbd::Channel c) const { return data_[static_cast<unsigned int>(c)]; } +}; + +// Specialisations + +template<> const cv::Mat& Frame::get(ftl::rgbd::Channel channel) const; +template<> const cv::cuda::GpuMat& Frame::get(ftl::rgbd::Channel channel) const; +template<> cv::Mat& Frame::get(ftl::rgbd::Channel channel); +template<> cv::cuda::GpuMat& Frame::get(ftl::rgbd::Channel channel); + +template <> cv::Mat &Frame::create(ftl::rgbd::Channel c, const ftl::rgbd::FormatBase &); +template <> cv::cuda::GpuMat &Frame::create(ftl::rgbd::Channel c, const ftl::rgbd::FormatBase &); +template <> cv::Mat &Frame::create(ftl::rgbd::Channel c); +template <> cv::cuda::GpuMat &Frame::create(ftl::rgbd::Channel c); + +template <typename T> +ftl::cuda::TextureObject<T> &Frame::getTexture(ftl::rgbd::Channel c) { + if (!channels_.has(c)) throw ftl::exception("Texture channel does not exist"); + if (!gpu_.has(c)) throw ftl::exception("Texture channel is not on GPU"); + + auto &m = _get(c); + + if (m.tex.cvType() != ftl::traits::OpenCVType<T>::value || m.tex.width() != m.gpu.cols || m.tex.height() != m.gpu.rows || m.gpu.type() != m.tex.cvType()) { + throw ftl::exception("Texture has not been created properly for this channel"); + } + + return ftl::cuda::TextureObject<T>::cast(m.tex); +} + +template <typename T> +ftl::cuda::TextureObject<T> &Frame::createTexture(ftl::rgbd::Channel c, const ftl::rgbd::Format<T> &f) { + if (!channels_.has(c)) channels_ += c; + if (!gpu_.has(c)) gpu_ += c; + + auto &m = _get(c); + + if (f.empty()) { + throw ftl::exception("createTexture needs a non-empty format"); + } else { + m.gpu.create(f.size(), f.cvType); + } + + if (m.gpu.type() != ftl::traits::OpenCVType<T>::value) { + throw ftl::exception("Texture type does not match underlying data type"); + } + + // TODO: Check tex cvType + + if (m.tex.devicePtr() == nullptr) { + LOG(INFO) << "Creating texture object"; + m.tex = ftl::cuda::TextureObject<T>(m.gpu); + } else if (m.tex.cvType() != ftl::traits::OpenCVType<T>::value || m.tex.width() != m.gpu.cols || m.tex.height() != m.gpu.rows) { + LOG(INFO) << "Recreating texture object"; + m.tex.free(); + m.tex = ftl::cuda::TextureObject<T>(m.gpu); + } + + return ftl::cuda::TextureObject<T>::cast(m.tex); +} + +template <typename T> +ftl::cuda::TextureObject<T> &Frame::createTexture(ftl::rgbd::Channel c) { + if (!channels_.has(c)) throw ftl::exception("createTexture needs a format if the channel does not exist"); + + auto &m = _get(c); + + if (isCPU(c) && !m.host.empty()) { + m.gpu.create(m.host.size(), m.host.type()); + // TODO: Should this upload to GPU or not? + //gpu_ += c; + } else if (isCPU(c) || (isGPU(c) && m.gpu.empty())) { + throw ftl::exception("createTexture needs a format if no memory is allocated"); + } + + if (m.gpu.type() != ftl::traits::OpenCVType<T>::value) { + throw ftl::exception("Texture type does not match underlying data type"); + } + + // TODO: Check tex cvType + + if (m.tex.devicePtr() == nullptr) { + LOG(INFO) << "Creating texture object"; + m.tex = ftl::cuda::TextureObject<T>(m.gpu); + } else if (m.tex.cvType() != ftl::traits::OpenCVType<T>::value || m.tex.width() != m.gpu.cols || m.tex.height() != m.gpu.rows || m.tex.devicePtr() != m.gpu.data) { + m.tex.free(); + m.tex = ftl::cuda::TextureObject<T>(m.gpu); + } + + return ftl::cuda::TextureObject<T>::cast(m.tex); +} + +} +} + +#endif // _FTL_RGBD_FRAME_HPP_ \ No newline at end of file diff --git a/components/rgbd-sources/include/ftl/rgbd/frameset.hpp b/components/rgbd-sources/include/ftl/rgbd/frameset.hpp new file mode 100644 index 0000000000000000000000000000000000000000..2fa39e2eacf19339860e98fa98df44f687ac64c7 --- /dev/null +++ b/components/rgbd-sources/include/ftl/rgbd/frameset.hpp @@ -0,0 +1,37 @@ +#ifndef _FTL_RGBD_FRAMESET_HPP_ +#define _FTL_RGBD_FRAMESET_HPP_ + +#include <ftl/threads.hpp> +#include <ftl/rgbd/frame.hpp> + +#include <opencv2/opencv.hpp> +#include <vector> + +namespace ftl { +namespace rgbd { + +class Source; + +/** + * Represents a set of synchronised frames, each with two channels. This is + * used to collect all frames from multiple computers that have the same + * timestamp. + */ +struct FrameSet { + int64_t timestamp; // Millisecond timestamp of all frames + std::vector<Source*> sources; // All source objects involved. + std::vector<ftl::rgbd::Frame> frames; + std::atomic<int> count; // Number of valid frames + std::atomic<unsigned int> mask; // Mask of all sources that contributed + bool stale; // True if buffers have been invalidated + SHARED_MUTEX mtx; + + void upload(ftl::rgbd::Channels, cudaStream_t stream=0); + void download(ftl::rgbd::Channels, cudaStream_t stream=0); + void swapTo(ftl::rgbd::FrameSet &); +}; + +} +} + +#endif // _FTL_RGBD_FRAMESET_HPP_ diff --git a/components/rgbd-sources/include/ftl/rgbd/group.hpp b/components/rgbd-sources/include/ftl/rgbd/group.hpp index 1fc92a772e4edb7ce21590429aa08c7f602ed2bd..0ded29e80b7d2fa01ad656c2fbb3b8865b726a70 100644 --- a/components/rgbd-sources/include/ftl/rgbd/group.hpp +++ b/components/rgbd-sources/include/ftl/rgbd/group.hpp @@ -1,7 +1,11 @@ #ifndef _FTL_RGBD_GROUP_HPP_ #define _FTL_RGBD_GROUP_HPP_ +#include <ftl/cuda_util.hpp> #include <ftl/threads.hpp> +#include <ftl/timer.hpp> +#include <ftl/rgbd/frame.hpp> +#include <ftl/rgbd/frameset.hpp> #include <opencv2/opencv.hpp> #include <vector> @@ -11,37 +15,99 @@ namespace rgbd { class Source; -struct FrameSet { - int64_t timestamp; - std::vector<Source*> sources; - std::vector<cv::Mat> channel1; - std::vector<cv::Mat> channel2; - int count; - unsigned int mask; -}; - +// Allows a latency of 20 frames maximum static const size_t kFrameBufferSize = 20; +/** + * Manage a group of RGB-D sources to obtain synchronised sets of frames from + * those sources. The Group class provides a synchronised callback mechanism + * that uses the high precision timer to ensure that it is called once per + * frame. The callback is not called if the frameset is not completed or + * is unavailable for some other reason. By default if the required frame is + * not available but there is an older frame available that has not been used + * then it will be used. This can be disabled. It is also possible to allow + * incomplete frames to be used, but this is disabled by default. + */ class Group { public: Group(); ~Group(); + /** + * Give this group a name for logging purposes. + */ + void setName(const std::string &name); + + /** + * Add a new source to the group. Framesets generated prior to the source + * being added will still be valid and will not contain a frame from this + * source. Sets generated after addition will require a frame from this + * source. + */ void addSource(ftl::rgbd::Source *); - void sync(int N=-1, int B=-1); - void sync(std::function<bool(const FrameSet &)>); + /** + * Add another group to this one. All sources in the other group are made + * available to this group in a synchronised way. There is additional + * overhead in supporting this as additional data copies are required + * internally for all the source frames. + */ + void addGroup(ftl::rgbd::Group *); + + /** + * Provide a function to be called once per frame with a valid frameset + * at the specified latency. The function may not be called under certain + * conditions (missing frameset). No guarantee is made about the timing + * accuracy of the call, it should be close to the frame point. This + * function may or may not block. It is intended that the buffers within + * the frameset are swapped during the function call, meaning that the + * frameset data is no longer valid upon returning. + */ + void sync(std::function<bool(FrameSet &)>); + + /** @deprecated */ + //bool getFrames(FrameSet &, bool complete=false); - bool getFrames(FrameSet &, bool complete=false); + /** To be deprecated in favour of ftl::timer::setInterval. + */ + //void setFPS(int fps); + + /** + * Set the minimum number of frames latency. The latency is from the most + * recent frame obtained, meaning that the timestamp of the input frames is + * the reference point, this may already be several frames old. Latency + * does not correspond to actual current time. + */ + void setLatency(int frames) { latency_ = frames; } + + void stop() {} private: std::vector<FrameSet> framesets_; std::vector<Source*> sources_; size_t head_; - std::function<bool(const FrameSet &)> callback_; + std::function<bool(FrameSet &)> callback_; MUTEX mutex_; + int mspf_; + int latency_; + int64_t last_ts_; + std::atomic<int> jobs_; + volatile bool skip_; + ftl::timer::TimerHandle cap_id_; + ftl::timer::TimerHandle swap_id_; + ftl::timer::TimerHandle main_id_; + std::string name_; + /* Insert a new frameset into the buffer, along with all intermediate + * framesets between the last in buffer and the new one. + */ void _addFrameset(int64_t timestamp); + + void _retrieveJob(ftl::rgbd::Source *); + void _computeJob(ftl::rgbd::Source *); + + /* Find a frameset with given latency in frames. */ + ftl::rgbd::FrameSet *_getFrameset(int f); }; } diff --git a/components/rgbd-sources/include/ftl/rgbd/snapshot.hpp b/components/rgbd-sources/include/ftl/rgbd/snapshot.hpp index edec6150217a6655021b94d49ef36d6018d87374..f9bb39756362c344877df441a445a07f3e5eab37 100644 --- a/components/rgbd-sources/include/ftl/rgbd/snapshot.hpp +++ b/components/rgbd-sources/include/ftl/rgbd/snapshot.hpp @@ -27,16 +27,26 @@ public: explicit SnapshotWriter(const std::string &filename); ~SnapshotWriter(); - bool addCameraParams(const std::string &name, const Eigen::Matrix4d &pose, const ftl::rgbd::Camera ¶ms); - bool addCameraRGBD(const std::string &name, const cv::Mat &rgb, const cv::Mat &depth); - bool addMat(const std::string &name, const cv::Mat &mat, const std::string &format="tiff"); - bool addEigenMatrix4d(const std::string &name, const Eigen::Matrix4d &m, const std::string &format="pfm"); + void addSource(const std::string &id, const ftl::rgbd::Camera ¶ms, const Eigen::Matrix4d &extrinsic); + void addSource(const std::string &id, const std::vector<double> ¶ms, const cv::Mat &extrinsic); + bool addRGBD(size_t source, const cv::Mat &rgb, const cv::Mat &depth, uint64_t time=0); + + bool addMat(const std::string &name, const cv::Mat &mat, const std::string &format, const std::vector<int> ¶ms); bool addFile(const std::string &name, const std::vector<uchar> &buf); bool addFile(const std::string &name, const uchar *buf, const size_t len); + + void writeIndex(); private: - struct archive *archive_; - struct archive_entry *entry_; + std::vector<std::string> sources_; + std::vector<std::vector<double>> params_; + std::vector<cv::Mat> extrinsic_; + std::vector<size_t> frame_idx_; + std::vector<std::vector<std::string>> fname_rgb_; + std::vector<std::vector<std::string>> fname_depth_; + + struct archive *archive_ = nullptr; + struct archive_entry *entry_ = nullptr; }; class SnapshotStreamWriter { @@ -59,35 +69,47 @@ private: void run(); }; -struct SnapshotEntry { - long t; - cv::Mat rgb; - cv::Mat depth; - Eigen::Matrix4d pose; - ftl::rgbd::Camera params; - uint status; - SnapshotEntry() : status(1+2+4+8) {}; +class Snapshot { +public: + size_t getSourcesCount(); + size_t getFramesCount(); + + std::string getSourceURI(size_t camera); + ftl::rgbd::Camera getParameters(size_t camera); + void getPose(size_t camera, cv::Mat &out); + void getPose(size_t camera, Eigen::Matrix4d &out); + + void getLeftRGB(size_t camera, size_t frame, cv::Mat &data); + void getLeftDepth(size_t camera, size_t frame, cv::Mat &data); + + size_t n_frames; + size_t n_cameras; + + std::vector<std::string> sources; + std::vector<ftl::rgbd::Camera> parameters; + std::vector<cv::Mat> extrinsic; + std::vector<std::vector<cv::Mat>> rgb_left; + std::vector<std::vector<cv::Mat>> depth_left; }; class SnapshotReader { public: explicit SnapshotReader(const std::string &filename); ~SnapshotReader(); - - bool getCameraRGBD(const std::string &id, cv::Mat &rgb, cv::Mat &depth, Eigen::Matrix4d &pose, ftl::rgbd::Camera ¶ms); - std::vector<std::string> getIds(); + + Snapshot readArchive(); private: - SnapshotEntry& getEntry(const std::string &id); bool readEntry(std::vector<uchar> &data); - bool readArchive(); - std::map<std::string, SnapshotEntry> data_; + bool getDepth(const std::string &name, cv::Mat &data); + bool getRGB(const std::string &name, cv::Mat &data); + + std::map<std::string, std::vector<uchar>> files_; struct archive *archive_; struct archive_entry *entry_; }; - }; }; diff --git a/components/rgbd-sources/include/ftl/rgbd/source.hpp b/components/rgbd-sources/include/ftl/rgbd/source.hpp index 4efb97175e886ba821f752f7f154eddde0aa1f8f..0ee163add0009023ec24e6df6bd18a1da927af1e 100644 --- a/components/rgbd-sources/include/ftl/rgbd/source.hpp +++ b/components/rgbd-sources/include/ftl/rgbd/source.hpp @@ -1,10 +1,11 @@ #ifndef _FTL_RGBD_SOURCE_HPP_ #define _FTL_RGBD_SOURCE_HPP_ +#include <ftl/cuda_util.hpp> #include <ftl/configuration.hpp> #include <ftl/rgbd/camera.hpp> #include <ftl/threads.hpp> -//#include <ftl/net/universe.hpp> +#include <ftl/net/universe.hpp> #include <ftl/uri.hpp> #include <ftl/rgbd/detail/source.hpp> #include <opencv2/opencv.hpp> @@ -12,6 +13,7 @@ #include <string> #include <ftl/cuda_common.hpp> +#include <ftl/rgbd/frame.hpp> namespace ftl { @@ -24,6 +26,7 @@ namespace rgbd { static inline bool isValidDepth(float d) { return (d > 0.01f) && (d < 39.99f); } class SnapshotReader; +class VirtualSource; /** * RGBD Generic data source configurable entity. This class hides the @@ -38,6 +41,7 @@ class Source : public ftl::Configurable { public: template <typename T, typename... ARGS> friend T *ftl::config::create(ftl::config::json_t &, ARGS ...); + friend class VirtualSource; //template <typename T, typename... ARGS> //friend T *ftl::config::create(ftl::Configurable *, const std::string &, ARGS ...); @@ -49,11 +53,11 @@ class Source : public ftl::Configurable { Source(const Source&)=delete; Source &operator=(const Source&) =delete; - private: + protected: explicit Source(ftl::config::json_t &cfg); Source(ftl::config::json_t &cfg, ftl::rgbd::SnapshotReader *); Source(ftl::config::json_t &cfg, ftl::net::Universe *net); - ~Source(); + virtual ~Source(); public: /** @@ -64,26 +68,48 @@ class Source : public ftl::Configurable { /** * Change the second channel source. */ - bool setChannel(channel_t c); + bool setChannel(ftl::rgbd::Channel c); - channel_t getChannel() const { return channel_; } + ftl::rgbd::Channel getChannel() const { return channel_; } /** - * Perform the hardware or virtual frame grab operation. + * Perform the hardware or virtual frame grab operation. This should be + * fast and non-blocking. */ - bool grab(int N=-1, int B=-1); + bool capture(int64_t ts); + + /** + * Download captured frame. This could be a blocking IO operation. + */ + bool retrieve(); + + /** + * Between frames, do any required buffer swaps. + */ + void swap() { if (impl_) impl_->swap(); } /** * Do any post-grab processing. This function * may take considerable time to return, especially for sources requiring - * software stereo correspondance. If `process` is not called manually - * after a `grab` and before a `get`, then it will be called automatically - * on first `get`. + * software stereo correspondance. + */ + bool compute(int N=-1, int B=-1); + + /** + * Wrapper grab that performs capture, swap and computation steps in one. + * It is more optimal to perform capture and compute in parallel. */ - //void process(); + bool grab(int N=-1, int B=-1) { + bool c = capture(0); + c = c && retrieve(); + swap(); + return c && compute(N,B); + } /** - * Get a copy of both colour and depth frames. + * Get a copy of both colour and depth frames. Note that this does a buffer + * swap rather than a copy, so the parameters should be persistent buffers for + * best performance. */ void getFrames(cv::Mat &c, cv::Mat &d); @@ -97,17 +123,6 @@ class Source : public ftl::Configurable { */ void getDepth(cv::Mat &d); - /** - * Write frames into source buffers from an external renderer. Virtual - * sources do not have an internal generator of frames but instead have - * their data provided from an external rendering class. This function only - * works when there is no internal generator. - */ - void writeFrames(const cv::Mat &rgb, const cv::Mat &depth); - void writeFrames(const ftl::cuda::TextureObject<uchar4> &rgb, const ftl::cuda::TextureObject<uint> &depth, cudaStream_t stream); - void writeFrames(const ftl::cuda::TextureObject<uchar4> &rgb, const ftl::cuda::TextureObject<float> &depth, cudaStream_t stream); - void writeFrames(const ftl::cuda::TextureObject<uchar4> &rgb, const ftl::cuda::TextureObject<uchar4> &rgb2, cudaStream_t stream); - int64_t timestamp() const { return timestamp_; } /** @@ -118,6 +133,8 @@ class Source : public ftl::Configurable { void uploadColour(cv::cuda::GpuMat&); void uploadDepth(cv::cuda::GpuMat&); + bool isVirtual() const { return impl_ == nullptr; } + /** * Get the source's camera intrinsics. */ @@ -126,7 +143,7 @@ class Source : public ftl::Configurable { else return params_; } - const Camera parameters(channel_t) const; + const Camera parameters(ftl::rgbd::Channel) const; cv::Mat cameraMatrix() const; @@ -183,11 +200,12 @@ class Source : public ftl::Configurable { SHARED_MUTEX &mutex() { return mutex_; } - std::function<void(int64_t, const cv::Mat &, const cv::Mat &)> &callback() { return callback_; } - void setCallback(std::function<void(int64_t, const cv::Mat &, const cv::Mat &)> cb) { callback_ = cb; } + std::function<void(int64_t, cv::Mat &, cv::Mat &)> &callback() { return callback_; } + void setCallback(std::function<void(int64_t, cv::Mat &, cv::Mat &)> cb); + void removeCallback() { callback_ = nullptr; } - private: + protected: detail::Source *impl_; cv::Mat rgb_; cv::Mat depth_; @@ -198,10 +216,10 @@ class Source : public ftl::Configurable { SHARED_MUTEX mutex_; bool paused_; bool bullet_; - channel_t channel_; + ftl::rgbd::Channel channel_; cudaStream_t stream_; int64_t timestamp_; - std::function<void(int64_t, const cv::Mat &, const cv::Mat &)> callback_; + std::function<void(int64_t, cv::Mat &, cv::Mat &)> callback_; detail::Source *_createImplementation(); detail::Source *_createFileImpl(const ftl::URI &uri); diff --git a/components/rgbd-sources/include/ftl/rgbd/streamer.hpp b/components/rgbd-sources/include/ftl/rgbd/streamer.hpp index 0143bc0b0a030e509629e5bb6185dd553d1c9683..7c6e6f479afe022cdacefbabf9098e276e0c9f79 100644 --- a/components/rgbd-sources/include/ftl/rgbd/streamer.hpp +++ b/components/rgbd-sources/include/ftl/rgbd/streamer.hpp @@ -5,7 +5,9 @@ #include <ftl/configuration.hpp> #include <ftl/configurable.hpp> #include <ftl/rgbd/source.hpp> +#include <ftl/rgbd/group.hpp> #include <ftl/net/universe.hpp> +#include <ftl/codecs/encoder.hpp> #include <ftl/threads.hpp> #include <string> #include <vector> @@ -15,8 +17,8 @@ namespace ftl { namespace rgbd { -static const int kChunkDim = 4; -static constexpr int kChunkCount = kChunkDim * kChunkDim; +//static const int kChunkDim = 4; +//static constexpr int kChunkCount = kChunkDim * kChunkDim; namespace detail { @@ -25,23 +27,38 @@ struct StreamClient { ftl::UUID peerid; std::atomic<int> txcount; // Frames sent since last request int txmax; // Frames to send in request + ftl::codecs::preset_t preset; }; static const unsigned int kGrabbed = 0x1; static const unsigned int kRGB = 0x2; -static const unsigned int kDepth = 0x4; +static const unsigned int kDepth = 0x4; + +static const unsigned int kFrameDropLimit = 5; +static const unsigned int kMaxBitrateLevels = 10; struct StreamSource { ftl::rgbd::Source *src; - std::atomic<unsigned int> jobs; // Busy or ready to swap? + std::atomic<int> jobs; // Busy or ready to swap? std::atomic<unsigned int> clientCount; + + int hq_count; // Number of high quality requests + int lq_count; // Number of low quality requests + ftl::codecs::preset_t hq_bitrate=ftl::codecs::kPresetBest; // Max bitrate + ftl::codecs::preset_t lq_bitrate=ftl::codecs::kPresetWorst; // Min bitrate + cv::Mat rgb; // Tx buffer cv::Mat depth; // Tx buffer cv::Mat prev_rgb; cv::Mat prev_depth; - std::list<detail::StreamClient> clients[10]; // One list per bitrate + std::list<detail::StreamClient> clients; SHARED_MUTEX mutex; unsigned long long frame; + + ftl::codecs::Encoder *hq_encoder_c1 = nullptr; + ftl::codecs::Encoder *hq_encoder_c2 = nullptr; + ftl::codecs::Encoder *lq_encoder_c1 = nullptr; + ftl::codecs::Encoder *lq_encoder_c2 = nullptr; }; } @@ -51,6 +68,11 @@ struct StreamSource { */ static const int kMaxFrames = 100; +enum encoder_t { + kEncodeVideo, + kEncodeImages +}; + /** * Allows network streaming of a number of RGB-Depth sources. Remote machines * can discover available streams from an instance of Streamer. It also allows @@ -95,6 +117,8 @@ class Streamer : public ftl::Configurable { void wait(); + void setLatency(int l) { group_.setLatency(l); } + /** * Alternative to calling run(), it will operate a single frame capture, * compress and stream cycle. @@ -104,27 +128,40 @@ class Streamer : public ftl::Configurable { Source *get(const std::string &uri); private: + ftl::rgbd::Group group_; std::map<std::string, detail::StreamSource*> sources_; //ctpl::thread_pool pool_; SHARED_MUTEX mutex_; bool active_; ftl::net::Universe *net_; bool late_; - std::mutex job_mtx_; - std::condition_variable job_cv_; - std::atomic<int> jobs_; int compress_level_; int64_t clock_adjust_; ftl::UUID time_peer_; int64_t last_frame_; int64_t frame_no_; - void _schedule(); - void _swap(detail::StreamSource *); + encoder_t encode_mode_; + + int64_t mspf_; + float actual_fps_; + //int64_t last_dropped_; + //int drop_count_; + + ftl::timer::TimerHandle timer_job_; + + ftl::codecs::device_t hq_devices_; + + void _process(ftl::rgbd::FrameSet &); + void _cleanUp(); void _addClient(const std::string &source, int N, int rate, const ftl::UUID &peer, const std::string &dest); - void _encodeAndTransmit(detail::StreamSource *src, int chunk); - void _encodeChannel1(const cv::Mat &in, std::vector<unsigned char> &out, unsigned int b); - bool _encodeChannel2(const cv::Mat &in, std::vector<unsigned char> &out, ftl::rgbd::channel_t c, unsigned int b); + void _transmitPacket(detail::StreamSource *src, const ftl::codecs::Packet &pkt, int chan, bool hasChan2, bool hqonly); + + //void _encodeHQAndTransmit(detail::StreamSource *src, const cv::Mat &, const cv::Mat &, int chunk); + //void _encodeLQAndTransmit(detail::StreamSource *src, const cv::Mat &, const cv::Mat &, int chunk); + //void _encodeAndTransmit(detail::StreamSource *src, ftl::codecs::Encoder *enc1, ftl::codecs::Encoder *enc2, const cv::Mat &, const cv::Mat &); + //void _encodeImageChannel1(const cv::Mat &in, std::vector<unsigned char> &out, unsigned int b); + //bool _encodeImageChannel2(const cv::Mat &in, std::vector<unsigned char> &out, ftl::rgbd::channel_t c, unsigned int b); }; } diff --git a/components/rgbd-sources/include/ftl/rgbd/virtual.hpp b/components/rgbd-sources/include/ftl/rgbd/virtual.hpp new file mode 100644 index 0000000000000000000000000000000000000000..ed3530258d64b14427802f8e56375635ab26a24a --- /dev/null +++ b/components/rgbd-sources/include/ftl/rgbd/virtual.hpp @@ -0,0 +1,30 @@ +#ifndef _FTL_RGBD_VIRTUAL_HPP_ +#define _FTL_RGBD_VIRTUAL_HPP_ + +#include <ftl/rgbd/source.hpp> + +namespace ftl { +namespace rgbd { + +class VirtualSource : public ftl::rgbd::Source { + public: + explicit VirtualSource(ftl::config::json_t &cfg); + ~VirtualSource(); + + void onRender(const std::function<void(ftl::rgbd::Frame &)> &); + + void setTimestamp(int64_t ts) { timestamp_ = ts; } + + /** + * Write frames into source buffers from an external renderer. Virtual + * sources do not have an internal generator of frames but instead have + * their data provided from an external rendering class. This function only + * works when there is no internal generator. + */ + //void write(int64_t ts, ftl::rgbd::Frame &frame, cudaStream_t stream=0); +}; + +} +} + +#endif // _FTL_RGBD_VIRTUAL_HPP_ diff --git a/components/rgbd-sources/include/qsort.h b/components/rgbd-sources/include/qsort.h new file mode 100644 index 0000000000000000000000000000000000000000..62a76b836c1b13861fc8a5a12ff0fc0eb7936f8a --- /dev/null +++ b/components/rgbd-sources/include/qsort.h @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2013, 2017 Alexey Tourbin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* + * This is a traditional Quicksort implementation which mostly follows + * [Sedgewick 1978]. Sorting is performed entirely on array indices, + * while actual access to the array elements is abstracted out with the + * user-defined `LESS` and `SWAP` primitives. + * + * Synopsis: + * QSORT(N, LESS, SWAP); + * where + * N - the number of elements in A[]; + * LESS(i, j) - compares A[i] to A[j]; + * SWAP(i, j) - exchanges A[i] with A[j]. + */ + +#ifndef QSORT_H +#define QSORT_H + +/* Sort 3 elements. */ +#define Q_SORT3(q_a1, q_a2, q_a3, Q_LESS, Q_SWAP) \ +do { \ + if (Q_LESS(q_a2, q_a1)) { \ + if (Q_LESS(q_a3, q_a2)) \ + Q_SWAP(q_a1, q_a3); \ + else { \ + Q_SWAP(q_a1, q_a2); \ + if (Q_LESS(q_a3, q_a2)) \ + Q_SWAP(q_a2, q_a3); \ + } \ + } \ + else if (Q_LESS(q_a3, q_a2)) { \ + Q_SWAP(q_a2, q_a3); \ + if (Q_LESS(q_a2, q_a1)) \ + Q_SWAP(q_a1, q_a2); \ + } \ +} while (0) + +/* Partition [q_l,q_r] around a pivot. After partitioning, + * [q_l,q_j] are the elements that are less than or equal to the pivot, + * while [q_i,q_r] are the elements greater than or equal to the pivot. */ +#define Q_PARTITION(q_l, q_r, q_i, q_j, Q_UINT, Q_LESS, Q_SWAP) \ +do { \ + /* The middle element, not to be confused with the median. */ \ + Q_UINT q_m = q_l + ((q_r - q_l) >> 1); \ + /* Reorder the second, the middle, and the last items. \ + * As [Edelkamp Weiss 2016] explain, using the second element \ + * instead of the first one helps avoid bad behaviour for \ + * decreasingly sorted arrays. This method is used in recent \ + * versions of gcc's std::sort, see gcc bug 58437#c13, although \ + * the details are somewhat different (cf. #c14). */ \ + Q_SORT3(q_l + 1, q_m, q_r, Q_LESS, Q_SWAP); \ + /* Place the median at the beginning. */ \ + Q_SWAP(q_l, q_m); \ + /* Partition [q_l+2, q_r-1] around the median which is in q_l. \ + * q_i and q_j are initially off by one, they get decremented \ + * in the do-while loops. */ \ + q_i = q_l + 1; q_j = q_r; \ + while (1) { \ + do q_i++; while (Q_LESS(q_i, q_l)); \ + do q_j--; while (Q_LESS(q_l, q_j)); \ + if (q_i >= q_j) break; /* Sedgewick says "until j < i" */ \ + Q_SWAP(q_i, q_j); \ + } \ + /* Compensate for the i==j case. */ \ + q_i = q_j + 1; \ + /* Put the median to its final place. */ \ + Q_SWAP(q_l, q_j); \ + /* The median is not part of the left subfile. */ \ + q_j--; \ +} while (0) + +/* Insertion sort is applied to small subfiles - this is contrary to + * Sedgewick's suggestion to run a separate insertion sort pass after + * the partitioning is done. The reason I don't like a separate pass + * is that it triggers extra comparisons, because it can't see that the + * medians are already in their final positions and need not be rechecked. + * Since I do not assume that comparisons are cheap, I also do not try + * to eliminate the (q_j > q_l) boundary check. */ +#define Q_INSERTION_SORT(q_l, q_r, Q_UINT, Q_LESS, Q_SWAP) \ +do { \ + Q_UINT q_i, q_j; \ + /* For each item starting with the second... */ \ + for (q_i = q_l + 1; q_i <= q_r; q_i++) \ + /* move it down the array so that the first part is sorted. */ \ + for (q_j = q_i; q_j > q_l && (Q_LESS(q_j, q_j - 1)); q_j--) \ + Q_SWAP(q_j, q_j - 1); \ +} while (0) + +/* When the size of [q_l,q_r], i.e. q_r-q_l+1, is greater than or equal to + * Q_THRESH, the algorithm performs recursive partitioning. When the size + * drops below Q_THRESH, the algorithm switches to insertion sort. + * The minimum valid value is probably 5 (with 5 items, the second and + * the middle items, the middle itself being rounded down, are distinct). */ +#define Q_THRESH 16 + +/* The main loop. */ +#define Q_LOOP(Q_UINT, Q_N, Q_LESS, Q_SWAP) \ +do { \ + Q_UINT q_l = 0; \ + Q_UINT q_r = (Q_N) - 1; \ + Q_UINT q_sp = 0; /* the number of frames pushed to the stack */ \ + struct { Q_UINT q_l, q_r; } \ + /* On 32-bit platforms, to sort a "char[3GB+]" array, \ + * it may take full 32 stack frames. On 64-bit CPUs, \ + * though, the address space is limited to 48 bits. \ + * The usage is further reduced if Q_N has a 32-bit type. */ \ + q_st[sizeof(Q_UINT) > 4 && sizeof(Q_N) > 4 ? 48 : 32]; \ + while (1) { \ + if (q_r - q_l + 1 >= Q_THRESH) { \ + Q_UINT q_i, q_j; \ + Q_PARTITION(q_l, q_r, q_i, q_j, Q_UINT, Q_LESS, Q_SWAP); \ + /* Now have two subfiles: [q_l,q_j] and [q_i,q_r]. \ + * Dealing with them depends on which one is bigger. */ \ + if (q_j - q_l >= q_r - q_i) \ + Q_SUBFILES(q_l, q_j, q_i, q_r); \ + else \ + Q_SUBFILES(q_i, q_r, q_l, q_j); \ + } \ + else { \ + Q_INSERTION_SORT(q_l, q_r, Q_UINT, Q_LESS, Q_SWAP); \ + /* Pop subfiles from the stack, until it gets empty. */ \ + if (q_sp == 0) break; \ + q_sp--; \ + q_l = q_st[q_sp].q_l; \ + q_r = q_st[q_sp].q_r; \ + } \ + } \ +} while (0) + +/* The missing part: dealing with subfiles. + * Assumes that the first subfile is not smaller than the second. */ +#define Q_SUBFILES(q_l1, q_r1, q_l2, q_r2) \ +do { \ + /* If the second subfile is only a single element, it needs \ + * no further processing. The first subfile will be processed \ + * on the next iteration (both subfiles cannot be only a single \ + * element, due to Q_THRESH). */ \ + if (q_l2 == q_r2) { \ + q_l = q_l1; \ + q_r = q_r1; \ + } \ + else { \ + /* Otherwise, both subfiles need processing. \ + * Push the larger subfile onto the stack. */ \ + q_st[q_sp].q_l = q_l1; \ + q_st[q_sp].q_r = q_r1; \ + q_sp++; \ + /* Process the smaller subfile on the next iteration. */ \ + q_l = q_l2; \ + q_r = q_r2; \ + } \ +} while (0) + +/* And now, ladies and gentlemen, may I proudly present to you... */ +#define QSORT(Q_N, Q_LESS, Q_SWAP) \ +do { \ + if ((Q_N) > 1) \ + /* We could check sizeof(Q_N) and use "unsigned", but at least \ + * on x86_64, this has the performance penalty of up to 5%. */ \ + Q_LOOP(unsigned long, Q_N, Q_LESS, Q_SWAP); \ +} while (0) + +#endif + +/* ex:set ts=8 sts=4 sw=4 noet: */ \ No newline at end of file diff --git a/components/rgbd-sources/src/abr.cpp b/components/rgbd-sources/src/abr.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c338d4725ed2ab493fd61143d24b9b7241453622 --- /dev/null +++ b/components/rgbd-sources/src/abr.cpp @@ -0,0 +1,120 @@ +#include <ftl/rgbd/detail/abr.hpp> +#include <ftl/timer.hpp> + +#include <bitset> + +using ftl::rgbd::detail::BitrateSetting; +using ftl::rgbd::detail::ABRController; +using ftl::rgbd::detail::bitrate_t; +using ftl::rgbd::detail::kBitrateWorst; +using ftl::rgbd::detail::kBitrateBest; +using ftl::rgbd::detail::bitrate_settings; +using ftl::rgbd::detail::NetFrame; + +ABRController::ABRController() { + bitrate_ = 0; + enabled_ = true; + max_ = kBitrateBest; + min_ = kBitrateWorst; +} + +ABRController::~ABRController() { + +} + +void ABRController::setMaximumBitrate(bitrate_t b) { + max_ = (b == -1) ? kBitrateBest : b; + if (bitrate_ < max_) bitrate_ = max_; +} + +void ABRController::setMinimumBitrate(bitrate_t b) { + min_ = (b == -1) ? kBitrateWorst : b; + if (bitrate_ > min_) bitrate_ = min_; +} + +void ABRController::notifyChanged() { + enabled_ = true; +} + +bitrate_t ABRController::selectBitrate(const NetFrame &frame) { + if (!enabled_) return bitrate_; + + float actual_mbps = (float(frame.tx_size) * 8.0f * (1000.0f / float(frame.tx_latency))) / 1048576.0f; + float min_mbps = (float(frame.tx_size) * 8.0f * (1000.0f / float(ftl::timer::getInterval()))) / 1048576.0f; + //LOG(INFO) << "Bitrate = " << actual_mbps << "Mbps, min required = " << min_mbps << "Mbps"; + float ratio = actual_mbps / min_mbps; + //LOG(INFO) << "Rate Ratio = " << frame.tx_latency; + + return bitrate_; + + down_log_ = down_log_ << 1; + up_log_ = up_log_ << 1; + + if (ratio < 1.2f) { + down_log_ += 1; + } else if (ratio > 1.5f) { + up_log_ += 1; + } + + std::bitset<32> bd(down_log_); + std::bitset<32> bu(up_log_); + + if (bitrate_ < min_ && int(bd.count()) - int(bu.count()) > 5) { + enabled_ = false; + down_log_ = 0; + up_log_ = 0; + bitrate_++; + LOG(INFO) << "Bitrate down to: " << bitrate_; + } else if (bitrate_ > max_ && int(bu.count()) - int(bd.count()) > 15) { + enabled_ = false; + up_log_ = 0; + down_log_ = 0; + bitrate_--; + LOG(INFO) << "Bitrate up to: " << bitrate_; + } + + return bitrate_; +} + +const BitrateSetting &ABRController::getBitrateInfo(bitrate_t b) { + if (b > kBitrateWorst) return bitrate_settings[kBitrateWorst]; + if (b < kBitrateBest) return bitrate_settings[kBitrateBest]; + return bitrate_settings[b]; +}; + +int ABRController::getColourWidth(bitrate_t b) { + return int(std::ceil(bitrate_settings[b].colour_res * kAspectRatio)) & 0x7FFFFFFFC; +} + +int ABRController::getDepthWidth(bitrate_t b) { + return std::ceil(bitrate_settings[b].depth_res * kAspectRatio); +} + +int ABRController::getColourHeight(bitrate_t b) { + return bitrate_settings[b].colour_res; +} + +int ABRController::getDepthHeight(bitrate_t b) { + return bitrate_settings[b].depth_res; +} + +int ABRController::getBlockCountX(bitrate_t b) { + return bitrate_settings[b].block_count_x; +} + +int ABRController::getBlockCountY(bitrate_t b) { + return bitrate_settings[b].block_count_x; +} + +int ABRController::getBlockCount(bitrate_t b) { + const int c = bitrate_settings[b].block_count_x; + return c*c; +} + +int ABRController::getColourQuality(bitrate_t b) { + return bitrate_settings[b].colour_qual; +} + +int ABRController::getDepthQuality(bitrate_t b) { + return bitrate_settings[b].depth_qual; +} diff --git a/components/rgbd-sources/src/algorithms/fixstars_sgm.cpp b/components/rgbd-sources/src/algorithms/fixstars_sgm.cpp index 73d853e9c2fb2fce7728a59c7956136b972c0ec6..5f8921bda0e562bcc26b454d750a35294ed75537 100644 --- a/components/rgbd-sources/src/algorithms/fixstars_sgm.cpp +++ b/components/rgbd-sources/src/algorithms/fixstars_sgm.cpp @@ -7,63 +7,135 @@ using ftl::algorithms::FixstarsSGM; using cv::Mat; using cv::cuda::GpuMat; +using ftl::rgbd::Channel; +using ftl::rgbd::Format; //static ftl::Disparity::Register fixstarssgm("libsgm", FixstarsSGM::create); FixstarsSGM::FixstarsSGM(nlohmann::json &config) : Disparity(config) { ssgm_ = nullptr; + const int width = size_.width; + const int height = size_.height; + + CHECK((width >= 480) && (height >= 360)); + uniqueness_ = value("uniqueness", 0.95f); + P1_ = value("P1", 10); + P2_ = value("P2", 120); + + CHECK((uniqueness_ >= 0.0) && (uniqueness_ <= 1.0)); + CHECK(P1_ >= 0); + CHECK(P2_ > P1_); + use_filter_ = value("use_filter", false); - filter_ = cv::cuda::createDisparityBilateralFilter(max_disp_ << 4, value("filter_radius", 25), value("filter_iter", 1)); + if (use_filter_) { + int radius = value("filter_radius", 25); + int iter = value("filter_iter", 1); + CHECK(radius > 0) << "filter_radius must be greater than 0"; + CHECK(iter > 0) << "filter_iter must be greater than 0"; + + filter_ = cv::cuda::createDisparityBilateralFilter(max_disp_ << 4, radius, iter); + } + +#ifdef HAVE_OPTFLOW + use_off_ = value("use_off", false); + + if (use_off_) + { + int off_size = value("off_size", 9); + double off_threshold = value("off_threshold", 0.9); + off_ = ftl::rgbd::OFDisparityFilter(size_, off_size, off_threshold); + } +#endif + + init(size_); } -void FixstarsSGM::compute(const cv::cuda::GpuMat &l, const cv::cuda::GpuMat &r, cv::cuda::GpuMat &disp, cv::cuda::Stream &stream) { - cv::cuda::cvtColor(l, lbw_, cv::COLOR_BGR2GRAY, 0, stream); - cv::cuda::cvtColor(r, rbw_, cv::COLOR_BGR2GRAY, 0, stream); +void FixstarsSGM::init(const cv::Size size) { + if (ssgm_) { delete ssgm_; } + lbw_ = GpuMat(size, CV_8UC1); + rbw_ = GpuMat(size, CV_8UC1); + dispt_ = GpuMat(size, CV_16SC1); - stream.waitForCompletion(); - if (!ssgm_) { // todo: move to constructor - dispt_ = GpuMat(l.rows, l.cols, CV_16SC1); - ssgm_ = new sgm::StereoSGM(l.cols, l.rows, max_disp_, 8, 16, lbw_.step, dispt_.step / sizeof(short), - sgm::EXECUTE_INOUT_CUDA2CUDA, sgm::StereoSGM::Parameters(10, 120, uniqueness_, true)); + ssgm_ = new sgm::StereoSGM(size.width, size.height, max_disp_, 8, 16, + lbw_.step, dispt_.step / sizeof(short), + sgm::EXECUTE_INOUT_CUDA2CUDA, + sgm::StereoSGM::Parameters(P1_, P2_, uniqueness_, true) + ); +} + +void FixstarsSGM::compute(ftl::rgbd::Frame &frame, cv::cuda::Stream &stream) +{ + /*if (!frame.hasChannel(ftl::rgbd::kChanLeftGray)) + { + auto &rgb = frame.getChannel<GpuMat>(ftl::rgbd::kChanLeft, stream); + auto &gray = frame.setChannel<GpuMat>(ftl::rgbd::kChanLeftGray); + cv::cuda::cvtColor(rgb, gray, cv::COLOR_BGR2GRAY, 0, stream); } - //auto start = std::chrono::high_resolution_clock::now(); + if (!frame.hasChannel(ftl::rgbd::kChanRightGray)) + { + auto &rgb = frame.getChannel<GpuMat>(ftl::rgbd::kChanRight, stream); + auto &gray = frame.setChannel<GpuMat>(ftl::rgbd::kChanRightGray); + cv::cuda::cvtColor(rgb, gray, cv::COLOR_BGR2GRAY, 0, stream); + }*/ + + const auto &l = frame.get<GpuMat>(Channel::Left); + const auto &r = frame.get<GpuMat>(Channel::Right); + auto &disp = frame.create<GpuMat>(Channel::Disparity, Format<float>(l.size())); + + GpuMat l_scaled; + if (l.size() != size_) + { + GpuMat _r; + scaleInput(l, r, l_scaled, _r, stream); + cv::cuda::cvtColor(l_scaled, lbw_, cv::COLOR_BGR2GRAY, 0, stream); + cv::cuda::cvtColor(_r, rbw_, cv::COLOR_BGR2GRAY, 0, stream); + } + else + { + cv::cuda::cvtColor(l, lbw_, cv::COLOR_BGR2GRAY, 0, stream); + cv::cuda::cvtColor(r, rbw_, cv::COLOR_BGR2GRAY, 0, stream); + } + + stream.waitForCompletion(); ssgm_->execute(lbw_.data, rbw_.data, dispt_.data); - //std::chrono::duration<double> elapsed = - // std::chrono::high_resolution_clock::now() - start; - //LOG(INFO) << "CUDA sgm in " << elapsed.count() << "s"; - - // todo: fix libSGM (return float data or provide mask separately) - // disparity values set to (256 << 5) in libSGM consistency check - //Mat bad_pixels = (disp == (256 << 5)); - - //disp.setTo(0, bad_pixels, stream_); GpuMat left_pixels(dispt_, cv::Rect(0, 0, max_disp_, dispt_.rows)); left_pixels.setTo(0); - cv::cuda::threshold(dispt_, dispt_, 4096.0f, 0.0f, cv::THRESH_TOZERO_INV, stream); - if (use_filter_) { - // parameters need benchmarking, impact of image - // quick tests show with parameters (max_disp_, 25, 3) - // roughly 50% in disparity calculation and 50% in filter; - filter_->apply(dispt_, l, dispt_, stream); + // TODO: filter could be applied after upscaling (to the upscaled disparity image) + if (use_filter_) + { + filter_->apply( + dispt_, + (l.size() == size_) ? l : l_scaled, + dispt_, + stream + ); } - - dispt_.convertTo(disp, CV_32F, 1.0f/16.0f, stream); + + GpuMat dispt_scaled; + if (l.size() != size_) + { + scaleDisparity(l.size(), dispt_, dispt_scaled, stream); + } + else + { + dispt_scaled = dispt_; + } + + dispt_scaled.convertTo(disp, CV_32F, 1.0f / 16.0f, stream); + +#ifdef HAVE_OPTFLOW + if (use_off_) { off_.filter(frame, stream); } +#endif } void FixstarsSGM::setMask(Mat &mask) { return; // TODO(Nick) Not needed, but also code below does not work with new GPU pipeline CHECK(mask.type() == CV_8UC1) << "mask type must be CV_8U"; - - if (!ssgm_) { // todo: move to constructor - ssgm_ = new sgm::StereoSGM(mask.cols, mask.rows, max_disp_, 8, 16, - sgm::EXECUTE_INOUT_HOST2HOST, - sgm::StereoSGM::Parameters(10, 120, uniqueness_, true)); - } - - mask_l_ = mask; - ssgm_->setMask((uint8_t*) mask.data, mask.cols); + if (!ssgm_) { init(size_); } + mask_l_ = GpuMat(mask); + ssgm_->setMask((uint8_t*)mask.data, mask.cols); } \ No newline at end of file diff --git a/components/rgbd-sources/src/algorithms/fixstars_sgm.hpp b/components/rgbd-sources/src/algorithms/fixstars_sgm.hpp index 95511cb2cdc9cde452d6ffe47ec4e3c9fb99c2f9..d2c0c1d2a8fd459695bb02c9f66f17e423dc9fa5 100644 --- a/components/rgbd-sources/src/algorithms/fixstars_sgm.hpp +++ b/components/rgbd-sources/src/algorithms/fixstars_sgm.hpp @@ -5,47 +5,59 @@ #ifndef _FTL_ALGORITHMS_FIXSTARS_SGM_HPP_ #define _FTL_ALGORITHMS_FIXSTARS_SGM_HPP_ +#include <ftl/cuda_util.hpp> #include <opencv2/core.hpp> #include <opencv2/opencv.hpp> -#include <libsgm.h> -#include "../disparity.hpp" #include <opencv2/cudastereo.hpp> + +#include "../disparity.hpp" #include <ftl/configuration.hpp> +#include <ftl/config.h> -#include "ftl/cb_segmentation.hpp" +#include <libsgm.h> +#include "ftl/offilter.hpp" namespace ftl { -namespace algorithms { - -/** - * Fixstars libSGM stereo matcher. - * @see https://github.com/fixstars/libSGM - * - * NOTE: We are using a modified version that supports disparity of 256. - * @see https://github.com/knicos/libSGM - */ -class FixstarsSGM : public ftl::rgbd::detail::Disparity { - public: - explicit FixstarsSGM(nlohmann::json &config); - - void compute(const cv::cuda::GpuMat &l, const cv::cuda::GpuMat &r, cv::cuda::GpuMat &disp, cv::cuda::Stream &stream) override; - void setMask(cv::Mat &mask) override; - - /* Factory creator */ - static inline Disparity *create(ftl::Configurable *p, const std::string &name) { - return ftl::create<FixstarsSGM>(p, name); - } - - private: - float uniqueness_; - bool use_filter_; - cv::Ptr<cv::cuda::DisparityBilateralFilter> filter_; - sgm::StereoSGM *ssgm_; - cv::cuda::GpuMat lbw_; - cv::cuda::GpuMat rbw_; - cv::cuda::GpuMat dispt_; -}; -}; + namespace algorithms { + + /** + * Fixstars libSGM stereo matcher. + * @see https://github.com/fixstars/libSGM + * + * NOTE: We are using a modified version that supports disparity of 256. + * @see https://github.com/knicos/libSGM + */ + class FixstarsSGM : public ftl::rgbd::detail::Disparity { + public: + explicit FixstarsSGM(nlohmann::json &config); + + void compute(ftl::rgbd::Frame &frame, cv::cuda::Stream &stream) override; + void setMask(cv::Mat &mask) override; + + /* Factory creator */ + static inline Disparity *create(ftl::Configurable *p, const std::string &name) { + return ftl::create<FixstarsSGM>(p, name); + } + + private: + void init(const cv::Size size); + + float uniqueness_; + int P1_; + int P2_; + bool use_filter_; + bool use_off_; + cv::Ptr<cv::cuda::DisparityBilateralFilter> filter_; + sgm::StereoSGM *ssgm_; + cv::cuda::GpuMat lbw_; + cv::cuda::GpuMat rbw_; + cv::cuda::GpuMat dispt_; + + #ifdef HAVE_OPTFLOW + ftl::rgbd::OFDisparityFilter off_; + #endif + }; + }; }; #endif // _FTL_ALGORITHMS_FIXSTARS_SGM_HPP_ diff --git a/components/rgbd-sources/src/algorithms/offilter.cu b/components/rgbd-sources/src/algorithms/offilter.cu new file mode 100644 index 0000000000000000000000000000000000000000..6feee5ae1daf9fc8b4b165370dbb8646b1d7b8a1 --- /dev/null +++ b/components/rgbd-sources/src/algorithms/offilter.cu @@ -0,0 +1,91 @@ +#include <ftl/cuda_common.hpp> +#include <ftl/rgbd/camera.hpp> +#include <opencv2/core/cuda_stream_accessor.hpp> +#include <qsort.h> + +__device__ void quicksort(float A[], size_t n) +{ + float tmp; + #define LESS(i, j) A[i] < A[j] + #define SWAP(i, j) tmp = A[i], A[i] = A[j], A[j] = tmp + QSORT(n, LESS, SWAP); +} + +template<typename T> +__device__ static bool inline isValidDisparity(T d) { return (0.0 < d) && (d < 256.0); } // TODO + +static const int max_history = 32; // TODO dynamic shared memory + +__global__ void temporal_median_filter_kernel( + cv::cuda::PtrStepSz<float> disp, + cv::cuda::PtrStepSz<int16_t> optflow, + cv::cuda::PtrStepSz<float> history, + int n_max, + int16_t threshold, // fixed point 10.5 + float granularity // 4 for Turing +) +{ + float sorted[max_history]; // TODO: dynamic shared memory + for (STRIDE_Y(y, disp.rows)) { + for (STRIDE_X(x, disp.cols)) { + + int flowx = optflow(round(y / granularity), 2 * round(x / granularity)); + int flowy = optflow(round(y / granularity), 2 * round(x / granularity) + 1); + + if ((abs(flowx) + abs(flowy)) > threshold) + { + // last element in history[x][y][t] + history(y, (x + 1) * n_max - 1) = 0.0; + return; + } + + int count = history(y, (x + 1) * n_max - 1); + int n = count % (n_max - 1); + + if (isValidDisparity(disp(y, x))) + { + history(y, (x + 1) * n_max - 1) += 1.0; + count++; + history(y, x * n_max + n) = disp(y, x); + } + + int n_end = count; + if (n_end >= n_max) { n_end = n_max - 1; } + + if (n_end != 0) + { + for (size_t i = 0; i < n_end; i++) + { + sorted[i] = history(y, x * n_max + i); + } + + quicksort(sorted, n_end); + disp(y, x) = sorted[n_end / 2]; + } + }} +} + +namespace ftl { +namespace cuda { + +void optflow_filter(cv::cuda::GpuMat &disp, const cv::cuda::GpuMat &optflow, + cv::cuda::GpuMat &history, int n, float threshold, + cv::cuda::Stream &stream) +{ + dim3 grid(1, 1, 1); + dim3 threads(128, 1, 1); + grid.x = cv::cuda::device::divUp(disp.cols, 128); + grid.y = cv::cuda::device::divUp(disp.rows, 1); + + // TODO: dynamic shared memory + temporal_median_filter_kernel<<<grid, threads, 0, cv::cuda::StreamAccessor::getStream(stream)>>> + ( disp, optflow, history, n, + round(threshold * (1 << 5)), // TODO: documentation; 10.5 format + 4 // TODO: (4 pixels granularity for Turing) + ); + + cudaSafeCall(cudaGetLastError()); +} + +} +} \ No newline at end of file diff --git a/components/rgbd-sources/src/bitrate_settings.hpp b/components/rgbd-sources/src/bitrate_settings.hpp index 6b6e1508ed87dac7e24f7af94b903782e2d9a3ce..3dbd23bc10129398d878cae0501bbc73d22bb3a8 100644 --- a/components/rgbd-sources/src/bitrate_settings.hpp +++ b/components/rgbd-sources/src/bitrate_settings.hpp @@ -5,26 +5,6 @@ namespace ftl { namespace rgbd { namespace detail { -struct BitrateSetting { - int width; - int height; - int jpg_quality; - int png_compression; -}; - -static const BitrateSetting bitrate_settings[] = { - 1280, 720, 95, 1, - 1280, 720, 95, 1, - 1280, 720, 95, 1, - 1280, 720, 75, 1, - 640, 360, 95, 1, - 640, 360, 75, 5, - 640, 360, 50, 5, - 320, 160, 95, 5, - 320, 160, 75, 5, - 320, 160, 50, 9 -}; - } } } diff --git a/components/rgbd-sources/src/calibrate.cpp b/components/rgbd-sources/src/calibrate.cpp index 17090992f36bebe355144e4ce714acf891005463..3940520d2374661449c376020cb98c5802e2c674 100644 --- a/components/rgbd-sources/src/calibrate.cpp +++ b/components/rgbd-sources/src/calibrate.cpp @@ -53,7 +53,7 @@ Calibrate::Calibrate(nlohmann::json &config, cv::Size image_size, cv::cuda::Stre else { LOG(WARNING) << "Calibration not loaded"; } - + this->on("use_intrinsics", [this](const ftl::config::Event &e) { _updateIntrinsics(); }); @@ -70,7 +70,7 @@ bool Calibrate::_loadCalibration(cv::Size img_size, std::pair<Mat, Mat> &map1, s LOG(WARNING) << "Could not open intrinsics file"; return false; } - + LOG(INFO) << "Intrinsics from: " << *ifile; } else { @@ -78,19 +78,24 @@ bool Calibrate::_loadCalibration(cv::Size img_size, std::pair<Mat, Mat> &map1, s return false; } + + cv::Size calib_size; { vector<Mat> K, D; fs["K"] >> K; fs["D"] >> D; - - K[0].copyTo(M1_); - K[1].copyTo(M2_); + fs["resolution"] >> calib_size; + + K[0].copyTo(K1_); + K[1].copyTo(K2_); D[0].copyTo(D1_); D[1].copyTo(D2_); } - CHECK(M1_.size() == Size(3, 3)); - CHECK(M2_.size() == Size(3, 3)); + fs.release(); + + CHECK(K1_.size() == Size(3, 3)); + CHECK(K2_.size() == Size(3, 3)); CHECK(D1_.size() == Size(5, 1)); CHECK(D2_.size() == Size(5, 1)); @@ -101,37 +106,68 @@ bool Calibrate::_loadCalibration(cv::Size img_size, std::pair<Mat, Mat> &map1, s LOG(WARNING) << "Could not open extrinsics file"; return false; } - + LOG(INFO) << "Extrinsics from: " << *efile; - } else { + } + else { LOG(WARNING) << "Calibration extrinsics file not found"; return false; } fs["R"] >> R_; fs["T"] >> T_; + + /* re-calculate rectification from camera parameters fs["R1"] >> R1_; fs["R2"] >> R2_; fs["P1"] >> P1_; fs["P2"] >> P2_; fs["Q"] >> Q_; + */ + fs.release(); img_size_ = img_size; + if (calib_size.empty()) + { + LOG(WARNING) << "Calibration resolution missing!"; + } + else + { + double scale_x = ((double) img_size.width) / ((double) calib_size.width); + double scale_y = ((double) img_size.height) / ((double) calib_size.height); + + Mat scale(cv::Size(3, 3), CV_64F, 0.0); + scale.at<double>(0, 0) = scale_x; + scale.at<double>(1, 1) = scale_y; + scale.at<double>(2, 2) = 1.0; + + K1_ = scale * K1_; + K2_ = scale * K2_; + } + + double alpha = value("alpha", 0.0); + cv::stereoRectify(K1_, D1_, K2_, D2_, img_size_, R_, T_, R1_, R2_, P1_, P2_, Q_, 0, alpha); + + /* scaling not required as rectification is performed from scaled values + Q_.at<double>(0, 3) = Q_.at<double>(0, 3) * scale_x; + Q_.at<double>(1, 3) = Q_.at<double>(1, 3) * scale_y; + Q_.at<double>(2, 3) = Q_.at<double>(2, 3) * scale_x; // TODO: scaling? + Q_.at<double>(3, 3) = Q_.at<double>(3, 3) * scale_x; + */ + // cv::cuda::remap() works only with CV_32FC1 - initUndistortRectifyMap(M1_, D1_, R1_, P1_, img_size_, CV_32FC1, map1.first, map2.first); - initUndistortRectifyMap(M2_, D2_, R2_, P2_, img_size_, CV_32FC1, map1.second, map2.second); + initUndistortRectifyMap(K1_, D1_, R1_, P1_, img_size_, CV_32FC1, map1.first, map2.first); + initUndistortRectifyMap(K2_, D2_, R2_, P2_, img_size_, CV_32FC1, map1.second, map2.second); return true; } void Calibrate::updateCalibration(const ftl::rgbd::Camera &p) { - std::pair<Mat, Mat> map1, map2; - - Q_.at<double>(3,2) = 1.0 / p.baseline; - Q_.at<double>(2,3) = p.fx; - Q_.at<double>(0,3) = p.cx; - Q_.at<double>(1,3) = p.cy; + Q_.at<double>(3, 2) = 1.0 / p.baseline; + Q_.at<double>(2, 3) = p.fx; + Q_.at<double>(0, 3) = p.cx; + Q_.at<double>(1, 3) = p.cy; // FIXME:(Nick) Update camera matrix also... _updateIntrinsics(); @@ -141,7 +177,6 @@ void Calibrate::_updateIntrinsics() { // TODO: pass parameters? Mat R1, R2, P1, P2; - std::pair<Mat, Mat> map1, map2; ftl::rgbd::Camera params(); if (this->value("use_intrinsics", true)) { @@ -155,28 +190,30 @@ void Calibrate::_updateIntrinsics() { // no rectification R1 = Mat::eye(Size(3, 3), CV_64FC1); R2 = R1; - P1 = M1_; - P2 = M2_; + P1 = Mat::zeros(Size(4, 3), CV_64FC1); + P2 = Mat::zeros(Size(4, 3), CV_64FC1); + K1_.copyTo(Mat(P1, cv::Rect(0, 0, 3, 3))); + K2_.copyTo(Mat(P2, cv::Rect(0, 0, 3, 3))); } // Set correct camera matrices for // getCameraMatrix(), getCameraMatrixLeft(), getCameraMatrixRight() - C_l_ = P1; - C_r_ = P2; + Kl_ = Mat(P1, cv::Rect(0, 0, 3, 3)); + Kr_ = Mat(P1, cv::Rect(0, 0, 3, 3)); + + initUndistortRectifyMap(K1_, D1_, R1, P1, img_size_, CV_32FC1, map1_.first, map2_.first); + initUndistortRectifyMap(K2_, D2_, R2, P2, img_size_, CV_32FC1, map1_.second, map2_.second); - initUndistortRectifyMap(M1_, D1_, R1, P1, img_size_, CV_32FC1, map1.first, map2.first); - initUndistortRectifyMap(M2_, D2_, R2, P2, img_size_, CV_32FC1, map1.second, map2.second); - // CHECK Is this thread safe!!!! - map1_gpu_.first.upload(map1.first); - map1_gpu_.second.upload(map1.second); - map2_gpu_.first.upload(map2.first); - map2_gpu_.second.upload(map2.second); + map1_gpu_.first.upload(map1_.first); + map1_gpu_.second.upload(map1_.second); + map2_gpu_.first.upload(map2_.first); + map2_gpu_.second.upload(map2_.second); } void Calibrate::rectifyStereo(GpuMat &l, GpuMat &r, Stream &stream) { // cv::cuda::remap() can not use same Mat for input and output - + GpuMat l_tmp(l.size(), l.type()); GpuMat r_tmp(r.size(), r.type()); cv::cuda::remap(l, l_tmp, map1_gpu_.first, map2_gpu_.first, cv::INTER_LINEAR, 0, cv::Scalar(), stream); @@ -186,6 +223,21 @@ void Calibrate::rectifyStereo(GpuMat &l, GpuMat &r, Stream &stream) { r = r_tmp; } +void Calibrate::rectifyStereo(cv::Mat &l, cv::Mat &r) { + // cv::cuda::remap() can not use same Mat for input and output + + cv::remap(l, l, map1_.first, map2_.first, cv::INTER_LINEAR, 0, cv::Scalar()); + cv::remap(r, r, map1_.second, map2_.second, cv::INTER_LINEAR, 0, cv::Scalar()); + + /*GpuMat l_tmp(l.size(), l.type()); + GpuMat r_tmp(r.size(), r.type()); + cv::cuda::remap(l, l_tmp, map1_gpu_.first, map2_gpu_.first, cv::INTER_LINEAR, 0, cv::Scalar(), stream); + cv::cuda::remap(r, r_tmp, map1_gpu_.second, map2_gpu_.second, cv::INTER_LINEAR, 0, cv::Scalar(), stream); + stream.waitForCompletion(); + l = l_tmp; + r = r_tmp;*/ +} + bool Calibrate::isCalibrated() { return calibrated_; } \ No newline at end of file diff --git a/components/rgbd-sources/src/calibrate.hpp b/components/rgbd-sources/src/calibrate.hpp index c5f4786831edb77435a4d205702e7909517829a9..4561b90a79129dd5a0d46d9d54bd005147d766a7 100644 --- a/components/rgbd-sources/src/calibrate.hpp +++ b/components/rgbd-sources/src/calibrate.hpp @@ -40,6 +40,11 @@ class Calibrate : public ftl::Configurable { */ void rectifyStereo(cv::cuda::GpuMat &l, cv::cuda::GpuMat &r, cv::cuda::Stream &stream); + /** + * Rectify and remove distortions from from images l and r using cv::remap() + */ + void rectifyStereo(cv::Mat &l, cv::Mat &r); + bool isCalibrated(); void updateCalibration(const ftl::rgbd::Camera &p); @@ -49,8 +54,9 @@ class Calibrate : public ftl::Configurable { * a 3D point cloud. */ const cv::Mat &getQ() const { return Q_; } - const cv::Mat &getCameraMatrixLeft() { return C_l_; } - const cv::Mat &getCameraMatrixRight() { return C_r_; } + + const cv::Mat &getCameraMatrixLeft() { return Kl_; } + const cv::Mat &getCameraMatrixRight() { return Kr_; } const cv::Mat &getCameraMatrix() { return getCameraMatrixLeft(); } private: @@ -60,15 +66,22 @@ private: private: bool calibrated_; + std::pair<cv::Mat, cv::Mat> map1_; + std::pair<cv::Mat, cv::Mat> map2_; std::pair<cv::cuda::GpuMat, cv::cuda::GpuMat> map1_gpu_; std::pair<cv::cuda::GpuMat, cv::cuda::GpuMat> map2_gpu_; - cv::Mat Q_; + // parameters for rectification, see cv::stereoRectify() documentation cv::Mat R_, T_, R1_, P1_, R2_, P2_; - cv::Mat M1_, D1_, M2_, D2_; - cv::Mat C_l_; - cv::Mat C_r_; + // disparity to depth matrix + cv::Mat Q_; + + // intrinsic paramters and distortion coefficients + cv::Mat K1_, D1_, K2_, D2_; + + cv::Mat Kl_; + cv::Mat Kr_; cv::Size img_size_; }; diff --git a/components/rgbd-sources/src/cuda_algorithms.hpp b/components/rgbd-sources/src/cuda_algorithms.hpp index 7c7d510ffebbb6ab4a42c7a2540b17d10c9a34e2..439c16cfc21fef08086bb69b7e84b1c8b49fec74 100644 --- a/components/rgbd-sources/src/cuda_algorithms.hpp +++ b/components/rgbd-sources/src/cuda_algorithms.hpp @@ -11,6 +11,9 @@ namespace ftl { namespace cuda { + void disparity_to_depth(const cv::cuda::GpuMat &disparity, cv::cuda::GpuMat &depth, + const ftl::rgbd::Camera &c, cv::cuda::Stream &stream); + /** * Disparity consistency algorithm. */ @@ -38,6 +41,9 @@ namespace cuda { void disparity_to_depth(const cv::cuda::GpuMat &disparity, cv::cuda::GpuMat &depth, const ftl::rgbd::Camera &c, cv::cuda::Stream &stream); + void optflow_filter(cv::cuda::GpuMat &disp, const cv::cuda::GpuMat &optflow, + cv::cuda::GpuMat &history, int n_max, float threshold, + cv::cuda::Stream &stream); } } diff --git a/components/rgbd-sources/src/disparity.cpp b/components/rgbd-sources/src/disparity.cpp index e92c72de765c7dcd69da1aa279de9181e3170087..7d9089c1ab5a2e47bd26ed87591ddb411dabf7f8 100644 --- a/components/rgbd-sources/src/disparity.cpp +++ b/components/rgbd-sources/src/disparity.cpp @@ -15,7 +15,11 @@ std::map<std::string, std::function<Disparity*(ftl::Configurable *, const std::s Disparity::Disparity(nlohmann::json &config) : ftl::Configurable(config), min_disp_(value("minimum",0)), - max_disp_(value("maximum", 256)) {} + max_disp_(value("maximum", 256)), + size_(value("width", 1280), value("height", 720)) + { + + } Disparity *Disparity::create(ftl::Configurable *parent, const std::string &name) { nlohmann::json &config = ftl::config::resolve((!parent->getConfig()[name].is_null()) ? parent->getConfig()[name] : ftl::config::resolve(parent->getConfig())[name]); // ftl::config::resolve(parent->getConfig()[name]); @@ -37,6 +41,28 @@ void Disparity::_register(const std::string &n, (*algorithms__)[n] = f; } +void Disparity::scaleInput( const cv::cuda::GpuMat& left_in, + const cv::cuda::GpuMat& right_in, + cv::cuda::GpuMat& left_out, + cv::cuda::GpuMat& right_out, + cv::cuda::Stream &stream) +{ + cv::cuda::resize(left_in, left_scaled_, size_, 0.0, 0.0, cv::INTER_CUBIC, stream); + left_out = left_scaled_; + cv::cuda::resize(right_in, right_scaled_, size_, 0.0, 0.0, cv::INTER_CUBIC, stream); + right_out = right_scaled_; +} + +void Disparity::scaleDisparity( const cv::Size& new_size, + cv::cuda::GpuMat& in, + cv::cuda::GpuMat& out, + cv::cuda::Stream& stream) +{ + cv::cuda::multiply(in, (double) new_size.width / (double) in.cols, in); + cv::cuda::resize(in, dispt_scaled_, new_size, 0.0, 0.0, cv::INTER_NEAREST, stream); + out = dispt_scaled_; +} + // TODO:(Nick) Add remaining algorithms /* #include "algorithms/rtcensus.hpp" diff --git a/components/rgbd-sources/src/disparity.hpp b/components/rgbd-sources/src/disparity.hpp index e7e78b277544afd87870e62ca5b833b15d9cfd6d..44215871d37b2944c08d072d63afd5bf871082e4 100644 --- a/components/rgbd-sources/src/disparity.hpp +++ b/components/rgbd-sources/src/disparity.hpp @@ -8,6 +8,7 @@ #include <opencv2/opencv.hpp> #include <nlohmann/json.hpp> #include <ftl/configurable.hpp> +#include <ftl/rgbd/frame.hpp> namespace ftl { namespace rgbd { @@ -26,13 +27,33 @@ class Disparity : public ftl::Configurable { virtual void setMinDisparity(size_t min) { min_disp_ = min; } virtual void setMaxDisparity(size_t max) { max_disp_ = max; } - virtual void setMask(cv::Mat &mask) { mask_l_ = mask; } + virtual void setMask(cv::Mat &mask) { mask_l_ = cv::cuda::GpuMat(mask); } + virtual void setMask(cv::cuda::GpuMat &mask) { mask_l_ = mask; } + void scaleInput(const cv::cuda::GpuMat& left_in, + const cv::cuda::GpuMat& right_in, + cv::cuda::GpuMat& left_out, + cv::cuda::GpuMat& right_out, + cv::cuda::Stream &stream); + + void scaleDisparity(const cv::Size &new_size, + cv::cuda::GpuMat& in, + cv::cuda::GpuMat& out, + cv::cuda::Stream &stream); + /** * Pure virtual function representing the actual computation of * disparity from left and right images to be implemented. */ - virtual void compute(const cv::cuda::GpuMat &l, const cv::cuda::GpuMat &r, cv::cuda::GpuMat &disp, cv::cuda::Stream &stream)=0; + virtual void compute(Frame &frame, cv::cuda::Stream &stream)=0; + virtual void compute(cv::cuda::GpuMat &l, cv::cuda::GpuMat &r, cv::cuda::GpuMat &disp, cv::cuda::Stream &stream) + { + // FIXME: What were these for? + //ftl::rgbd::Frame frame; + //frame.create<cv::cuda::GpuMat>(ftl::rgbd::Channel::Left) = l; + //frame.create<cv::cuda::GpuMat>(ftl::rgbd::Channel::Right) = r; + //frame.create<cv::cuda::GpuMat>(ftl::rgbd::Channel::Disparity) = disp; + } /** * Factory registration class. @@ -54,11 +75,15 @@ class Disparity : public ftl::Configurable { protected: static void _register(const std::string &n, std::function<Disparity*(ftl::Configurable *, const std::string &)> f); - protected: - //nlohmann::json &config_; +protected: int min_disp_; int max_disp_; - cv::Mat mask_l_; + cv::Size size_; + + cv::cuda::GpuMat left_scaled_; + cv::cuda::GpuMat right_scaled_; + cv::cuda::GpuMat dispt_scaled_; + cv::cuda::GpuMat mask_l_; private: static std::map<std::string,std::function<Disparity*(ftl::Configurable *, const std::string &)>> *algorithms__; @@ -69,4 +94,3 @@ class Disparity : public ftl::Configurable { } #endif // _FTL_DISPARITY_HPP_ - diff --git a/components/rgbd-sources/src/frame.cpp b/components/rgbd-sources/src/frame.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a56a19355526d9cdcdf25faaecdc67c05d09469d --- /dev/null +++ b/components/rgbd-sources/src/frame.cpp @@ -0,0 +1,201 @@ + +#include <ftl/rgbd/frame.hpp> + +using ftl::rgbd::Frame; +using ftl::rgbd::Channels; +using ftl::rgbd::Channel; + +static cv::Mat none; +static cv::cuda::GpuMat noneGPU; + +void Frame::reset() { + channels_.clear(); + gpu_.clear(); +} + +void Frame::download(Channel c, cv::cuda::Stream stream) { + download(Channels(c), stream); +} + +void Frame::upload(Channel c, cv::cuda::Stream stream) { + upload(Channels(c), stream); +} + +void Frame::download(Channels c, cv::cuda::Stream stream) { + for (size_t i=0u; i<Channels::kMax; ++i) { + if (c.has(i) && channels_.has(i) && gpu_.has(i)) { + data_[i].gpu.download(data_[i].host, stream); + gpu_ -= i; + } + } +} + +void Frame::upload(Channels c, cv::cuda::Stream stream) { + for (size_t i=0u; i<Channels::kMax; ++i) { + if (c.has(i) && channels_.has(i) && !gpu_.has(i)) { + data_[i].gpu.upload(data_[i].host, stream); + gpu_ += i; + } + } +} + +bool Frame::empty(ftl::rgbd::Channels channels) { + for (auto c : channels) { + if (empty(c)) return true; + } + return false; +} + +void Frame::swapTo(ftl::rgbd::Channels channels, Frame &f) { + f.reset(); + + // For all channels in this frame object + for (auto c : channels_) { + // Should we swap this channel? + if (channels.has(c)) { + // Does 'f' have this channel? + //if (!f.hasChannel(c)) { + // No, so create it first + // FIXME: Allocate the memory as well? + if (isCPU(c)) f.create<cv::Mat>(c); + else f.create<cv::cuda::GpuMat>(c); + //} + + auto &m1 = _get(c); + auto &m2 = f._get(c); + + cv::swap(m1.host, m2.host); + cv::cuda::swap(m1.gpu, m2.gpu); + + auto temptex = std::move(m2.tex); + m2.tex = std::move(m1.tex); + m1.tex = std::move(temptex); + } + } +} + +template<> cv::Mat& Frame::get(ftl::rgbd::Channel channel) { + if (channel == Channel::None) { + DLOG(WARNING) << "Cannot get the None channel from a Frame"; + none.release(); + return none; + } + + if (isGPU(channel)) { + download(Channels(channel)); + LOG(WARNING) << "Getting GPU channel on CPU without explicit 'download'"; + } + + // Add channel if not already there + if (!channels_.has(channel)) { + throw ftl::exception("Frame channel does not exist"); + } + + return _get(channel).host; +} + +template<> cv::cuda::GpuMat& Frame::get(ftl::rgbd::Channel channel) { + if (channel == Channel::None) { + DLOG(WARNING) << "Cannot get the None channel from a Frame"; + noneGPU.release(); + return noneGPU; + } + + if (isCPU(channel)) { + upload(Channels(channel)); + LOG(WARNING) << "Getting CPU channel on GPU without explicit 'upload'"; + } + + // Add channel if not already there + if (!channels_.has(channel)) { + throw ftl::exception("Frame channel does not exist"); + } + + return _get(channel).gpu; +} + +template<> const cv::Mat& Frame::get(ftl::rgbd::Channel channel) const { + if (channel == Channel::None) { + LOG(FATAL) << "Cannot get the None channel from a Frame"; + } + + if (isGPU(channel)) { + LOG(FATAL) << "Getting GPU channel on CPU without explicit 'download'"; + } + + if (!channels_.has(channel)) throw ftl::exception("Frame channel does not exist"); + + return _get(channel).host; +} + +template<> const cv::cuda::GpuMat& Frame::get(ftl::rgbd::Channel channel) const { + if (channel == Channel::None) { + LOG(FATAL) << "Cannot get the None channel from a Frame"; + } + + if (isCPU(channel)) { + LOG(FATAL) << "Getting CPU channel on GPU without explicit 'upload'"; + } + + // Add channel if not already there + if (!channels_.has(channel)) { + throw ftl::exception("Frame channel does not exist"); + } + + return _get(channel).gpu; +} + +template <> cv::Mat &Frame::create(ftl::rgbd::Channel c, const ftl::rgbd::FormatBase &f) { + if (c == Channel::None) { + throw ftl::exception("Cannot create a None channel"); + } + channels_ += c; + gpu_ -= c; + + auto &m = _get(c).host; + + if (!f.empty()) { + m.create(f.size(), f.cvType); + } + + return m; +} + +template <> cv::cuda::GpuMat &Frame::create(ftl::rgbd::Channel c, const ftl::rgbd::FormatBase &f) { + if (c == Channel::None) { + throw ftl::exception("Cannot create a None channel"); + } + channels_ += c; + gpu_ += c; + + auto &m = _get(c).gpu; + + if (!f.empty()) { + m.create(f.size(), f.cvType); + } + + return m; +} + +template <> cv::Mat &Frame::create(ftl::rgbd::Channel c) { + if (c == Channel::None) { + throw ftl::exception("Cannot create a None channel"); + } + channels_ += c; + gpu_ -= c; + + auto &m = _get(c).host; + return m; +} + +template <> cv::cuda::GpuMat &Frame::create(ftl::rgbd::Channel c) { + if (c == Channel::None) { + throw ftl::exception("Cannot create a None channel"); + } + channels_ += c; + gpu_ += c; + + auto &m = _get(c).gpu; + return m; +} + diff --git a/components/rgbd-sources/src/frameset.cpp b/components/rgbd-sources/src/frameset.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9b9a807d8599c23141b6c3806546bf8038ef30f9 --- /dev/null +++ b/components/rgbd-sources/src/frameset.cpp @@ -0,0 +1,39 @@ +#include <ftl/rgbd/frameset.hpp> + +using ftl::rgbd::FrameSet; +using ftl::rgbd::Channels; +using ftl::rgbd::Channel; + +void FrameSet::upload(ftl::rgbd::Channels c, cudaStream_t stream) { + for (auto &f : frames) { + f.upload(c, stream); + } +} + +void FrameSet::download(ftl::rgbd::Channels c, cudaStream_t stream) { + for (auto &f : frames) { + f.download(c, stream); + } +} + +void FrameSet::swapTo(ftl::rgbd::FrameSet &fs) { + UNIQUE_LOCK(fs.mtx, lk); + + //if (fs.frames.size() != frames.size()) { + // Assume "this" is correct and "fs" is not. + fs.sources.clear(); + for (auto s : sources) fs.sources.push_back(s); + fs.frames.resize(frames.size()); + //} + + fs.timestamp = timestamp; + fs.count = static_cast<int>(count); + fs.stale = stale; + fs.mask = static_cast<unsigned int>(mask); + + for (size_t i=0; i<frames.size(); ++i) { + frames[i].swapTo(Channels::All(), fs.frames[i]); + } + + stale = true; +} diff --git a/components/rgbd-sources/src/group.cpp b/components/rgbd-sources/src/group.cpp index f548806d5b78b00428269f1f4c7d7c5bced8a197..96ca3a82fd3e306656f047d4971ce4a29b00fe48 100644 --- a/components/rgbd-sources/src/group.cpp +++ b/components/rgbd-sources/src/group.cpp @@ -1,26 +1,62 @@ #include <ftl/rgbd/group.hpp> #include <ftl/rgbd/source.hpp> +#include <ftl/timer.hpp> + +#include <chrono> using ftl::rgbd::Group; using ftl::rgbd::Source; using ftl::rgbd::kFrameBufferSize; using std::vector; +using std::chrono::milliseconds; +using std::this_thread::sleep_for; +using ftl::rgbd::Channel; Group::Group() : framesets_(kFrameBufferSize), head_(0) { framesets_[0].timestamp = -1; + jobs_ = 0; + skip_ = false; + //setFPS(20); + + mspf_ = ftl::timer::getInterval(); + name_ = "NoName"; + + setLatency(5); } Group::~Group() { + for (auto s : sources_) { + s->removeCallback(); + } + main_id_.cancel(); + swap_id_.cancel(); + cap_id_.cancel(); + + UNIQUE_LOCK(mutex_, lk); + // Make sure all jobs have finished + while (jobs_ > 0) { + sleep_for(milliseconds(10)); + } } +//void Group::setFPS(int fps) { +// mspf_ = 1000 / fps; +// ftl::timer::setInterval(mspf_); +//} + void Group::addSource(ftl::rgbd::Source *src) { UNIQUE_LOCK(mutex_, lk); size_t ix = sources_.size(); sources_.push_back(src); - src->setCallback([this,ix](int64_t timestamp, const cv::Mat &rgb, const cv::Mat &depth) { + src->setCallback([this,ix,src](int64_t timestamp, cv::Mat &rgb, cv::Mat &depth) { if (timestamp == 0) return; + + auto chan = src->getChannel(); + + //LOG(INFO) << "SRC CB: " << timestamp << " (" << framesets_[head_].timestamp << ")"; + UNIQUE_LOCK(mutex_, lk); if (timestamp > framesets_[head_].timestamp) { // Add new frameset @@ -35,79 +71,257 @@ void Group::addSource(ftl::rgbd::Source *src) { for (size_t i=0; i<kFrameBufferSize; ++i) { FrameSet &fs = framesets_[(head_+kFrameBufferSize-i) % kFrameBufferSize]; if (fs.timestamp == timestamp) { + lk.unlock(); + SHARED_LOCK(fs.mtx, lk2); + //LOG(INFO) << "Adding frame: " << ix << " for " << timestamp; - rgb.copyTo(fs.channel1[ix]); - depth.copyTo(fs.channel2[ix]); + // Ensure channels match source mat format + //fs.channel1[ix].create(rgb.size(), rgb.type()); + //fs.channel2[ix].create(depth.size(), depth.type()); + fs.frames[ix].create<cv::Mat>(Channel::Colour, Format<uchar3>(rgb.size())); //.create(rgb.size(), rgb.type()); + if (chan != Channel::None) fs.frames[ix].create<cv::Mat>(chan, ftl::rgbd::FormatBase(depth.cols, depth.rows, depth.type())); //.create(depth.size(), depth.type()); + + //cv::swap(rgb, fs.channel1[ix]); + //cv::swap(depth, fs.channel2[ix]); + cv::swap(rgb, fs.frames[ix].get<cv::Mat>(Channel::Colour)); + if (chan != Channel::None) cv::swap(depth, fs.frames[ix].get<cv::Mat>(chan)); + ++fs.count; fs.mask |= (1 << ix); + if (fs.count == sources_.size()) { + //LOG(INFO) << "COMPLETE SET: " << fs.timestamp; + } else if (fs.count > sources_.size()) { + LOG(ERROR) << "Too many frames for frame set: " << fs.timestamp << " sources=" << sources_.size(); + } else { + //LOG(INFO) << "INCOMPLETE SET (" << ix << "): " << fs.timestamp; + } + if (callback_ && fs.count == sources_.size()) { - //LOG(INFO) << "DOING CALLBACK"; - if (callback_(fs)) { - //sources_[ix]->grab(); - //LOG(INFO) << "GRAB"; + try { + if (callback_(fs)) { + // TODO: Remove callback if returns false? + } + } catch (...) { + LOG(ERROR) << "Exception in group callback"; } + + // Reset count to prevent multiple reads of these frames + //fs.count = 0; } return; } } - LOG(WARNING) << "Frame timestamp not found in buffer"; + DLOG(WARNING) << "Frame timestamp not found in buffer"; }); } -// TODO: This should be a callback -// Callback returns true if it wishes to continue receiving frames. -void Group::sync(int N, int B) { - for (auto s : sources_) { - s->grab(N,B); +void Group::addGroup(Group *grp) { + +} + +void Group::_retrieveJob(ftl::rgbd::Source *src) { + try { + src->retrieve(); + } catch (std::exception &ex) { + LOG(ERROR) << "Exception when retrieving frame"; + LOG(ERROR) << ex.what(); + } + catch (...) { + LOG(ERROR) << "Unknown exception when retrieving frame"; } } -void Group::sync(std::function<bool(const ftl::rgbd::FrameSet &)> cb) { - callback_ = cb; - sync(-1,-1); +void Group::_computeJob(ftl::rgbd::Source *src) { + try { + src->compute(); + } catch (std::exception &ex) { + LOG(ERROR) << "Exception when computing frame"; + LOG(ERROR) << ex.what(); + } + catch (...) { + LOG(ERROR) << "Unknown exception when computing frame"; + } } -bool Group::getFrames(ftl::rgbd::FrameSet &fs, bool complete) { - // Use oldest frameset or search back until first complete set is found? - if (complete) { - UNIQUE_LOCK(mutex_, lk); - // Search backwards to find match - for (size_t i=0; i<kFrameBufferSize; ++i) { - FrameSet &f = framesets_[(head_+kFrameBufferSize-i) % kFrameBufferSize]; - if (f.count == sources_.size()) { - LOG(INFO) << "Complete set found"; - fs = f; // FIXME: This needs to move or copy safely... - return true; +void Group::sync(std::function<bool(ftl::rgbd::FrameSet &)> cb) { + if (latency_ == 0) { + callback_ = cb; + } + + // 1. Capture camera frames with high precision + cap_id_ = ftl::timer::add(ftl::timer::kTimerHighPrecision, [this](int64_t ts) { + skip_ = jobs_ != 0; // Last frame not finished so skip all steps + + if (skip_) return true; + + last_ts_ = ts; + for (auto s : sources_) { + s->capture(ts); + } + + return true; + }); + + // 2. After capture, swap any internal source double buffers + swap_id_ = ftl::timer::add(ftl::timer::kTimerSwap, [this](int64_t ts) { + if (skip_) return true; + for (auto s : sources_) { + s->swap(); + } + return true; + }); + + // 3. Issue IO retrieve ad compute jobs before finding a valid + // frame at required latency to pass to callback. + main_id_ = ftl::timer::add(ftl::timer::kTimerMain, [this,cb](int64_t ts) { + if (skip_) return true; + jobs_++; + + for (auto s : sources_) { + jobs_ += 2; + + ftl::pool.push([this,s](int id) { + _retrieveJob(s); + --jobs_; + }); + ftl::pool.push([this,s](int id) { + _computeJob(s); + --jobs_; + }); + } + + // Find a previous frameset and specified latency and do the sync + // callback with that frameset. + if (latency_ > 0) { + ftl::rgbd::FrameSet *fs = nullptr; + + UNIQUE_LOCK(mutex_, lk); + fs = _getFrameset(latency_); + + if (fs) { + UNIQUE_LOCK(fs->mtx, lk2); + lk.unlock(); + + try { + cb(*fs); + //LOG(INFO) << "Frameset processed (" << name_ << "): " << fs->timestamp; + } catch(...) { + LOG(ERROR) << "Exception in group sync callback"; + } + + // The buffers are invalid after callback so mark stale + fs->stale = true; + } else { + //LOG(INFO) << "NO FRAME FOUND: " << last_ts_ - latency_*mspf_; } } - LOG(WARNING) << "No complete frame set found"; - return false; - } - return false; + jobs_--; + return true; + }); + + ftl::timer::start(true); +} + +//ftl::rgbd::FrameSet &Group::_getRelativeFrameset(int rel) { +// int idx = (rel < 0) ? (head_+kFrameBufferSize+rel)%kFrameBufferSize : (head_+rel)%kFrameBufferSize; +// return framesets_[idx]; +//} + +ftl::rgbd::FrameSet *Group::_getFrameset(int f) { + const int64_t lookfor = last_ts_-f*mspf_; + + for (size_t i=1; i<kFrameBufferSize; ++i) { + int idx = (head_+kFrameBufferSize-i)%kFrameBufferSize; + + if (framesets_[idx].timestamp == lookfor && framesets_[idx].count != sources_.size()) { + LOG(INFO) << "Required frame not complete (timestamp=" << (framesets_[idx].timestamp) << " buffer=" << i << ")"; + //framesets_[idx].stale = true; + continue; + } + + if (framesets_[idx].stale) return nullptr; + + if (framesets_[idx].timestamp == lookfor && framesets_[idx].count == sources_.size()) { + //framesets_[idx].stale = false; + return &framesets_[idx]; + } else if (framesets_[idx].timestamp < lookfor && framesets_[idx].count == sources_.size()) { + //framesets_[idx].stale = true; + return &framesets_[idx]; + } + + } + return nullptr; } void Group::_addFrameset(int64_t timestamp) { - int count = (framesets_[head_].timestamp == -1) ? 1 : (timestamp - framesets_[head_].timestamp) / 40; - // Must make sure to also insert missing framesets - //LOG(INFO) << "Adding " << count << " framesets for " << timestamp << " head=" << framesets_[head_].timestamp; + int count = (framesets_[head_].timestamp == -1) ? 200 : (timestamp - framesets_[head_].timestamp) / mspf_; + //LOG(INFO) << "Massive timestamp difference: " << count; + + // Allow for massive timestamp changes (Windows clock adjust) + // Only add a single frameset for large changes + if (count < -int(kFrameBufferSize) || count >= kFrameBufferSize-1) { + head_ = (head_+1) % kFrameBufferSize; - //if (count > 10 || count < 1) return; + #ifdef DEBUG_MUTEX + std::unique_lock<std::shared_timed_mutex> lk(framesets_[head_].mtx, std::defer_lock); + #else + std::unique_lock<std::shared_mutex> lk(framesets_[head_].mtx, std::defer_lock); + #endif + if (!lk.try_lock()) { + LOG(ERROR) << "Frameset in use!!"; + return; + } + framesets_[head_].timestamp = timestamp; + framesets_[head_].count = 0; + framesets_[head_].mask = 0; + framesets_[head_].stale = false; + //framesets_[head_].channel1.resize(sources_.size()); + //framesets_[head_].channel2.resize(sources_.size()); + framesets_[head_].frames.resize(sources_.size()); + if (framesets_[head_].sources.size() != sources_.size()) { + framesets_[head_].sources.clear(); + for (auto s : sources_) framesets_[head_].sources.push_back(s); + } + return; + } + + if (count < 1) return; + + // Must make sure to also insert missing framesets for (int i=0; i<count; ++i) { - int64_t lt = (framesets_[head_].timestamp == -1) ? timestamp-40 : framesets_[head_].timestamp; + int64_t lt = (framesets_[head_].timestamp == -1) ? timestamp-mspf_ : framesets_[head_].timestamp; head_ = (head_+1) % kFrameBufferSize; - framesets_[head_].timestamp = lt+40; + + #ifdef DEBUG_MUTEX + std::unique_lock<std::shared_timed_mutex> lk(framesets_[head_].mtx, std::defer_lock); + #else + std::unique_lock<std::shared_mutex> lk(framesets_[head_].mtx, std::defer_lock); + #endif + if (!lk.try_lock()) { + LOG(ERROR) << "Frameset in use!! (" << name_ << ") " << framesets_[head_].timestamp << " stale=" << framesets_[head_].stale; + continue; + } + framesets_[head_].timestamp = lt+mspf_; framesets_[head_].count = 0; framesets_[head_].mask = 0; - framesets_[head_].channel1.resize(sources_.size()); - framesets_[head_].channel2.resize(sources_.size()); + framesets_[head_].stale = false; + //framesets_[head_].channel1.resize(sources_.size()); + //framesets_[head_].channel2.resize(sources_.size()); + framesets_[head_].frames.resize(sources_.size()); - framesets_[head_].sources.clear(); - for (auto s : sources_) framesets_[head_].sources.push_back(s); + if (framesets_[head_].sources.size() != sources_.size()) { + framesets_[head_].sources.clear(); + for (auto s : sources_) framesets_[head_].sources.push_back(s); + } } } +void Group::setName(const std::string &name) { + name_ = name; +} + diff --git a/components/rgbd-sources/src/image.hpp b/components/rgbd-sources/src/image.hpp index b389fd176ec246db3ca51180589673817897eb03..2e2391b7cb95b0c7b120d2992cf3e66c91ad3a1a 100644 --- a/components/rgbd-sources/src/image.hpp +++ b/components/rgbd-sources/src/image.hpp @@ -14,7 +14,9 @@ class ImageSource : public ftl::rgbd::detail::Source { } - bool grab(int n, int b) { return false; }; + bool capture(int64_t ts) { timestamp_ = ts; return true; } + bool retrieve() { return true; } + bool compute(int n, int b) { return false; }; bool isReady() { return false; }; }; diff --git a/components/rgbd-sources/src/local.cpp b/components/rgbd-sources/src/local.cpp index 410cb9f96c0a1c1ca48cadd52300e25b260c3aad..03b80ff52f60c95d374cfee289c0b3880bb766bd 100644 --- a/components/rgbd-sources/src/local.cpp +++ b/components/rgbd-sources/src/local.cpp @@ -9,11 +9,13 @@ #include <thread> #include "local.hpp" +#include "calibrate.hpp" #include <opencv2/core.hpp> #include <opencv2/opencv.hpp> #include <opencv2/xphoto.hpp> using ftl::rgbd::detail::LocalSource; +using ftl::rgbd::detail::Calibrate; using cv::Mat; using cv::VideoCapture; using cv::Rect; @@ -27,28 +29,15 @@ using std::this_thread::sleep_for; LocalSource::LocalSource(nlohmann::json &config) : Configurable(config), timestamp_(0.0) { - REQUIRED({ - {"flip","Switch left and right views","boolean"}, - {"flip_vert","Rotate image 180 degrees","boolean"}, - {"nostereo","Force single camera mode","boolean"}, - {"width","Pixel width of camera source","number"}, - {"height","Pixel height of camera source","number"}, - {"max_fps","Maximum frames per second","number"}, - {"scale","Change the input image or video scaling","number"} - }); - - flip_ = value("flip", false); - flip_v_ = value("flip_vert", false); nostereo_ = value("nostereo", false); - downsize_ = value("scale", 1.0f); // Use cameras camera_a_ = new VideoCapture; LOG(INFO) << "Cameras check... "; - camera_a_->open((flip_) ? 1 : 0); + camera_a_->open(0); if (!nostereo_) { - camera_b_ = new VideoCapture((flip_) ? 0 : 1); + camera_b_ = new VideoCapture(1); } else { camera_b_ = nullptr; } @@ -82,26 +71,18 @@ LocalSource::LocalSource(nlohmann::json &config) stereo_ = true; } - tps_ = 1.0 / value("max_fps", 25.0); + // Allocate page locked host memory for fast GPU transfer + left_hm_ = cv::cuda::HostMem(height_, width_, CV_8UC3); + right_hm_ = cv::cuda::HostMem(height_, width_, CV_8UC3); } LocalSource::LocalSource(nlohmann::json &config, const string &vid) : Configurable(config), timestamp_(0.0) { - REQUIRED({ - {"flip","Switch left and right views","boolean"}, - {"flip_vert","Rotate image 180 degrees","boolean"}, - {"nostereo","Force single camera mode","boolean"}, - {"width","Pixel width of camera source","number"}, - {"height","Pixel height of camera source","number"}, - {"max_fps","Maximum frames per second","number"}, - {"scale","Change the input image or video scaling","number"} - }); - - flip_ = value("flip", false); - flip_v_ = value("flip_vert", false); + //flip_ = value("flip", false); + //flip_v_ = value("flip_vert", false); nostereo_ = value("nostereo", false); - downsize_ = value("scale", 1.0f); + //downsize_ = value("scale", 1.0f); if (vid == "") { LOG(FATAL) << "No video file specified"; @@ -138,10 +119,14 @@ LocalSource::LocalSource(nlohmann::json &config, const string &vid) stereo_ = false; } - tps_ = 1.0 / value("max_fps", 25.0); + // Allocate page locked host memory for fast GPU transfer + left_hm_ = cv::cuda::HostMem(height_, width_, CV_8UC3); + right_hm_ = cv::cuda::HostMem(height_, width_, CV_8UC3); + + //tps_ = 1.0 / value("max_fps", 25.0); } -bool LocalSource::left(cv::Mat &l) { +/*bool LocalSource::left(cv::Mat &l) { if (!camera_a_) return false; if (!camera_a_->grab()) { @@ -174,9 +159,9 @@ bool LocalSource::left(cv::Mat &l) { } return true; -} +}*/ -bool LocalSource::right(cv::Mat &r) { +/*bool LocalSource::right(cv::Mat &r) { if (!camera_a_->grab()) { LOG(ERROR) << "Unable to grab from camera A"; return false; @@ -212,10 +197,9 @@ bool LocalSource::right(cv::Mat &r) { } return true; -} +}*/ -bool LocalSource::get(cv::cuda::GpuMat &l_out, cv::cuda::GpuMat &r_out, cv::cuda::Stream &stream) { - Mat l, r; +bool LocalSource::grab() { if (!camera_a_) return false; if (!camera_a_->grab()) { @@ -238,8 +222,20 @@ bool LocalSource::get(cv::cuda::GpuMat &l_out, cv::cuda::GpuMat &r_out, cv::cuda timestamp_ = timestamp; + return true; +} + +bool LocalSource::get(cv::cuda::GpuMat &l_out, cv::cuda::GpuMat &r_out, Calibrate *c, cv::cuda::Stream &stream) { + Mat l, r; + + // Use page locked memory + l = left_hm_.createMatHeader(); + r = right_hm_.createMatHeader(); + + if (!camera_a_) return false; + if (camera_b_ || !stereo_) { - if (!camera_a_->retrieve(left_)) { + if (!camera_a_->retrieve(l)) { LOG(ERROR) << "Unable to read frame from camera A"; return false; } @@ -255,23 +251,23 @@ bool LocalSource::get(cv::cuda::GpuMat &l_out, cv::cuda::GpuMat &r_out, cv::cuda } int resx = frame.cols / 2; - if (flip_) { - r = Mat(frame, Rect(0, 0, resx, frame.rows)); - left_ = Mat(frame, Rect(resx, 0, frame.cols-resx, frame.rows)); - } else { - left_ = Mat(frame, Rect(0, 0, resx, frame.rows)); + //if (flip_) { + // r = Mat(frame, Rect(0, 0, resx, frame.rows)); + // l = Mat(frame, Rect(resx, 0, frame.cols-resx, frame.rows)); + //} else { + l = Mat(frame, Rect(0, 0, resx, frame.rows)); r = Mat(frame, Rect(resx, 0, frame.cols-resx, frame.rows)); - } + //} } - if (downsize_ != 1.0f) { + /*if (downsize_ != 1.0f) { // cv::cuda::resize() cv::resize(left_, left_, cv::Size((int)(left_.cols * downsize_), (int)(left_.rows * downsize_)), 0, 0, cv::INTER_LINEAR); cv::resize(r, r, cv::Size((int)(r.cols * downsize_), (int)(r.rows * downsize_)), 0, 0, cv::INTER_LINEAR); - } + }*/ // Note: this seems to be too slow on CPU... /*cv::Ptr<cv::xphoto::WhiteBalancer> wb; @@ -279,15 +275,17 @@ bool LocalSource::get(cv::cuda::GpuMat &l_out, cv::cuda::GpuMat &r_out, cv::cuda wb->balanceWhite(l, l); wb->balanceWhite(r, r);*/ - if (flip_v_) { + /*if (flip_v_) { Mat tl, tr; cv::flip(left_, tl, 0); cv::flip(r, tr, 0); left_ = tl; r = tr; - } + }*/ + + c->rectifyStereo(l, r); - l_out.upload(left_, stream); + l_out.upload(l, stream); r_out.upload(r, stream); return true; diff --git a/components/rgbd-sources/src/local.hpp b/components/rgbd-sources/src/local.hpp index e3fcb91bd585d8090cfb50a8ee83bf49dd78f98d..9f21f5cf79b86f7cdee9455052c55a829eaf0da7 100644 --- a/components/rgbd-sources/src/local.hpp +++ b/components/rgbd-sources/src/local.hpp @@ -15,19 +15,20 @@ namespace ftl { namespace rgbd { namespace detail { +class Calibrate; + class LocalSource : public Configurable { public: explicit LocalSource(nlohmann::json &config); LocalSource(nlohmann::json &config, const std::string &vid); - bool left(cv::Mat &m); - bool right(cv::Mat &m); - bool get(cv::cuda::GpuMat &l, cv::cuda::GpuMat &r, cv::cuda::Stream &stream); + //bool left(cv::Mat &m); + //bool right(cv::Mat &m); + bool grab(); + bool get(cv::cuda::GpuMat &l, cv::cuda::GpuMat &r, Calibrate *c, cv::cuda::Stream &stream); unsigned int width() const { return width_; } unsigned int height() const { return height_; } - - cv::Mat &cachedLeft() { return left_; } //void setFramerate(float fps); //float getFramerate() const; @@ -38,18 +39,20 @@ class LocalSource : public Configurable { private: double timestamp_; - double tps_; + //double tps_; bool stereo_; //float fps_; - bool flip_; - bool flip_v_; + //bool flip_; + //bool flip_v_; bool nostereo_; - float downsize_; + //float downsize_; cv::VideoCapture *camera_a_; cv::VideoCapture *camera_b_; unsigned int width_; unsigned int height_; - cv::Mat left_; + + cv::cuda::HostMem left_hm_; + cv::cuda::HostMem right_hm_; }; } diff --git a/components/rgbd-sources/src/middlebury_source.cpp b/components/rgbd-sources/src/middlebury_source.cpp index 6f38f7bdcf9cd9a1de8812934e8da9c460face47..e82167fdcf6b4e2bfd1a38a00b50e3cdceeb2aef 100644 --- a/components/rgbd-sources/src/middlebury_source.cpp +++ b/components/rgbd-sources/src/middlebury_source.cpp @@ -3,6 +3,8 @@ #include "disparity.hpp" #include "cuda_algorithms.hpp" +#include "cuda_algorithms.hpp" + using ftl::rgbd::detail::MiddleburySource; using ftl::rgbd::detail::Disparity; using std::string; @@ -14,14 +16,13 @@ MiddleburySource::MiddleburySource(ftl::rgbd::Source *host) static bool loadMiddleburyCalib(const std::string &filename, ftl::rgbd::Camera ¶ms, double scaling) { FILE* fp = fopen(filename.c_str(), "r"); - char buff[512]; - float cam0[3][3]; + float cam0[3][3] = {}; float cam1[3][3]; - float doffs; - float baseline; - int width; - int height; + float doffs = 0.0f; + float baseline = 0.0f; + int width = 0; + int height = 0; int ndisp; int isint; int vmin; @@ -29,8 +30,8 @@ static bool loadMiddleburyCalib(const std::string &filename, ftl::rgbd::Camera & float dyavg; float dymax; - if (fp != nullptr) - { + if (fp != nullptr) { + char buff[512]; if (fgets(buff, sizeof(buff), fp) != nullptr) sscanf(buff, "cam0 = [%f %f %f; %f %f %f; %f %f %f]\n", &cam0[0][0], &cam0[0][1], &cam0[0][2], &cam0[1][0], &cam0[1][1], &cam0[1][2], &cam0[2][0], &cam0[2][1], &cam0[2][2]); if (fgets(buff, sizeof(buff), fp) != nullptr) sscanf(buff, "cam1 = [%f %f %f; %f %f %f; %f %f %f]\n", &cam1[0][0], &cam1[0][1], &cam1[0][2], &cam1[1][0], &cam1[1][1], &cam1[1][2], &cam1[2][0], &cam1[2][1], &cam1[2][2]); if (fgets(buff, sizeof(buff), fp) != nullptr) sscanf(buff, "doffs = %f\n", &doffs); @@ -120,7 +121,7 @@ MiddleburySource::MiddleburySource(ftl::rgbd::Source *host, const string &dir) mask_l_ = (mask_l == 0); if (!host_->getConfig()["disparity"].is_object()) { - host_->getConfig()["disparity"] = {{"algorithm","libsgm"}}; + host_->getConfig()["disparity"] = ftl::config::json_t{{"algorithm","libsgm"}}; } disp_ = Disparity::create(host_, "disparity"); @@ -142,43 +143,6 @@ MiddleburySource::MiddleburySource(ftl::rgbd::Source *host, const string &dir) ready_ = true; } -static void disparityToDepth(const cv::cuda::GpuMat &disparity, cv::cuda::GpuMat &depth, - const ftl::rgbd::Camera &c, cv::cuda::Stream &stream) { - double val = c.baseline * c.fx; - cv::cuda::add(disparity, c.doffs, depth, cv::noArray(), -1, stream); - cv::cuda::divide(val, depth, depth, 1.0f / 1000.0f, -1, stream); -} - -/*static void disparityToDepthTRUE(const cv::Mat &disp, cv::Mat &depth, const ftl::rgbd::Camera &c) { - using namespace cv; - - double doffs = 270.821 * 0.3; - - Matx44d Q( - 1.0,0.0,0.0,c.cx, - 0.0,1.0,0.0,c.cy, - 0.0,0.0,0.0,c.fx, - 0.0,0.0,1.0/c.baseline,0.0); - - for( int y = 0; y < disp.rows; y++ ) - { - const float* sptr = disp.ptr<float>(y); - float* dptr = depth.ptr<float>(y); - - for( int x = 0; x < disp.cols; x++) - { - double d = sptr[x] + doffs; - Vec4d homg_pt = Q*Vec4d(x, y, d, 1.0); - auto dvec = Vec3d(homg_pt.val); - dvec /= homg_pt[3]; - dptr[x] = dvec[2] / 1000.0; - - //if( fabs(d-minDisparity) <= FLT_EPSILON ) - // dptr[x][2] = bigZ; - } - } -}*/ - void MiddleburySource::_performDisparity() { if (depth_tmp_.empty()) depth_tmp_ = cv::cuda::GpuMat(left_.size(), CV_32FC1); if (disp_tmp_.empty()) disp_tmp_ = cv::cuda::GpuMat(left_.size(), CV_32FC1); @@ -195,7 +159,7 @@ void MiddleburySource::_performDisparity() { //disparityToDepthTRUE(depth_, depth_, params_); } -bool MiddleburySource::grab(int n, int b) { +bool MiddleburySource::compute(int n, int b) { //_performDisparity(); return true; } diff --git a/components/rgbd-sources/src/middlebury_source.hpp b/components/rgbd-sources/src/middlebury_source.hpp index 5f0e2be538a069747d633e3e4b9483eb8a7a75b2..d273d23a66d67c6618c0ac4a2062a780d9a3bddb 100644 --- a/components/rgbd-sources/src/middlebury_source.hpp +++ b/components/rgbd-sources/src/middlebury_source.hpp @@ -15,11 +15,13 @@ class Disparity; class MiddleburySource : public detail::Source { public: - MiddleburySource(ftl::rgbd::Source *); + explicit MiddleburySource(ftl::rgbd::Source *); MiddleburySource(ftl::rgbd::Source *, const std::string &dir); ~MiddleburySource() {}; - bool grab(int n, int b); + bool capture(int64_t ts) { timestamp_ = ts; return true; } + bool retrieve() { return true; } + bool compute(int n, int b); bool isReady() { return ready_; } private: diff --git a/components/rgbd-sources/src/net.cpp b/components/rgbd-sources/src/net.cpp index 42b2c8fd60ca0b505cb11e956809937fd522fe5b..ba485c9402d888be0636902f3962cce2dd1e85ed 100644 --- a/components/rgbd-sources/src/net.cpp +++ b/components/rgbd-sources/src/net.cpp @@ -3,11 +3,14 @@ #include <thread> #include <chrono> #include <tuple> +#include <bitset> #include "colour.hpp" #include <ftl/rgbd/streamer.hpp> +using ftl::rgbd::detail::NetFrame; +using ftl::rgbd::detail::NetFrameQueue; using ftl::rgbd::detail::NetSource; using ftl::net::Universe; using ftl::UUID; @@ -17,8 +20,57 @@ using std::vector; using std::this_thread::sleep_for; using std::chrono::milliseconds; using std::tuple; +using ftl::rgbd::Channel; -bool NetSource::_getCalibration(Universe &net, const UUID &peer, const string &src, ftl::rgbd::Camera &p, ftl::rgbd::channel_t chan) { +// ===== NetFrameQueue ========================================================= + +NetFrameQueue::NetFrameQueue(int size) : frames_(size) { + for (auto &f : frames_) f.timestamp = -1; +} + +NetFrameQueue::~NetFrameQueue() { + +} + +NetFrame &NetFrameQueue::getFrame(int64_t ts, const cv::Size &s, int c1type, int c2type) { + UNIQUE_LOCK(mtx_, lk); + + // Find matching timestamp + for (auto &f : frames_) { + if (f.timestamp == ts) return f; + } + + // No match so find an empty slot + for (auto &f : frames_) { + if (f.timestamp == -1) { + f.timestamp = ts; + f.chunk_count = 0; + f.chunk_total = 0; + f.tx_size = 0; + f.channel1.create(s, c1type); + f.channel2.create(s, c2type); + return f; + } + } + + // No empty slot, so give a fatal error + for (auto &f : frames_) { + LOG(ERROR) << "Stale frame: " << f.timestamp << " - " << f.chunk_count; + } + LOG(FATAL) << "Net Frame Queue not large enough: " << ts; + // FIXME: (Nick) Could auto resize the queue. + return frames_[0]; // To avoid missing return error... +} + +void NetFrameQueue::freeFrame(NetFrame &f) { + UNIQUE_LOCK(mtx_, lk); + f.timestamp = -1; +} + + +// ===== NetSource ============================================================= + +bool NetSource::_getCalibration(Universe &net, const UUID &peer, const string &src, ftl::rgbd::Camera &p, ftl::rgbd::Channel chan) { try { while(true) { auto [cap,buf] = net.call<tuple<unsigned int,vector<unsigned char>>>(peer_, "source_details", src, chan); @@ -60,11 +112,15 @@ bool NetSource::_getCalibration(Universe &net, const UUID &peer, const string &s } NetSource::NetSource(ftl::rgbd::Source *host) - : ftl::rgbd::detail::Source(host), active_(false), minB_(9), maxN_(1), current_frame_(0) { + : ftl::rgbd::detail::Source(host), active_(false), minB_(9), maxN_(1), adaptive_(0), queue_(3) { gamma_ = host->value("gamma", 1.0f); temperature_ = host->value("temperature", 6500); default_quality_ = host->value("quality", 0); + last_bitrate_ = 0; + + decoder_c1_ = nullptr; + decoder_c2_ = nullptr; host->on("gamma", [this,host](const ftl::config::Event&) { gamma_ = host->value("gamma", 1.0f); @@ -90,10 +146,18 @@ NetSource::NetSource(ftl::rgbd::Source *host) host_->getNet()->send(peer_, "update_cfg", host_->getURI() + "/baseline", host_->getConfig()["baseline"].dump()); }); + host->on("doffs", [this,host](const ftl::config::Event&) { + params_.doffs = host_->value("doffs", params_.doffs); + host_->getNet()->send(peer_, "update_cfg", host_->getURI() + "/doffs", host_->getConfig()["doffs"].dump()); + }); + host->on("quality", [this,host](const ftl::config::Event&) { default_quality_ = host->value("quality", 0); }); + abr_.setMaximumBitrate(host->value("max_bitrate", -1)); + abr_.setMinimumBitrate(host->value("min_bitrate", -1)); + _updateURI(); h_ = host_->getNet()->onConnect([this](ftl::net::Peer *p) { @@ -104,6 +168,9 @@ NetSource::NetSource(ftl::rgbd::Source *host) } NetSource::~NetSource() { + if (decoder_c1_) ftl::codecs::free(decoder_c1_); + if (decoder_c2_) ftl::codecs::free(decoder_c2_); + if (uri_.size() > 0) { host_->getNet()->unbind(uri_); } @@ -111,111 +178,136 @@ NetSource::~NetSource() { host_->getNet()->removeCallback(h_); } -void NetSource::_recvChunk(int64_t frame, int chunk, bool delta, const vector<unsigned char> &jpg, const vector<unsigned char> &d) { - cv::Mat tmp_rgb, tmp_depth; +/*void NetSource::_checkAdaptive(int64_t ts) { + const int64_t current = ftl::timer::get_time(); + int64_t net_latency = current - ts; + + // Only change bit rate gradually + if (current - last_br_change_ > ftl::rgbd::detail::kAdaptationRate) { + // Was this frame late? + if (adaptive_ < ftl::rgbd::detail::kMaxBitrateLevels && net_latency > ftl::rgbd::detail::kLatencyThreshold) { + slow_log_ = (slow_log_ << 1) + 1; + std::bitset<32> bs(slow_log_); + + // Enough late frames to reduce bit rate + if (bs.count() > ftl::rgbd::detail::kSlowFramesThreshold) { + adaptive_++; + slow_log_ = 0; + last_br_change_ = current; + LOG(WARNING) << "Adjust bitrate to " << adaptive_; + } + // No late frames in recent history... + } else if (adaptive_ > 0 && slow_log_ == 0) { + // TODO: (Nick) Don't change bitrate up so quickly as down? + // Try a higher bitrate again? + adaptive_--; + } + } +}*/ + +void NetSource::_createDecoder(int chan, const ftl::codecs::Packet &pkt) { + UNIQUE_LOCK(mutex_,lk); + auto *decoder = (chan == 0) ? decoder_c1_ : decoder_c2_; + if (decoder) { + if (!decoder->accepts(pkt)) { + ftl::codecs::free((chan == 0) ? decoder_c1_ : decoder_c2_); + } else { + return; + } + } + + if (chan == 0) { + decoder_c1_ = ftl::codecs::allocateDecoder(pkt); + } else { + decoder_c2_ = ftl::codecs::allocateDecoder(pkt); + } +} +void NetSource::_recvPacket(short ttimeoff, const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { + // Capture time here for better net latency estimate + int64_t now = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now()).time_since_epoch().count(); if (!active_) return; - // Decode in temporary buffers to prevent long locks - cv::imdecode(jpg, cv::IMREAD_COLOR, &tmp_rgb); - if (d.size() > 0) cv::imdecode(d, cv::IMREAD_UNCHANGED, &tmp_depth); + const ftl::rgbd::Channel chan = host_->getChannel(); + int rchan = spkt.channel & 0x1; - // Apply colour correction to chunk - ftl::rgbd::colourCorrection(tmp_rgb, gamma_, temperature_); - - // Build chunk head - int cx = (chunk % chunks_dim_) * chunk_width_; - int cy = (chunk / chunks_dim_) * chunk_height_; - - // Make certain last frame has finished decoding before swap - while (frame > current_frame_ && chunk_count_ < 16 && chunk_count_ > 0) { - std::this_thread::yield(); - //std::function<void(int)> j = ftl::pool.pop(); - //if ((bool)j) j(-1); - //else std::this_thread::yield(); + // Ignore any unwanted second channel + if (chan == ftl::rgbd::Channel::None && rchan > 0) { + LOG(INFO) << "Unwanted channel"; + //return; + // TODO: Allow decode to be skipped } - //{ - // A new frame has been started... finish the last one - if (frame > current_frame_) { - // Lock host to prevent grab - UNIQUE_LOCK(host_->mutex(),lk); - if (frame > current_frame_) { - { - // Lock to allow buffer swap - UNIQUE_LOCK(mutex_,lk2); - - chunk_count_ = 0; - - // Swap the double buffers - cv::Mat tmp; - tmp = rgb_; - rgb_ = d_rgb_; - d_rgb_ = tmp; - tmp = depth_; - depth_ = d_depth_; - d_depth_ = tmp; - - timestamp_ = current_frame_*40; // FIXME: Don't hardcode 40ms - current_frame_ = frame; - } + NetFrame &frame = queue_.getFrame(spkt.timestamp, cv::Size(params_.width, params_.height), CV_8UC3, (isFloatChannel(chan) ? CV_32FC1 : CV_8UC3)); - if (host_->callback()) { - //ftl::pool.push([this](id) { - // UNIQUE_LOCK(host_->mutex(),lk); - host_->callback()(timestamp_, rgb_, depth_); - //}); - } - } - } else if (frame < current_frame_) { - LOG(WARNING) << "Chunk dropped"; - if (chunk == 0) N_--; - return; - } - //} + // Update frame statistics + frame.tx_size += pkt.data.size(); + + _createDecoder(rchan, pkt); + auto *decoder = (rchan == 0) ? decoder_c1_ : decoder_c2_; + if (!decoder) { + LOG(ERROR) << "No frame decoder available"; + return; + } + + decoder->decode(pkt, (rchan == 0) ? frame.channel1 : frame.channel2); + + // Apply colour correction to chunk + //ftl::rgbd::colourCorrection(tmp_rgb, gamma_, temperature_); // TODO:(Nick) Decode directly into double buffer if no scaling - { - SHARED_LOCK(mutex_, lk); - - cv::Rect roi(cx,cy,chunk_width_,chunk_height_); - cv::Mat chunkRGB = d_rgb_(roi); - cv::Mat chunkDepth = d_depth_(roi); - - // Original size so just copy - if (tmp_rgb.cols == chunkRGB.cols) { - tmp_rgb.copyTo(chunkRGB); - if (!tmp_depth.empty() && tmp_depth.type() == CV_16U && chunkDepth.type() == CV_32F) { - tmp_depth.convertTo(chunkDepth, CV_32FC1, 1.0f/1000.0f); //(16.0f*10.0f)); - } else if (!tmp_depth.empty() && tmp_depth.type() == CV_8UC3 && chunkDepth.type() == CV_8UC3) { - tmp_depth.copyTo(chunkDepth); - } else { - // Silent ignore? - } - // Downsized so needs a scale up - } else { - cv::resize(tmp_rgb, chunkRGB, chunkRGB.size()); - tmp_depth.convertTo(tmp_depth, CV_32FC1, 1.0f/1000.0f); - if (!tmp_depth.empty() && tmp_depth.type() == CV_16U && chunkDepth.type() == CV_32F) { - tmp_depth.convertTo(tmp_depth, CV_32FC1, 1.0f/1000.0f); //(16.0f*10.0f)); - cv::resize(tmp_depth, chunkDepth, chunkDepth.size()); - } else if (!tmp_depth.empty() && tmp_depth.type() == CV_8UC3 && chunkDepth.type() == CV_8UC3) { - cv::resize(tmp_depth, chunkDepth, chunkDepth.size()); - } else { - // Silent ignore? - } - } + if (timestamp_ > 0 && frame.timestamp <= timestamp_) { + LOG(ERROR) << "BAD DUPLICATE FRAME - " << frame.timestamp << " received=" << int(rchan) << " uri=" << uri_; + return; } - { - - ++chunk_count_; + // Calculate how many packets to expect for this frame + if (frame.chunk_total == 0) { + // Getting a second channel first means expect double packets + frame.chunk_total = pkt.block_total * ((spkt.channel >> 1) + 1); + } + + ++frame.chunk_count; + + if (frame.chunk_count > frame.chunk_total) LOG(FATAL) << "TOO MANY CHUNKS"; + + // Capture tx time of first received chunk + if (frame.chunk_count == 1) { + UNIQUE_LOCK(frame.mtx, flk); + if (frame.chunk_count == 1) { + frame.tx_latency = int64_t(ttimeoff); + } } - if (chunk == 0) { - UNIQUE_LOCK(host_->mutex(),lk); - N_--; + // Last chunk now received + if (frame.chunk_count == frame.chunk_total) { + UNIQUE_LOCK(frame.mtx, flk); + + if (frame.timestamp >= 0 && frame.chunk_count == frame.chunk_total) { + timestamp_ = frame.timestamp; + frame.tx_latency = now-(spkt.timestamp+frame.tx_latency); + + adaptive_ = abr_.selectBitrate(frame); + //LOG(INFO) << "Frame finished: " << frame.timestamp; + auto cb = host_->callback(); + if (cb) { + try { + cb(frame.timestamp, frame.channel1, frame.channel2); + } catch (...) { + LOG(ERROR) << "Exception in net frame callback"; + } + } else { + LOG(ERROR) << "NO FRAME CALLBACK"; + } + + queue_.freeFrame(frame); + + { + // Decrement expected frame counter + N_--; + } + } } } @@ -233,8 +325,8 @@ void NetSource::setPose(const Eigen::Matrix4d &pose) { //Source::setPose(pose); } -ftl::rgbd::Camera NetSource::parameters(ftl::rgbd::channel_t chan) { - if (chan == ftl::rgbd::kChanRight) { +ftl::rgbd::Camera NetSource::parameters(ftl::rgbd::Channel chan) { + if (chan == ftl::rgbd::Channel::Right) { auto uri = host_->get<string>("uri"); if (!uri) return params_; @@ -249,7 +341,7 @@ ftl::rgbd::Camera NetSource::parameters(ftl::rgbd::channel_t chan) { void NetSource::_updateURI() { UNIQUE_LOCK(mutex_,lk); active_ = false; - prev_chan_ = ftl::rgbd::kChanNone; + prev_chan_ = ftl::rgbd::Channel::None; auto uri = host_->get<string>("uri"); if (uri_.size() > 0) { @@ -264,30 +356,27 @@ void NetSource::_updateURI() { } peer_ = *p; - has_calibration_ = _getCalibration(*host_->getNet(), peer_, *uri, params_, ftl::rgbd::kChanLeft); - - host_->getNet()->bind(*uri, [this](int64_t frame, int chunk, bool delta, const vector<unsigned char> &jpg, const vector<unsigned char> &d) { - _recvChunk(frame, chunk, delta, jpg, d); + has_calibration_ = _getCalibration(*host_->getNet(), peer_, *uri, params_, ftl::rgbd::Channel::Left); + + host_->getNet()->bind(*uri, [this](short ttimeoff, const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { + //if (chunk == -1) { + //#ifdef HAVE_NVPIPE + //_recvVideo(frame, ttimeoff, bitrate, jpg, d); + //#else + //LOG(ERROR) << "Cannot receive HEVC, no NvPipe support"; + //#endif + //} else { + //_recvChunk(frame, ttimeoff, bitrate, chunk, jpg, d); + _recvPacket(ttimeoff, spkt, pkt); + //} }); N_ = 0; - // Initiate stream with request for first 10 frames - //try { - // host_->getNet()->send(peer_, "get_stream", *uri, N_, 0, host_->getNet()->id(), *uri); - //} catch(...) { - // LOG(ERROR) << "Could not connect to stream " << *uri; - //} - - // Update chunk details - chunks_dim_ = ftl::rgbd::kChunkDim; - chunk_width_ = params_.width / chunks_dim_; - chunk_height_ = params_.height / chunks_dim_; - chunk_count_ = 0; rgb_ = cv::Mat(cv::Size(params_.width, params_.height), CV_8UC3, cv::Scalar(0,0,0)); depth_ = cv::Mat(cv::Size(params_.width, params_.height), CV_32FC1, 0.0f); - d_rgb_ = cv::Mat(cv::Size(params_.width, params_.height), CV_8UC3, cv::Scalar(0,0,0)); - d_depth_ = cv::Mat(cv::Size(params_.width, params_.height), CV_32FC1, 0.0f); + //d_rgb_ = cv::Mat(cv::Size(params_.width, params_.height), CV_8UC3, cv::Scalar(0,0,0)); + //d_depth_ = cv::Mat(cv::Size(params_.width, params_.height), CV_32FC1, 0.0f); uri_ = *uri; active_ = true; @@ -297,17 +386,17 @@ void NetSource::_updateURI() { } } -bool NetSource::grab(int n, int b) { +bool NetSource::compute(int n, int b) { // Choose highest requested number of frames maxN_ = std::max(maxN_,(n == -1) ? ftl::rgbd::detail::kDefaultFrameCount : n); // Choose best requested quality - minB_ = std::min(minB_,(b == -1) ? 0 : b); + minB_ = std::min(minB_,(b == -1) ? int(adaptive_) : b); // Send k frames before end to prevent unwanted pause // Unless only a single frame is requested if ((N_ <= maxN_/2 && maxN_ > 1) || N_ == 0) { - const ftl::rgbd::channel_t chan = host_->getChannel(); + const ftl::rgbd::Channel chan = host_->getChannel(); N_ = maxN_; @@ -324,11 +413,13 @@ bool NetSource::grab(int n, int b) { } if (!host_->getNet()->send(peer_, "get_stream", - *host_->get<string>("uri"), N_, minB_, + *host_->get<string>("uri"), maxN_, minB_, host_->getNet()->id(), *host_->get<string>("uri"))) { active_ = false; } + abr_.notifyChanged(); + maxN_ = 1; // Reset to single frame minB_ = 9; // Reset to worst quality } diff --git a/components/rgbd-sources/src/net.hpp b/components/rgbd-sources/src/net.hpp index b99dc3487a6f4ab1949779935e2ed41699e9f4b0..51f31861fa3c9c39ea0cb53217e0fa3f764aeef3 100644 --- a/components/rgbd-sources/src/net.hpp +++ b/components/rgbd-sources/src/net.hpp @@ -2,65 +2,89 @@ #ifndef _FTL_RGBD_NET_HPP_ #define _FTL_RGBD_NET_HPP_ +#include <ftl/config.h> + #include <ftl/net/universe.hpp> #include <ftl/rgbd/source.hpp> +#include <ftl/rgbd/detail/abr.hpp> #include <ftl/threads.hpp> +#include <ftl/rgbd/detail/netframe.hpp> +#include <ftl/codecs/decoder.hpp> #include <string> +#ifdef HAVE_NVPIPE +#include <NvPipe.h> +#endif + namespace ftl { namespace rgbd { namespace detail { static const int kDefaultFrameCount = 30; +static const int kLatencyThreshold = 5; // Milliseconds delay considered as late +static const int kSlowFramesThreshold = 5; // Number of late frames before change +static const int kAdaptationRate = 5000; // Milliseconds between bitrate changes /** - * RGBD source from either a stereo video file with left + right images, or - * direct from two camera devices. A variety of algorithms are included for - * calculating disparity, before converting to depth. Calibration of the images - * is also performed. + * A two channel network streamed source for RGB-Depth. */ class NetSource : public detail::Source { public: explicit NetSource(ftl::rgbd::Source *); ~NetSource(); - bool grab(int n, int b); + bool capture(int64_t ts) { return true; } + bool retrieve() { return true; } + bool compute(int n, int b); bool isReady(); void setPose(const Eigen::Matrix4d &pose); - Camera parameters(channel_t chan); + Camera parameters(ftl::rgbd::Channel chan); void reset(); private: bool has_calibration_; ftl::UUID peer_; - int N_; + std::atomic<int> N_; bool active_; std::string uri_; ftl::net::callback_t h_; SHARED_MUTEX mutex_; - int chunks_dim_; - int chunk_width_; - int chunk_height_; cv::Mat idepth_; float gamma_; int temperature_; int minB_; int maxN_; int default_quality_; - ftl::rgbd::channel_t prev_chan_; - int64_t current_frame_; - std::atomic<int> chunk_count_; + ftl::rgbd::Channel prev_chan_; + + ftl::rgbd::detail::ABRController abr_; + int last_bitrate_; + + //#ifdef HAVE_NVPIPE + //NvPipe *nv_channel1_decoder_; + //NvPipe *nv_channel2_decoder_; + //#endif + + ftl::codecs::Decoder *decoder_c1_; + ftl::codecs::Decoder *decoder_c2_; + + // Adaptive bitrate functionality + ftl::rgbd::detail::bitrate_t adaptive_; // Current adapted bitrate + //unsigned int slow_log_; // Bit count of delayed frames + //int64_t last_br_change_; // Time of last adaptive change - // Double buffering - cv::Mat d_depth_; - cv::Mat d_rgb_; + NetFrameQueue queue_; - bool _getCalibration(ftl::net::Universe &net, const ftl::UUID &peer, const std::string &src, ftl::rgbd::Camera &p, ftl::rgbd::channel_t chan); + bool _getCalibration(ftl::net::Universe &net, const ftl::UUID &peer, const std::string &src, ftl::rgbd::Camera &p, ftl::rgbd::Channel chan); void _recv(const std::vector<unsigned char> &jpg, const std::vector<unsigned char> &d); - void _recvChunk(int64_t frame, int chunk, bool delta, const std::vector<unsigned char> &jpg, const std::vector<unsigned char> &d); + void _recvPacket(short ttimeoff, const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &); + //void _recvChunk(int64_t frame, short ttimeoff, uint8_t bitrate, int chunk, const std::vector<unsigned char> &jpg, const std::vector<unsigned char> &d); + //void _recvVideo(int64_t ts, short ttimeoff, uint8_t bitrate, const std::vector<unsigned char> &chan1, const std::vector<unsigned char> &chan2); void _updateURI(); + //void _checkAdaptive(int64_t); + void _createDecoder(int chan, const ftl::codecs::Packet &); }; } diff --git a/components/rgbd-sources/src/offilter.cpp b/components/rgbd-sources/src/offilter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..466aa9249517b91ac54726e821401e85272aa082 --- /dev/null +++ b/components/rgbd-sources/src/offilter.cpp @@ -0,0 +1,41 @@ +#include "ftl/offilter.hpp" +#include "cuda_algorithms.hpp" + +#ifdef HAVE_OPTFLOW + +#include <loguru.hpp> + +using namespace ftl::rgbd; + +using cv::Mat; +using cv::Size; + +using std::vector; + +template<typename T> static bool inline isValidDisparity(T d) { return (0.0 < d) && (d < 256.0); } // TODO + +OFDisparityFilter::OFDisparityFilter(Size size, int n_frames, float threshold) : + n_max_(n_frames + 1), threshold_(threshold) +{ + CHECK((n_max_ > 1) && (n_max_ <= 32)) << "History length must be between 0 and 31!"; + disp_old_ = cv::cuda::GpuMat(cv::Size(size.width * n_max_, size.height), CV_32FC1); + + /*nvof_ = cv::cuda::NvidiaOpticalFlow_1_0::create(size.width, size.height, + cv::cuda::NvidiaOpticalFlow_1_0::NV_OF_PERF_LEVEL_SLOW, + true, false, false, 0);*/ + +} + +void OFDisparityFilter::filter(ftl::rgbd::Frame &frame, cv::cuda::Stream &stream) +{ + frame.upload(Channel::Flow, stream); + const cv::cuda::GpuMat &optflow = frame.get<cv::cuda::GpuMat>(Channel::Flow); + //frame.get<cv::cuda::GpuMat>(Channel::Disparity); + stream.waitForCompletion(); + if (optflow.empty()) { return; } + + cv::cuda::GpuMat &disp = frame.create<cv::cuda::GpuMat>(Channel::Disparity); + ftl::cuda::optflow_filter(disp, optflow, disp_old_, n_max_, threshold_, stream); +} + +#endif // HAVE_OPTFLOW diff --git a/components/rgbd-sources/src/realsense_source.cpp b/components/rgbd-sources/src/realsense_source.cpp index d6c6f487be89c3eafd4e3e8e6020272dd93e8e9d..df4c0fe2535426ac52808ea985911968efb74e15 100644 --- a/components/rgbd-sources/src/realsense_source.cpp +++ b/components/rgbd-sources/src/realsense_source.cpp @@ -41,7 +41,7 @@ RealsenseSource::~RealsenseSource() { } -bool RealsenseSource::grab(int n, int b) { +bool RealsenseSource::compute(int n, int b) { rs2::frameset frames; if (!pipe_.poll_for_frames(&frames)) return false; //wait_for_frames(); diff --git a/components/rgbd-sources/src/realsense_source.hpp b/components/rgbd-sources/src/realsense_source.hpp index 2af26bbaffb99081e3311a70cb2c715c8fca5cee..371d305b7d27fc73ad85bba83965f58dcd28c45b 100644 --- a/components/rgbd-sources/src/realsense_source.hpp +++ b/components/rgbd-sources/src/realsense_source.hpp @@ -14,10 +14,12 @@ namespace detail { class RealsenseSource : public ftl::rgbd::detail::Source { public: - RealsenseSource(ftl::rgbd::Source *host); + explicit RealsenseSource(ftl::rgbd::Source *host); ~RealsenseSource(); - bool grab(int n=-1, int b=-1); + bool capture(int64_t ts) { timestamp_ = ts; return true; } + bool retrieve() { return true; } + bool compute(int n=-1, int b=-1); bool isReady(); private: diff --git a/components/rgbd-sources/src/snapshot.cpp b/components/rgbd-sources/src/snapshot.cpp index 20202302693721ce8f136ecdd3db8cd227f42db0..7a80ee677c6275f2446d0f2b404e3bc778745d08 100644 --- a/components/rgbd-sources/src/snapshot.cpp +++ b/components/rgbd-sources/src/snapshot.cpp @@ -13,6 +13,8 @@ using cv::imdecode; using std::string; using std::vector; +using cv::FileStorage; + // TODO: move to camera_params using ftl::rgbd::Camera; @@ -39,6 +41,12 @@ void from_json(const nlohmann::json& j, Camera &p) { j.at("minDepth").get_to(p.minDepth); j.at("maxDepth").get_to(p.maxDepth); } +/* +Mat getCameraMatrix(const ftl::rgbd::Camera ¶meters) { + Mat m = (cv::Mat_<double>(3,3) << parameters.fx, 0.0, -parameters.cx, 0.0, parameters.fy, -parameters.cy, 0.0, 0.0, 1.0); + return m; +} +*/ // SnapshotWriter::SnapshotWriter(const string &filename) { @@ -69,9 +77,7 @@ SnapshotWriter::SnapshotWriter(const string &filename) { } SnapshotWriter::~SnapshotWriter() { - archive_entry_free(entry_); - archive_write_close(archive_); - archive_write_free(archive_); + if (archive_) writeIndex(); } bool SnapshotWriter::addFile(const string &name, const uchar *buf, const size_t len) { @@ -104,44 +110,83 @@ bool SnapshotWriter::addFile(const string &name, const vector<uchar> &buf) { return addFile(name, buf.data(), buf.size()); } -bool SnapshotWriter::addMat(const string &name, const Mat &mat, const std::string &format) { +bool SnapshotWriter::addMat(const string &name, const Mat &mat, const std::string &format, const vector<int> ¶ms) { if (mat.rows == 0 || mat.cols == 0) { LOG(ERROR) << "empty mat"; return false; } vector<uchar> buf; - vector<int> params; bool retval = true; retval &= imencode("." + format, mat, buf, params); retval &= addFile(name + "." + format, buf); return retval; } -bool SnapshotWriter::addEigenMatrix4d(const string &name, const Matrix4d &m, const string &format) { - Mat tmp; - cv::eigen2cv(m, tmp); - return addMat(name, tmp, format); +void SnapshotWriter::addSource(const std::string &id, const vector<double> ¶ms, const cv::Mat &extrinsic) { + frame_idx_.push_back(0); + sources_.push_back(id); + params_.push_back(params); + extrinsic_.push_back(extrinsic); + fname_rgb_.emplace_back(); + fname_depth_.emplace_back(); } -bool SnapshotWriter::addCameraParams(const string &name, const Matrix4d &pose, const Camera ¶ms) { +void SnapshotWriter::addSource(const std::string &id, const ftl::rgbd::Camera ¶ms, const Eigen::Matrix4d &extrinsic) { + vector<double> params_vec; + Mat extrinsic_cv; + cv::eigen2cv(extrinsic, extrinsic_cv); + params_vec.push_back(params.fx); + params_vec.push_back(params.fy); + params_vec.push_back(params.cx); + params_vec.push_back(params.cy); + params_vec.push_back(params.width); + params_vec.push_back(params.height); + params_vec.push_back(params.minDepth); + params_vec.push_back(params.maxDepth); + params_vec.push_back(params.baseline); + params_vec.push_back(params.doffs); + addSource(id, params_vec, extrinsic_cv); +} + + +bool SnapshotWriter::addRGBD(size_t source, const cv::Mat &rgb, const cv::Mat &depth, uint64_t time) { + // TODO: png option + if (time != 0) { LOG(WARNING) << "time parameter not used (not implemented)"; } + bool retval = true; - retval &= addEigenMatrix4d(name + "-POSE", pose); + string fname = std::to_string(source) + "/" + std::to_string(frame_idx_[source]++); + + fname_rgb_[source].push_back("RGB" + fname + ".jpg"); + retval &= addMat("RGB" + fname, rgb, "jpg", {}); + + fname_depth_[source].push_back("DEPTH" + fname + ".tiff"); + retval &= addMat("DEPTH" + fname, depth, "tiff", {}); - nlohmann::json j; - to_json(j, params); - string str_params = j.dump(); - retval &= addFile(name + "-PARAMS.json", (uchar*) str_params.c_str(), str_params.size()); return retval; } -bool SnapshotWriter::addCameraRGBD(const string &name, const Mat &rgb, const Mat &depth) { - bool retval = true; - cv::Mat tdepth; - depth.convertTo(tdepth, CV_16UC1, 1000.0f); - retval &= addMat(name + "-RGB", rgb, "jpg"); - retval &= addMat(name + "-D", tdepth, "png"); - return retval; +void SnapshotWriter::writeIndex() { + FileStorage fs(".yml", FileStorage::WRITE + FileStorage::MEMORY); + + vector<string> channels{"time", "rgb_left", "depth_left"}; + + fs << "sources" << sources_; + fs << "params" <<params_; + fs << "extrinsic" << extrinsic_; + fs << "channels" << channels; + + fs << "rgb_left" << fname_rgb_; + fs << "depth_left" << fname_depth_; + + string buf = fs.releaseAndGetString(); + addFile("index.yml", (uchar*) buf.c_str(), buf.length()); + + archive_entry_free(entry_); + archive_write_close(archive_); + archive_write_free(archive_); + archive_ = nullptr; + entry_ = nullptr; } SnapshotStreamWriter::SnapshotStreamWriter(const string &filename, int delay) : @@ -150,11 +195,11 @@ SnapshotStreamWriter::SnapshotStreamWriter(const string &filename, int delay) : } SnapshotStreamWriter::~SnapshotStreamWriter() { - + } void SnapshotStreamWriter::addSource(ftl::rgbd::Source *src) { - writer_.addCameraParams(std::to_string(sources_.size()), src->getPose(), src->parameters()); + writer_.addSource(src->getURI(), src->parameters(), src->getPose()); sources_.push_back(src); } @@ -169,12 +214,19 @@ void SnapshotStreamWriter::run() { auto duration = t_now.time_since_epoch(); auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count(); + bool good = true; for(size_t i = 0; i < sources_.size(); ++i) { sources_[i]->getFrames(rgb[i], depth[i]); + good &= !rgb[i].empty() && !depth[i].empty(); + } + + if (!good) { + LOG(WARNING) << "Missing frames"; + continue; } for(size_t i = 0; i < sources_.size(); ++i) { - writer_.addCameraRGBD(std::to_string(ms) + "-" + std::to_string(i), rgb[i], depth[i]); + writer_.addRGBD(i, rgb[i], depth[i]); } std::this_thread::sleep_until(t_wakeup); @@ -197,10 +249,25 @@ void SnapshotStreamWriter::stop() { if (wasrunning) thread_.join(); } - +size_t Snapshot::getSourcesCount() { return sources.size(); } +size_t Snapshot::getFramesCount() { return depth_left[0].size(); } + +string Snapshot::getSourceURI(size_t camera) { return sources[camera]; } +ftl::rgbd::Camera Snapshot::getParameters(size_t camera) { return parameters[camera]; } +void Snapshot::getPose(size_t camera, cv::Mat &out) { out = extrinsic[camera]; } +void Snapshot::getPose(size_t camera, Eigen::Matrix4d &out) { + Mat mat; + getPose(camera, mat); + cv::cv2eigen(mat, out); +} +void Snapshot::getLeftRGB(size_t camera, size_t frame, cv::Mat &data) { data = rgb_left[camera][frame]; } +void Snapshot::getLeftDepth(size_t camera, size_t frame, cv::Mat &data) { data = depth_left[camera][frame]; } SnapshotReader::SnapshotReader(const string &filename) { archive_ = archive_read_new(); + int retval = ARCHIVE_OK; + string msg; + if (!archive_) goto error2; archive_read_support_format_all(archive_); archive_read_support_filter_all(archive_); @@ -208,12 +275,22 @@ SnapshotReader::SnapshotReader(const string &filename) { if (archive_read_open_filename(archive_, filename.c_str(), 4096) != ARCHIVE_OK) goto error1; - readArchive(); + while((retval = archive_read_next_header(archive_, &entry_)) == ARCHIVE_OK) { + string path = string(archive_entry_pathname(entry_)); + vector<uchar> data; + + if (readEntry(data)) { files_[path] = data; } + } + + if (retval != ARCHIVE_EOF) { goto error1; } + return; error1: - LOG(ERROR) << archive_error_string(archive_); + msg = archive_error_string(archive_); archive_read_free(archive_); + throw std::runtime_error(msg); + error2: // throw exception; otherwise destructor might be called throw std::runtime_error("SnapshotReader failed"); @@ -242,94 +319,200 @@ bool SnapshotReader::readEntry(vector<uchar> &data) { } } -SnapshotEntry& SnapshotReader::getEntry(const string &id) { - /*if (data_.find(id) == data_.end()) { - data_.emplace(id, SnapshotEntry{}); - }*/ - return data_[id]; -} - -/* read all entries to data_ */ -bool SnapshotReader::readArchive() { - int retval = ARCHIVE_OK; - vector<uchar> data; - - while((retval = archive_read_next_header(archive_, &entry_)) == ARCHIVE_OK) { - string path = string(archive_entry_pathname(entry_)); - if (path.rfind("-") == string::npos) { - LOG(WARNING) << "unrecognized file " << path; - continue; - } - string id = path.substr(0, path.find("-")); - - SnapshotEntry &snapshot = getEntry(id); +bool SnapshotReader::getDepth(const std::string &name, cv::Mat &data) { + if (files_.find(name) == files_.end()) { + LOG(ERROR) << name << " not found in archive"; + return false; + } - // TODO: verify that input is valid - // TODO: check that earlier results are not overwritten (status) + const vector<uchar> &data_raw = files_[name]; + const string ext = name.substr(name.find_last_of(".") + 1); - if (path.rfind("-RGB.") != string::npos) { - if (!readEntry(data)) continue; - snapshot.rgb = cv::imdecode(data, cv::IMREAD_COLOR); - snapshot.status &= ~1; - } - else if (path.rfind("-D.") != string::npos) { - if (!readEntry(data)) continue; - snapshot.depth = cv::imdecode(data, cv::IMREAD_ANYDEPTH); - snapshot.depth.convertTo(snapshot.depth, CV_32FC1, 1.0f / 1000.0f); - snapshot.status &= ~(1 << 1); - } - else if (path.rfind("-POSE.pfm") != string::npos) { - if (!readEntry(data)) continue; - Mat m_ = cv::imdecode(Mat(data), cv::IMREAD_ANYDEPTH); - if ((m_.rows != 4) || (m_.cols != 4)) continue; - cv::Matx44d pose_(m_); - cv::cv2eigen(pose_, snapshot.pose); - snapshot.status &= ~(1 << 2); - } - else if (path.rfind("-PARAMS.json") != string::npos) { - if (!readEntry(data)) continue; - nlohmann::json j = nlohmann::json::parse(string((const char*) data.data(), data.size())); - from_json(j, snapshot.params); - snapshot.status &= ~(1 << 3); - } - else { - LOG(WARNING) << "unknown file " << path; - } + if (ext == "tiff") { + data = cv::imdecode(data_raw, cv::IMREAD_ANYDEPTH); } - - if (retval != ARCHIVE_EOF) { - LOG(ERROR) << archive_error_string(archive_); + else if (ext == "png") { + data = cv::imdecode(data_raw, cv::IMREAD_ANYDEPTH); + data.convertTo(data, CV_32FC1, 1.0f / 1000.0f); + } + else { + LOG(ERROR) << "Unsupported file extension for depth image: " << ext; + return false; + } + + if (data.empty()) { + LOG(ERROR) << "Error decoding file: " << name; return false; } return true; } -vector<string> SnapshotReader::getIds() { - vector<string> res; - res.reserve(data_.size()); - for(auto itr = data_.begin(); itr != data_.end(); ++itr) { - res.push_back(itr->first); +bool SnapshotReader::getRGB(const std::string &name, cv::Mat &data) { + if (files_.find(name) == files_.end()) { + LOG(ERROR) << name << " not found in archive"; + return false; } - return res; -} -bool SnapshotReader::getCameraRGBD(const string &id, Mat &rgb, Mat &depth, - Matrix4d &pose, Camera ¶ms) { - if (data_.find(id) == data_.end()) { - LOG(ERROR) << "entry not found: " << id; + const vector<uchar> &data_raw = files_[name]; + const string ext = name.substr(name.find_last_of(".") + 1); + + if (!(ext == "png" || ext == "jpg")) { + LOG(ERROR) << "Unsupported file extension for depth image: " << ext; return false; } - SnapshotEntry item = getEntry(id); + data = cv::imdecode(data_raw, cv::IMREAD_COLOR); - if (item.status != 0) { - LOG(ERROR) << "entry incomplete: " << id; + if (data.empty()) { + LOG(ERROR) << "Error decoding file: " << name; + return false; } - - rgb = item.rgb; - depth = item.depth; - params = item.params; - pose = item.pose; + return true; -} \ No newline at end of file +} + +Snapshot SnapshotReader::readArchive() { + Snapshot result; + + if (files_.find("index.yml") != files_.end()) { + LOG(INFO) << "Using new format snapshot archive"; + string input; + { + vector<uchar> data = files_["index.yml"]; + input = string((char*) data.data(), data.size()); + } + FileStorage fs(input, FileStorage::READ | FileStorage::MEMORY); + + vector<string> &sources = result.sources; + vector<ftl::rgbd::Camera> ¶ms = result.parameters; + vector<Mat> &extrinsic = result.extrinsic; + + vector<vector<Mat>> &rgb_left = result.rgb_left; + vector<vector<Mat>> &depth_left = result.depth_left; + + vector<string> channels; + + fs["sources"] >> sources; + fs["extrinsic"] >> extrinsic; + fs["channels"] >> channels; + + cv::FileNode fn; + fn = fs["params"]; + for (cv::FileNodeIterator it = fn.begin(); it != fn.end(); it++) { + vector<double> p; + *it >> p; + + ftl::rgbd::Camera camera; + camera.fx = p[0]; + camera.fy = p[1]; + camera.cx = p[2]; + camera.cy = p[3]; + camera.width = p[4]; + camera.height = p[5]; + camera.minDepth = p[6]; + camera.maxDepth = p[7]; + camera.baseline = p[8]; + camera.doffs = p[9]; + params.push_back(camera); + } + + vector<string> files; + for (auto const &channel : channels) { + files.clear(); + + if (channel == "time") { + //fs["time"] >> times; + } + else if (channel == "rgb_left") { + fn = fs["rgb_left"]; + files.clear(); + for (cv::FileNodeIterator it = fn.begin(); it != fn.end(); it++) { + *it >> files; + auto &images = rgb_left.emplace_back(); + for (const string& file : files) { + Mat &img = images.emplace_back(); + getRGB(file, img); + } + } + } + else if (channel == "depth_left") { + fn = fs["depth_left"]; + files.clear(); + for (cv::FileNodeIterator it = fn.begin(); it != fn.end(); it++) { + *it >> files; + auto &images = depth_left.emplace_back(); + for (const string& file : files) { + Mat &img = images.emplace_back(); + getDepth(file, img); + } + } + } + else { + LOG(ERROR) << "Unsupported channel: " << channel; + } + } + + fs.release(); + } + else { + LOG(INFO) << "Using old format snapshot archive"; + + result.n_cameras = 0; + result.n_frames = 1; + + std::map<string,int> cammap; + + for (auto const& [path, data] : files_) { + if (path.rfind("-") == string::npos) { + LOG(WARNING) << "unrecognized file " << path; + continue; + } + string id = path.substr(0, path.find("-")); + int idx; + + if (cammap.find(id) == cammap.end()) { + cammap[id] = result.n_cameras; + idx = result.n_cameras; + result.n_cameras++; + + result.rgb_left.emplace_back().emplace_back(); + result.depth_left.emplace_back().emplace_back(); + result.extrinsic.emplace_back(); + result.parameters.emplace_back(); + } else { + idx = cammap[id]; + } + + Mat &rgb = result.rgb_left[idx][0]; + Mat &depth = result.depth_left[idx][0]; + Mat &pose = result.extrinsic[idx]; + Camera ¶ms = result.parameters[idx]; + + // TODO: verify that input is valid + // TODO: check that earlier results are not overwritten (status) + + if (path.rfind("-RGB.") != string::npos) { + getRGB(path, rgb); + } + else if (path.rfind("-D.") != string::npos) { + getDepth(path, depth); + } + else if (path.rfind("-POSE.pfm") != string::npos) { + Mat m_ = cv::imdecode(Mat(data), cv::IMREAD_ANYDEPTH); + if ((m_.rows != 4) || (m_.cols != 4)) continue; + cv::Matx44d pose_(m_); + pose = m_; + } + else if (path.rfind("-PARAMS.json") != string::npos) { + nlohmann::json j = nlohmann::json::parse(string((const char*) data.data(), data.size())); + from_json(j, params); + } + else { + LOG(WARNING) << "unknown file " << path; + } + } + } + + return result; +} diff --git a/components/rgbd-sources/src/snapshot_source.cpp b/components/rgbd-sources/src/snapshot_source.cpp index 030e56dd48bf48bf351917274e2f26cc0e414cd3..73db6b86c3861cd0aa1105f4d9ff7dcd9cbe9fe3 100644 --- a/components/rgbd-sources/src/snapshot_source.cpp +++ b/components/rgbd-sources/src/snapshot_source.cpp @@ -13,19 +13,21 @@ using ftl::rgbd::detail::SnapshotSource; using std::string; using std::vector; -SnapshotSource::SnapshotSource(ftl::rgbd::Source *host, SnapshotReader &reader, const string &id) : detail::Source(host) { - Eigen::Matrix4d pose; - reader.getCameraRGBD(id, rgb_, depth_, pose, params_); +SnapshotSource::SnapshotSource(ftl::rgbd::Source *host, Snapshot &snapshot, const string &id) : detail::Source(host) { + snapshot_ = snapshot; + camera_idx_ = std::atoi(id.c_str()); + frame_idx_ = 0; - if (rgb_.empty()) LOG(ERROR) << "Did not load snapshot rgb - " << id; - if (depth_.empty()) LOG(ERROR) << "Did not load snapshot depth - " << id; - if (params_.width != rgb_.cols) LOG(ERROR) << "Camera parameters corrupt for " << id; + Eigen::Matrix4d pose; + snapshot.getPose(camera_idx_, pose); + params_ = snapshot.getParameters(camera_idx_); + /* ftl::rgbd::colourCorrection(rgb_, host->value("gamma", 1.0f), host->value("temperature", 6500)); - host->on("gamma", [this,host](const ftl::config::Event&) { ftl::rgbd::colourCorrection(rgb_, host->value("gamma", 1.0f), host->value("temperature", 6500)); }); + */ // Add calibration to config object host_->getConfig()["focal"] = params_.fx; @@ -49,4 +51,21 @@ SnapshotSource::SnapshotSource(ftl::rgbd::Source *host, SnapshotReader &reader, LOG(INFO) << "POSE = " << pose; host->setPose(pose); + + mspf_ = 1000 / host_->value("fps", 20); +} + +bool SnapshotSource::compute(int n, int b) { + snapshot_.getLeftRGB(camera_idx_, frame_idx_, snap_rgb_); + snapshot_.getLeftDepth(camera_idx_, frame_idx_, snap_depth_); + + snap_rgb_.copyTo(rgb_); + snap_depth_.copyTo(depth_); + + auto cb = host_->callback(); + if (cb) cb(timestamp_, rgb_, depth_); + + frame_idx_ = (frame_idx_ + 1) % snapshot_.getFramesCount(); + + return true; } diff --git a/components/rgbd-sources/src/snapshot_source.hpp b/components/rgbd-sources/src/snapshot_source.hpp index 38b9d875ad9e9846d0c83e42d6b84668cfe7dd18..de1b0df48be79df732f51144226f5c7e6d2f0478 100644 --- a/components/rgbd-sources/src/snapshot_source.hpp +++ b/components/rgbd-sources/src/snapshot_source.hpp @@ -13,15 +13,25 @@ namespace detail { class SnapshotSource : public detail::Source { public: - SnapshotSource(ftl::rgbd::Source *); - SnapshotSource(ftl::rgbd::Source *, ftl::rgbd::SnapshotReader &reader, const std::string &id); + explicit SnapshotSource(ftl::rgbd::Source *); + SnapshotSource(ftl::rgbd::Source *, ftl::rgbd::Snapshot &snapshot, const std::string &id); ~SnapshotSource() {}; - bool grab(int n, int b) override { return true; }; + bool capture(int64_t ts) { timestamp_ = ts; return true; } + bool retrieve() { return true; } + bool compute(int n, int b); bool isReady() { return true; } //void reset(); - + private: + size_t frame_idx_; + size_t camera_idx_; + + ftl::rgbd::Snapshot snapshot_; + + cv::Mat snap_rgb_; + cv::Mat snap_depth_; + int mspf_; }; } diff --git a/components/rgbd-sources/src/source.cpp b/components/rgbd-sources/src/source.cpp index 1014e24401b4d2dccdc361de3d4331ff2815eacb..35d23f27ad7edac18d3e3e02247296f1382be5e2 100644 --- a/components/rgbd-sources/src/source.cpp +++ b/components/rgbd-sources/src/source.cpp @@ -25,10 +25,11 @@ using ftl::rgbd::detail::NetSource; using ftl::rgbd::detail::ImageSource; using ftl::rgbd::detail::MiddleburySource; using ftl::rgbd::capability_t; +using ftl::rgbd::Channel; Source::Source(ftl::config::json_t &cfg) : Configurable(cfg), pose_(Eigen::Matrix4d::Identity()), net_(nullptr) { impl_ = nullptr; - params_ = {0}; + params_ = {}; stream_ = 0; timestamp_ = 0; reset(); @@ -41,7 +42,7 @@ Source::Source(ftl::config::json_t &cfg) : Configurable(cfg), pose_(Eigen::Matri Source::Source(ftl::config::json_t &cfg, ftl::net::Universe *net) : Configurable(cfg), pose_(Eigen::Matrix4d::Identity()), net_(net) { impl_ = nullptr; - params_ = {0}; + params_ = {}; stream_ = 0; timestamp_ = 0; reset(); @@ -120,7 +121,8 @@ ftl::rgbd::detail::Source *Source::_createFileImpl(const ftl::URI &uri) { } else if (ext == "tar" || ext == "gz") { #ifdef HAVE_LIBARCHIVE ftl::rgbd::SnapshotReader reader(path); - return new ftl::rgbd::detail::SnapshotSource(this, reader, value("index", std::string("0"))); // TODO: Use URI fragment + auto snapshot = reader.readArchive(); + return new ftl::rgbd::detail::SnapshotSource(this, snapshot, value("index", std::string("0"))); // TODO: Use URI fragment #else LOG(ERROR) << "Cannot read snapshots, libarchive not installed"; return nullptr; @@ -164,11 +166,20 @@ ftl::rgbd::detail::Source *Source::_createDeviceImpl(const ftl::URI &uri) { } void Source::getFrames(cv::Mat &rgb, cv::Mat &depth) { + if (bool(callback_)) LOG(WARNING) << "Cannot use getFrames and callback in source"; SHARED_LOCK(mutex_,lk); - //rgb_.copyTo(rgb); - //depth_.copyTo(depth); + rgb_.copyTo(rgb); + depth_.copyTo(depth); + //rgb = rgb_; + //depth = depth_; + + /*cv::Mat tmp; + tmp = rgb; rgb = rgb_; + rgb_ = tmp; + tmp = depth; depth = depth_; + depth_ = tmp;*/ } Eigen::Vector4d Source::point(uint ux, uint uy) { @@ -217,19 +228,30 @@ capability_t Source::getCapabilities() const { void Source::reset() { UNIQUE_LOCK(mutex_,lk); - channel_ = kChanNone; + channel_ = Channel::None; if (impl_) delete impl_; impl_ = _createImplementation(); } -bool Source::grab(int N, int B) { +bool Source::capture(int64_t ts) { + //timestamp_ = ts; + if (impl_) return impl_->capture(ts); + else return true; +} + +bool Source::retrieve() { + if (impl_) return impl_->retrieve(); + else return true; +} + +bool Source::compute(int N, int B) { UNIQUE_LOCK(mutex_,lk); if (!impl_ && stream_ != 0) { cudaSafeCall(cudaStreamSynchronize(stream_)); if (depth_.type() == CV_32SC1) depth_.convertTo(depth_, CV_32F, 1.0f / 1000.0f); stream_ = 0; return true; - } else if (impl_ && impl_->grab(N,B)) { + } else if (impl_ && impl_->compute(N,B)) { timestamp_ = impl_->timestamp_; /*cv::Mat tmp; rgb_.create(impl_->rgb_.size(), impl_->rgb_.type()); @@ -240,72 +262,17 @@ bool Source::grab(int N, int B) { tmp = depth_; depth_ = impl_->depth_; impl_->depth_ = tmp;*/ + + // TODO:(Nick) Reduce buffer copies impl_->rgb_.copyTo(rgb_); impl_->depth_.copyTo(depth_); + //rgb_ = impl_->rgb_; + //depth_ = impl_->depth_; return true; } return false; } -void Source::writeFrames(const cv::Mat &rgb, const cv::Mat &depth) { - if (!impl_) { - UNIQUE_LOCK(mutex_,lk); - rgb.copyTo(rgb_); - depth.copyTo(depth_); - } -} - -void Source::writeFrames(const ftl::cuda::TextureObject<uchar4> &rgb, const ftl::cuda::TextureObject<uint> &depth, cudaStream_t stream) { - if (!impl_) { - UNIQUE_LOCK(mutex_,lk); - rgb_.create(rgb.height(), rgb.width(), CV_8UC4); - cudaSafeCall(cudaMemcpy2DAsync(rgb_.data, rgb_.step, rgb.devicePtr(), rgb.pitch(), rgb_.cols * sizeof(uchar4), rgb_.rows, cudaMemcpyDeviceToHost, stream)); - depth_.create(depth.height(), depth.width(), CV_32SC1); - cudaSafeCall(cudaMemcpy2DAsync(depth_.data, depth_.step, depth.devicePtr(), depth.pitch(), depth_.cols * sizeof(uint), depth_.rows, cudaMemcpyDeviceToHost, stream)); - //cudaSafeCall(cudaStreamSynchronize(stream)); // TODO:(Nick) Don't wait here. - stream_ = stream; - //depth_.convertTo(depth_, CV_32F, 1.0f / 1000.0f); - } else { - LOG(ERROR) << "writeFrames cannot be done on this source: " << getURI(); - } -} - -void Source::writeFrames(const ftl::cuda::TextureObject<uchar4> &rgb, const ftl::cuda::TextureObject<float> &depth, cudaStream_t stream) { - if (!impl_) { - UNIQUE_LOCK(mutex_,lk); - rgb.download(rgb_, stream); - //rgb_.create(rgb.height(), rgb.width(), CV_8UC4); - //cudaSafeCall(cudaMemcpy2DAsync(rgb_.data, rgb_.step, rgb.devicePtr(), rgb.pitch(), rgb_.cols * sizeof(uchar4), rgb_.rows, cudaMemcpyDeviceToHost, stream)); - depth.download(depth_, stream); - //depth_.create(depth.height(), depth.width(), CV_32FC1); - //cudaSafeCall(cudaMemcpy2DAsync(depth_.data, depth_.step, depth.devicePtr(), depth.pitch(), depth_.cols * sizeof(float), depth_.rows, cudaMemcpyDeviceToHost, stream)); - - stream_ = stream; - cudaSafeCall(cudaStreamSynchronize(stream_)); - cv::cvtColor(rgb_,rgb_, cv::COLOR_BGRA2BGR); - cv::cvtColor(rgb_,rgb_, cv::COLOR_Lab2BGR); - } -} - -void Source::writeFrames(const ftl::cuda::TextureObject<uchar4> &rgb, const ftl::cuda::TextureObject<uchar4> &rgb2, cudaStream_t stream) { - if (!impl_) { - UNIQUE_LOCK(mutex_,lk); - rgb.download(rgb_, stream); - //rgb_.create(rgb.height(), rgb.width(), CV_8UC4); - //cudaSafeCall(cudaMemcpy2DAsync(rgb_.data, rgb_.step, rgb.devicePtr(), rgb.pitch(), rgb_.cols * sizeof(uchar4), rgb_.rows, cudaMemcpyDeviceToHost, stream)); - rgb2.download(depth_, stream); - //depth_.create(depth.height(), depth.width(), CV_32FC1); - //cudaSafeCall(cudaMemcpy2DAsync(depth_.data, depth_.step, depth.devicePtr(), depth.pitch(), depth_.cols * sizeof(float), depth_.rows, cudaMemcpyDeviceToHost, stream)); - - stream_ = stream; - cudaSafeCall(cudaStreamSynchronize(stream_)); - cv::cvtColor(rgb_,rgb_, cv::COLOR_BGRA2BGR); - cv::cvtColor(rgb_,rgb_, cv::COLOR_Lab2BGR); - cv::cvtColor(depth_,depth_, cv::COLOR_BGRA2BGR); - cv::cvtColor(depth_,depth_, cv::COLOR_Lab2BGR); - } -} - bool Source::thumbnail(cv::Mat &t) { if (!impl_ && stream_ != 0) { cudaSafeCall(cudaStreamSynchronize(stream_)); @@ -314,7 +281,9 @@ bool Source::thumbnail(cv::Mat &t) { return true; } else if (impl_) { UNIQUE_LOCK(mutex_,lk); - impl_->grab(1, 9); + impl_->capture(0); + impl_->swap(); + impl_->compute(1, 9); impl_->rgb_.copyTo(rgb_); impl_->depth_.copyTo(depth_); } @@ -327,12 +296,17 @@ bool Source::thumbnail(cv::Mat &t) { return !thumb_.empty(); } -bool Source::setChannel(ftl::rgbd::channel_t c) { +bool Source::setChannel(ftl::rgbd::Channel c) { channel_ = c; // FIXME:(Nick) Verify channel is supported by this source... return true; } -const ftl::rgbd::Camera Source::parameters(ftl::rgbd::channel_t chan) const { +const ftl::rgbd::Camera Source::parameters(ftl::rgbd::Channel chan) const { return (impl_) ? impl_->parameters(chan) : parameters(); } + +void Source::setCallback(std::function<void(int64_t, cv::Mat &, cv::Mat &)> cb) { + if (bool(callback_)) LOG(ERROR) << "Source already has a callback: " << getURI(); + callback_ = cb; +} diff --git a/components/rgbd-sources/src/stereovideo.cpp b/components/rgbd-sources/src/stereovideo.cpp index cb5e08a3a572d1534f5aae086e635551a6cbd32f..6573f74f4d7cf1f3761f98a66c68c6963e10af31 100644 --- a/components/rgbd-sources/src/stereovideo.cpp +++ b/components/rgbd-sources/src/stereovideo.cpp @@ -7,9 +7,12 @@ #include "disparity.hpp" #include "cuda_algorithms.hpp" +#include "cuda_algorithms.hpp" + using ftl::rgbd::detail::Calibrate; using ftl::rgbd::detail::LocalSource; using ftl::rgbd::detail::StereoVideoSource; +using ftl::rgbd::Channel; using std::string; StereoVideoSource::StereoVideoSource(ftl::rgbd::Source *host) @@ -29,7 +32,8 @@ StereoVideoSource::~StereoVideoSource() { delete lsrc_; } -void StereoVideoSource::init(const string &file) { +void StereoVideoSource::init(const string &file) +{ capabilities_ = kCapVideo | kCapStereo; if (ftl::is_video(file)) { @@ -55,19 +59,31 @@ void StereoVideoSource::init(const string &file) { } cv::Size size = cv::Size(lsrc_->width(), lsrc_->height()); + frames_ = std::vector<Frame>(2); + +#ifdef HAVE_OPTFLOW + use_optflow_ = host_->value("use_optflow", false); + LOG(INFO) << "Using optical flow: " << (use_optflow_ ? "true" : "false"); + + nvof_ = cv::cuda::NvidiaOpticalFlow_1_0::create(size.width, size.height, + cv::cuda::NvidiaOpticalFlow_1_0::NV_OF_PERF_LEVEL_SLOW, + true, false, false, 0); + +#endif + calib_ = ftl::create<Calibrate>(host_, "calibration", size, stream_); if (!calib_->isCalibrated()) LOG(WARNING) << "Cameras are not calibrated!"; // Generate camera parameters from camera matrix - cv::Mat q = calib_->getCameraMatrix(); + cv::Mat K = calib_->getCameraMatrix(); params_ = { - q.at<double>(0,0), // Fx - q.at<double>(1,1), // Fy - -q.at<double>(0,2), // Cx - -q.at<double>(1,2), // Cy - (unsigned int)lsrc_->width(), - (unsigned int)lsrc_->height(), + K.at<double>(0,0), // Fx + K.at<double>(1,1), // Fy + -K.at<double>(0,2), // Cx + -K.at<double>(1,2), // Cy + (unsigned int) lsrc_->width(), + (unsigned int) lsrc_->height(), 0.0f, // 0m min 15.0f, // 15m max 1.0 / calib_->getQ().at<double>(3,2), // Baseline @@ -113,15 +129,15 @@ void StereoVideoSource::init(const string &file) { mask_l_ = (mask_l == 0); disp_ = Disparity::create(host_, "disparity"); - if (!disp_) LOG(FATAL) << "Unknown disparity algorithm : " << *host_->get<ftl::config::json_t>("disparity"); + if (!disp_) LOG(FATAL) << "Unknown disparity algorithm : " << *host_->get<ftl::config::json_t>("disparity"); disp_->setMask(mask_l_); LOG(INFO) << "StereoVideo source ready..."; ready_ = true; } -ftl::rgbd::Camera StereoVideoSource::parameters(ftl::rgbd::channel_t chan) { - if (chan == ftl::rgbd::kChanRight) { +ftl::rgbd::Camera StereoVideoSource::parameters(Channel chan) { + if (chan == Channel::Right) { cv::Mat q = calib_->getCameraMatrixRight(); ftl::rgbd::Camera params = { q.at<double>(0,0), // Fx @@ -151,34 +167,85 @@ static void disparityToDepth(const cv::cuda::GpuMat &disparity, cv::cuda::GpuMat cv::cuda::divide(val, disparity, depth, 1.0f / 1000.0f, -1, stream); } -bool StereoVideoSource::grab(int n, int b) { - const ftl::rgbd::channel_t chan = host_->getChannel(); +bool StereoVideoSource::capture(int64_t ts) { + timestamp_ = ts; + lsrc_->grab(); + return true; +} + +bool StereoVideoSource::retrieve() { + auto &frame = frames_[0]; + frame.reset(); + auto &left = frame.create<cv::cuda::GpuMat>(Channel::Left); + auto &right = frame.create<cv::cuda::GpuMat>(Channel::Right); + lsrc_->get(left, right, calib_, stream2_); - if (chan == ftl::rgbd::kChanDepth) { - lsrc_->get(left_, right_, stream_); - if (depth_tmp_.empty()) depth_tmp_ = cv::cuda::GpuMat(left_.size(), CV_32FC1); - if (disp_tmp_.empty()) disp_tmp_ = cv::cuda::GpuMat(left_.size(), CV_32FC1); - calib_->rectifyStereo(left_, right_, stream_); - disp_->compute(left_, right_, disp_tmp_, stream_); - ftl::cuda::disparity_to_depth(disp_tmp_, depth_tmp_, params_, stream_); - left_.download(rgb_, stream_); - //rgb_ = lsrc_->cachedLeft(); - depth_tmp_.download(depth_, stream_); +#ifdef HAVE_OPTFLOW + // see comments in https://gitlab.utu.fi/nicolas.pope/ftl/issues/155 + + if (use_optflow_) + { + auto &left_gray = frame.create<cv::cuda::GpuMat>(Channel::LeftGray); + auto &right_gray = frame.create<cv::cuda::GpuMat>(Channel::RightGray); + + cv::cuda::cvtColor(left, left_gray, cv::COLOR_BGR2GRAY, 0, stream2_); + cv::cuda::cvtColor(right, right_gray, cv::COLOR_BGR2GRAY, 0, stream2_); + + if (frames_[1].hasChannel(Channel::LeftGray)) + { + //frames_[1].download(Channel::LeftGray); + auto &left_gray_prev = frames_[1].get<cv::cuda::GpuMat>(Channel::LeftGray); + auto &optflow = frame.create<cv::cuda::GpuMat>(Channel::Flow); + nvof_->calc(left_gray, left_gray_prev, optflow, stream2_); + // nvof_->upSampler() isn't implemented with CUDA + // cv::cuda::resize() does not work wiht 2-channel input + // cv::cuda::resize(optflow_, optflow, left.size(), 0.0, 0.0, cv::INTER_NEAREST, stream2_); + } + } +#endif + stream2_.waitForCompletion(); + return true; +} + +void StereoVideoSource::swap() { + auto tmp = std::move(frames_[0]); + frames_[0] = std::move(frames_[1]); + frames_[1] = std::move(tmp); +} + +bool StereoVideoSource::compute(int n, int b) { + auto &frame = frames_[1]; + auto &left = frame.get<cv::cuda::GpuMat>(Channel::Left); + auto &right = frame.get<cv::cuda::GpuMat>(Channel::Right); + + const ftl::rgbd::Channel chan = host_->getChannel(); + if (left.empty() || right.empty()) return false; + + if (chan == Channel::Depth) { + disp_->compute(frame, stream_); + + auto &disp = frame.get<cv::cuda::GpuMat>(Channel::Disparity); + auto &depth = frame.create<cv::cuda::GpuMat>(Channel::Depth); + if (depth.empty()) depth = cv::cuda::GpuMat(left.size(), CV_32FC1); + + ftl::cuda::disparity_to_depth(disp, depth, params_, stream_); + + left.download(rgb_, stream_); + depth.download(depth_, stream_); + //frame.download(Channel::Left + Channel::Depth); stream_.waitForCompletion(); // TODO:(Nick) Move to getFrames - } else if (chan == ftl::rgbd::kChanRight) { - lsrc_->get(left_, right_, stream_); - calib_->rectifyStereo(left_, right_, stream_); - left_.download(rgb_, stream_); - right_.download(depth_, stream_); + } else if (chan == Channel::Right) { + left.download(rgb_, stream_); + right.download(depth_, stream_); stream_.waitForCompletion(); // TODO:(Nick) Move to getFrames } else { - lsrc_->get(left_, right_, stream_); - calib_->rectifyStereo(left_, right_, stream_); - //rgb_ = lsrc_->cachedLeft(); - left_.download(rgb_, stream_); + left.download(rgb_, stream_); stream_.waitForCompletion(); // TODO:(Nick) Move to getFrames } + + auto cb = host_->callback(); + if (cb) cb(timestamp_, rgb_, depth_); return true; } diff --git a/components/rgbd-sources/src/stereovideo.hpp b/components/rgbd-sources/src/stereovideo.hpp index 7835742389330046a33b7f22289ac36e8cdab4d5..9d3325e1ac27ec544abeb409149cb89817dc29d2 100644 --- a/components/rgbd-sources/src/stereovideo.hpp +++ b/components/rgbd-sources/src/stereovideo.hpp @@ -26,9 +26,12 @@ class StereoVideoSource : public detail::Source { StereoVideoSource(ftl::rgbd::Source*, const std::string &); ~StereoVideoSource(); - bool grab(int n, int b); + void swap(); + bool capture(int64_t ts); + bool retrieve(); + bool compute(int n, int b); bool isReady(); - Camera parameters(channel_t chan); + Camera parameters(ftl::rgbd::Channel chan); //const cv::Mat &getRight() const { return right_; } @@ -38,16 +41,21 @@ class StereoVideoSource : public detail::Source { Disparity *disp_; bool ready_; + bool use_optflow_; cv::cuda::Stream stream_; + cv::cuda::Stream stream2_; + + std::vector<Frame> frames_; - cv::cuda::GpuMat left_; - cv::cuda::GpuMat right_; - cv::cuda::GpuMat disp_tmp_; - cv::cuda::GpuMat depth_tmp_; - cv::Mat mask_l_; +#ifdef HAVE_OPTFLOW + // see comments in https://gitlab.utu.fi/nicolas.pope/ftl/issues/155 + cv::Ptr<cv::cuda::NvidiaOpticalFlow_1_0> nvof_; + cv::cuda::GpuMat optflow_; +#endif + void init(const std::string &); }; diff --git a/components/rgbd-sources/src/streamer.cpp b/components/rgbd-sources/src/streamer.cpp index 1d46ad7fd3c6ab9f35aa435bf6775927994eaceb..7a9118c9f47975a31d6389982b2adb818ed8a046 100644 --- a/components/rgbd-sources/src/streamer.cpp +++ b/components/rgbd-sources/src/streamer.cpp @@ -1,17 +1,23 @@ #include <ftl/rgbd/streamer.hpp> +#include <ftl/timer.hpp> #include <vector> #include <optional> #include <thread> #include <chrono> #include <tuple> +#include <algorithm> -#include "bitrate_settings.hpp" +#include <ftl/rgbd/detail/abr.hpp> +#include <ftl/codecs/encoder.hpp> using ftl::rgbd::Streamer; using ftl::rgbd::Source; using ftl::rgbd::detail::StreamSource; using ftl::rgbd::detail::StreamClient; -using ftl::rgbd::detail::bitrate_settings; +using ftl::rgbd::detail::ABRController; +using ftl::codecs::definition_t; +using ftl::codecs::device_t; +using ftl::rgbd::Channel; using ftl::net::Universe; using std::string; using std::list; @@ -23,18 +29,29 @@ using std::chrono::milliseconds; using std::tuple; using std::make_tuple; +static const ftl::codecs::preset_t kQualityThreshold = ftl::codecs::kPresetLQThreshold; + Streamer::Streamer(nlohmann::json &config, Universe *net) - : ftl::Configurable(config), late_(false), jobs_(0) { + : ftl::Configurable(config), late_(false) { active_ = false; net_ = net; time_peer_ = ftl::UUID(0); clock_adjust_ = 0; + mspf_ = ftl::timer::getInterval(); //1000 / value("fps", 20); + //last_dropped_ = 0; + //drop_count_ = 0; + + encode_mode_ = ftl::rgbd::kEncodeVideo; + hq_devices_ = (value("disable_hardware_encode", false)) ? device_t::Software : device_t::Any; + + //group_.setFPS(value("fps", 20)); + group_.setLatency(4); compress_level_ = value("compression", 1); - net->bind("find_stream", [this](const std::string &uri) -> optional<UUID> { + net->bind("find_stream", [this](const std::string &uri) -> optional<ftl::UUID> { SHARED_LOCK(mutex_,slk); if (sources_.find(uri) != sources_.end()) { @@ -75,7 +92,7 @@ Streamer::Streamer(nlohmann::json &config, Universe *net) }); // Allow remote users to access camera calibration matrix - net->bind("source_details", [this](const std::string &uri, ftl::rgbd::channel_t chan) -> tuple<unsigned int,vector<unsigned char>> { + net->bind("source_details", [this](const std::string &uri, ftl::rgbd::Channel chan) -> tuple<unsigned int,vector<unsigned char>> { vector<unsigned char> buf; SHARED_LOCK(mutex_,slk); @@ -94,11 +111,11 @@ Streamer::Streamer(nlohmann::json &config, Universe *net) _addClient(source, N, rate, peer, dest); }); - net->bind("set_channel", [this](const string &uri, unsigned int chan) { + net->bind("set_channel", [this](const string &uri, Channel chan) { SHARED_LOCK(mutex_,slk); if (sources_.find(uri) != sources_.end()) { - sources_[uri]->src->setChannel((ftl::rgbd::channel_t)chan); + sources_[uri]->src->setChannel(chan); } }); @@ -112,6 +129,7 @@ Streamer::Streamer(nlohmann::json &config, Universe *net) } Streamer::~Streamer() { + timer_job_.cancel(); net_->unbind("find_stream"); net_->unbind("list_streams"); net_->unbind("source_calibration"); @@ -119,6 +137,19 @@ Streamer::~Streamer() { net_->unbind("sync_streams"); net_->unbind("ping_streamer"); //pool_.stop(); + + { + UNIQUE_LOCK(mutex_,ulk); + for (auto &s : sources_) { + StreamSource *src = s.second; + src->clientCount = 0; + } + } + _cleanUp(); + { + UNIQUE_LOCK(mutex_,ulk); + sources_.clear(); + } } void Streamer::add(Source *src) { @@ -132,7 +163,11 @@ void Streamer::add(Source *src) { s->jobs = 0; s->frame = 0; s->clientCount = 0; + s->hq_count = 0; + s->lq_count = 0; sources_[src->getID()] = s; + + group_.addSource(src); } LOG(INFO) << "Streaming: " << src->getID(); @@ -149,7 +184,7 @@ void Streamer::_addClient(const string &source, int N, int rate, const ftl::UUID if (rate < 0 || rate >= 10) return; if (N < 0 || N > ftl::rgbd::kMaxFrames) return; - DLOG(INFO) << "Adding Stream Peer: " << peer.to_string() << " rate=" << rate << " N=" << N; + //DLOG(INFO) << "Adding Stream Peer: " << peer.to_string() << " rate=" << rate << " N=" << N; s = sources_[source]; @@ -157,15 +192,33 @@ void Streamer::_addClient(const string &source, int N, int rate, const ftl::UUID if (time_peer_ == ftl::UUID(0)) { time_peer_ = peer; - // Also do a time sync (but should be repeated periodically) - auto start = std::chrono::high_resolution_clock::now(); - int64_t mastertime = net_->call<int64_t>(peer, "__ping__"); - auto elapsed = std::chrono::high_resolution_clock::now() - start; - int64_t latency = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count(); - clock_adjust_ = mastertime - (std::chrono::time_point_cast<std::chrono::milliseconds>(start).time_since_epoch().count() + (latency/2)); - LOG(INFO) << "Clock adjustment: " << clock_adjust_; - LOG(INFO) << "Latency: " << (latency / 2); - LOG(INFO) << "Local: " << std::chrono::time_point_cast<std::chrono::milliseconds>(start).time_since_epoch().count() << ", master: " << mastertime; + // Do a time sync whenever the CPU is idle for 10ms or more. + // FIXME: Could be starved + timer_job_ = ftl::timer::add(ftl::timer::kTimerIdle10, [peer,this](int id) { + auto start = std::chrono::high_resolution_clock::now(); + int64_t mastertime; + + try { + mastertime = net_->call<int64_t>(peer, "__ping__"); + } catch (...) { + // Reset time peer and remove timer + time_peer_ = ftl::UUID(0); + return false; + } + + auto elapsed = std::chrono::high_resolution_clock::now() - start; + int64_t latency = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count(); + auto clock_adjust = mastertime - (ftl::timer::get_time() + (latency/2)); + + if (clock_adjust > 0) { + LOG(INFO) << "Clock adjustment: " << clock_adjust; + //LOG(INFO) << "Latency: " << (latency / 2); + //LOG(INFO) << "Local: " << std::chrono::time_point_cast<std::chrono::milliseconds>(start).time_since_epoch().count() << ", master: " << mastertime; + ftl::timer::setClockAdjustment(clock_adjust); + } + + return true; + }); } } @@ -173,21 +226,48 @@ void Streamer::_addClient(const string &source, int N, int rate, const ftl::UUID SHARED_LOCK(mutex_, slk); UNIQUE_LOCK(s->mutex, lk2); - for (auto &client : s->clients[rate]) { + for (auto &client : s->clients) { // If already listening, just update chunk counters if (client.peerid == peer) { - client.txmax = N * kChunkCount; + client.txmax = N; client.txcount = 0; + + // Possible switch from high quality to low quality encoding or vice versa + if (client.preset < kQualityThreshold && rate >= kQualityThreshold) { + s->hq_count--; + s->lq_count++; + if (s->lq_encoder_c1) s->lq_encoder_c1->reset(); + if (s->lq_encoder_c2) s->lq_encoder_c2->reset(); + } else if (client.preset >= kQualityThreshold && rate < kQualityThreshold) { + s->hq_count++; + s->lq_count--; + if (s->hq_encoder_c1) s->hq_encoder_c1->reset(); + if (s->hq_encoder_c2) s->hq_encoder_c2->reset(); + } + + client.preset = rate; return; } } // Not an existing client so add one - StreamClient &c = s->clients[rate].emplace_back(); + StreamClient &c = s->clients.emplace_back(); c.peerid = peer; c.uri = dest; c.txcount = 0; - c.txmax = N * kChunkCount; + c.txmax = N; + c.preset = rate; + + if (rate >= kQualityThreshold) { + if (s->lq_encoder_c1) s->lq_encoder_c1->reset(); + if (s->lq_encoder_c2) s->lq_encoder_c2->reset(); + s->lq_count++; + } else { + if (s->hq_encoder_c1) s->hq_encoder_c1->reset(); + if (s->hq_encoder_c2) s->hq_encoder_c2->reset(); + s->hq_count++; + } + ++s->clientCount; } @@ -200,237 +280,387 @@ void Streamer::remove(const std::string &) { } void Streamer::stop() { - active_ = false; - wait(); -} - -void Streamer::poll() { - //double wait = 1.0f / 25.0f; // TODO:(Nick) Should be in config - //auto start = std::chrono::high_resolution_clock::now(); - //int64_t now = std::chrono::time_point_cast<std::chrono::milliseconds>(start).time_since_epoch().count()+clock_adjust_; - - //int64_t msdelay = 40 - (now % 40); - //while (msdelay >= 20) { - // sleep_for(milliseconds(10)); - // now = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now()).time_since_epoch().count()+clock_adjust_; - // msdelay = 40 - (now % 40); - //} - //LOG(INFO) << "Required Delay: " << (now / 40) << " = " << msdelay; - - // Create frame jobs at correct FPS interval - _schedule(); - //std::function<void(int)> j = ftl::pool.pop(); - //if (j) j(-1); - - //std::chrono::duration<double> elapsed = - // std::chrono::high_resolution_clock::now() - start; - - //if (elapsed.count() >= wait) { - //LOG(WARNING) << "Frame rate below optimal @ " << (1.0f / elapsed.count()); - //} else { - //LOG(INFO) << "Frame rate @ " << (1.0f / elapsed.count()); - // Otherwise, wait until next frame should start. - // FIXME:(Nick) Is this accurate enough? Almost certainly not - // TODO:(Nick) Synchronise by time corrections and use of fixed time points - // but this only works if framerate can be achieved. - //sleep_for(milliseconds((long long)((wait - elapsed.count()) * 1000.0f))); - //} + group_.stop(); } void Streamer::run(bool block) { - active_ = true; - if (block) { - while (ftl::running && active_) { - poll(); - } + group_.sync([this](FrameSet &fs) -> bool { + _process(fs); + return true; + }); } else { // Create thread job for frame ticking ftl::pool.push([this](int id) { - while (ftl::running && active_) { - poll(); - } + group_.sync([this](FrameSet &fs) -> bool { + _process(fs); + return true; + }); }); } } -// Must be called in source locked state or src.state must be atomic -void Streamer::_swap(StreamSource *src) { - if (src->jobs == 0) { +void Streamer::_cleanUp() { + for (auto &s : sources_) { + StreamSource *src = s.second; UNIQUE_LOCK(src->mutex,lk); - for (unsigned int b=0; b<10; ++b) { - auto i = src->clients[b].begin(); - while (i != src->clients[b].end()) { - // Client request completed so remove from list - if ((*i).txcount >= (*i).txmax) { - LOG(INFO) << "Remove client: " << (*i).uri; - i = src->clients[b].erase(i); - --src->clientCount; + auto i = src->clients.begin(); + while (i != src->clients.end()) { + // Client request completed so remove from list + if ((*i).txcount >= (*i).txmax) { + // If peer was clock sync master, the remove that... + if ((*i).peerid == time_peer_) { + timer_job_.cancel(); + time_peer_ = ftl::UUID(0); + } + LOG(INFO) << "Remove client: " << (*i).uri; + + if ((*i).preset < kQualityThreshold) { + src->hq_count--; } else { - i++; + src->lq_count--; } + + i = src->clients.erase(i); + --src->clientCount; + } else { + i++; } } - src->src->getFrames(src->rgb, src->depth); + if (src->hq_count == 0) { + if (src->hq_encoder_c1) ftl::codecs::free(src->hq_encoder_c1); + if (src->hq_encoder_c2) ftl::codecs::free(src->hq_encoder_c2); + } - //if (!src->rgb.empty() && src->prev_depth.empty()) { - //src->prev_depth = cv::Mat(src->rgb.size(), CV_16UC1, cv::Scalar(0)); - //LOG(INFO) << "Creating prevdepth: " << src->rgb.cols << "," << src->rgb.rows; - //} - src->jobs = 0; - src->frame++; + if (src->lq_count == 0) { + if (src->lq_encoder_c1) ftl::codecs::free(src->lq_encoder_c1); + if (src->lq_encoder_c2) ftl::codecs::free(src->lq_encoder_c2); + } + + if (src->clientCount == 0) { + + } } } -void Streamer::wait() { - // Do some jobs in this thread, might as well... - std::function<void(int)> j; - while ((bool)(j=ftl::pool.pop())) { - j(-1); - } +void Streamer::_process(ftl::rgbd::FrameSet &fs) { + // Prevent new clients during processing. + SHARED_LOCK(mutex_,slk); - // Wait for all jobs to complete before finishing frame - //UNIQUE_LOCK(job_mtx_, lk); - std::unique_lock<std::mutex> lk(job_mtx_); - job_cv_.wait_for(lk, std::chrono::seconds(20), [this]{ return jobs_ == 0; }); - if (jobs_ != 0) { - LOG(FATAL) << "Deadlock detected"; + if (fs.sources.size() != sources_.size()) { + LOG(ERROR) << "Incorrect number of sources in frameset: " << fs.sources.size() << " vs " << sources_.size(); + return; } - // Capture frame number? - frame_no_ = last_frame_; -} + int totalclients = 0; -void Streamer::_schedule() { - wait(); - //std::mutex job_mtx; - //std::condition_variable job_cv; - //int jobs = 0; + frame_no_ = fs.timestamp; - //auto now = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now()).time_since_epoch().count()+clock_adjust_; - //LOG(INFO) << "Frame time = " << (now-(last_frame_*40)) << "ms"; + for (int j=0; j<fs.sources.size(); ++j) { + StreamSource *src = sources_[fs.sources[j]->getID()]; + SHARED_LOCK(src->mutex,lk); - // Prevent new clients during processing. - SHARED_LOCK(mutex_,slk); + // Don't do any work in the following cases + if (!src) continue; + if (!fs.sources[j]->isReady()) continue; + if (src->clientCount == 0) continue; + //if (fs.channel1[j].empty() || (fs.sources[j]->getChannel() != ftl::rgbd::kChanNone && fs.channel2[j].empty())) continue; + if (!fs.frames[j].hasChannel(Channel::Colour) || !fs.frames[j].hasChannel(fs.sources[j]->getChannel())) continue; + + bool hasChan2 = fs.sources[j]->getChannel() != Channel::None; + + totalclients += src->clientCount; + + // Do we need to do high quality encoding? + if (src->hq_count > 0) { + if (!src->hq_encoder_c1) src->hq_encoder_c1 = ftl::codecs::allocateEncoder( + definition_t::HD1080, hq_devices_); + if (!src->hq_encoder_c2 && hasChan2) src->hq_encoder_c2 = ftl::codecs::allocateEncoder( + definition_t::HD1080, hq_devices_); + + // Do we have the resources to do a HQ encoding? + if (src->hq_encoder_c1 && (!hasChan2 || src->hq_encoder_c2)) { + auto *enc1 = src->hq_encoder_c1; + auto *enc2 = src->hq_encoder_c2; + + // Important to send channel 2 first if needed... + // Receiver only waits for channel 1 by default + // TODO: Each encode could be done in own thread + if (hasChan2) { + enc2->encode(fs.frames[j].get<cv::Mat>(fs.sources[j]->getChannel()), src->hq_bitrate, [this,src,hasChan2](const ftl::codecs::Packet &blk){ + _transmitPacket(src, blk, 1, hasChan2, true); + }); + } else { + if (enc2) enc2->reset(); + } + + enc1->encode(fs.frames[j].get<cv::Mat>(Channel::Colour), src->hq_bitrate, [this,src,hasChan2](const ftl::codecs::Packet &blk){ + _transmitPacket(src, blk, 0, hasChan2, true); + }); + } + } - for (auto s : sources_) { - string uri = s.first; + // Do we need to do low quality encoding? + if (src->lq_count > 0) { + if (!src->lq_encoder_c1) src->lq_encoder_c1 = ftl::codecs::allocateEncoder( + definition_t::SD480, device_t::Software); + if (!src->lq_encoder_c2 && hasChan2) src->lq_encoder_c2 = ftl::codecs::allocateEncoder( + definition_t::SD480, device_t::Software); + + // Do we have the resources to do a LQ encoding? + if (src->lq_encoder_c1 && (!hasChan2 || src->lq_encoder_c2)) { + auto *enc1 = src->lq_encoder_c1; + auto *enc2 = src->lq_encoder_c2; + + // Important to send channel 2 first if needed... + // Receiver only waits for channel 1 by default + if (hasChan2) { + enc2->encode(fs.frames[j].get<cv::Mat>(fs.sources[j]->getChannel()), src->lq_bitrate, [this,src,hasChan2](const ftl::codecs::Packet &blk){ + _transmitPacket(src, blk, 1, hasChan2, false); + }); + } else { + if (enc2) enc2->reset(); + } - // No point in doing work if no clients - if (s.second->clientCount == 0) { - continue; + enc1->encode(fs.frames[j].get<cv::Mat>(Channel::Colour), src->lq_bitrate, [this,src,hasChan2](const ftl::codecs::Packet &blk){ + _transmitPacket(src, blk, 0, hasChan2, false); + }); + } } - // There will be two jobs for this source... - //UNIQUE_LOCK(job_mtx_,lk); - jobs_ += 1 + kChunkCount; - //lk.unlock(); + // Do we need to do low quality encoding? + /*if (src->lq_count > 0) { + if (!src->lq_encoder_c1) src->lq_encoder_c1 = ftl::codecs::allocateLQEncoder(); + if (!src->lq_encoder_c2) src->lq_encoder_c2 = ftl::codecs::allocateLQEncoder(); + + // Do we have the resources to do a LQ encoding? + if (src->lq_encoder_c1 && src->lq_encoder_c2) { + const auto *enc1 = src->lq_encoder_c1; + const auto *enc2 = src->lq_encoder_c2; + + // Do entire frame as single step + if (!enc1->useBlocks() || !enc2->useBlocks()) { + ftl::pool.push([this,&fs,j,src](int id) { + _encodeLQAndTransmit(src, fs.channel1[j], fs.channel2[j], -1); + std::unique_lock<std::mutex> lk(job_mtx_); + --jobs_; + if (jobs_ == 0) job_cv_.notify_one(); + }); + + jobs_++; + // Or divide frame into blocks and encode each + } else { + // Create jobs for each chunk + for (int i=0; i<chunk_count_; ++i) { + // Add chunk job to thread pool + ftl::pool.push([this,&fs,j,i,src](int id) { + int chunk = i; + try { + _encodeLQAndTransmit(src, fs.channel1[j], fs.channel2[j], chunk); + } catch(...) { + LOG(ERROR) << "Encode Exception: " << chunk; + } + + //src->jobs--; + std::unique_lock<std::mutex> lk(job_mtx_); + --jobs_; + if (jobs_ == 0) job_cv_.notify_one(); + }); + } + + jobs_ += chunk_count_; + } + } + }*/ + } - StreamSource *src = sources_[uri]; - if (src == nullptr || src->jobs != 0) continue; - src->jobs = 1 + kChunkCount; + /*std::unique_lock<std::mutex> lk(job_mtx_); + job_cv_.wait_for(lk, std::chrono::seconds(20), [this]{ return jobs_ == 0; }); + if (jobs_ != 0) { + LOG(FATAL) << "Deadlock detected"; + }*/ - // Grab job - ftl::pool.push([this,src](int id) { - //auto start = std::chrono::high_resolution_clock::now(); + // Go to sleep if no clients instead of spinning the cpu + if (totalclients == 0 || sources_.size() == 0) { + // Make sure to unlock so clients can connect! + //lk.unlock(); + slk.unlock(); + sleep_for(milliseconds(50)); + } else _cleanUp(); +} - auto start = std::chrono::high_resolution_clock::now(); - int64_t now = std::chrono::time_point_cast<std::chrono::milliseconds>(start).time_since_epoch().count()+clock_adjust_; - int64_t target = now / 40; +void Streamer::_transmitPacket(StreamSource *src, const ftl::codecs::Packet &pkt, int chan, bool hasChan2, bool hqonly) { + ftl::codecs::StreamPacket spkt = { + frame_no_, + static_cast<uint8_t>((chan & 0x1) | ((hasChan2) ? 0x2 : 0x0)) + }; + + // Lock to prevent clients being added / removed + //SHARED_LOCK(src->mutex,lk); + auto c = src->clients.begin(); + while (c != src->clients.end()) { + const ftl::codecs::preset_t b = (*c).preset; + if ((hqonly && b >= kQualityThreshold) || (!hqonly && b < kQualityThreshold)) { + ++c; + continue; + } - // TODO:(Nick) A now%40 == 0 should be accepted - if (target != last_frame_) LOG(WARNING) << "Frame " << "(" << (target-last_frame_) << ") dropped by " << (now%40) << "ms"; + try { + // TODO:(Nick) Send pose + short pre_transmit_latency = short(ftl::timer::get_time() - spkt.timestamp); + if (!net_->send((*c).peerid, + (*c).uri, + pre_transmit_latency, // Time since timestamp for tx + spkt, + pkt)) { - // Use sleep_for for larger delays - int64_t msdelay = 40 - (now % 40); - //LOG(INFO) << "Required Delay: " << (now / 40) << " = " << msdelay; - while (msdelay >= 20) { - sleep_for(milliseconds(10)); - now = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now()).time_since_epoch().count()+clock_adjust_; - msdelay = 40 - (now % 40); + // Send failed so mark as client stream completed + (*c).txcount = (*c).txmax; + } else { + // Count frame as completed only if last block and channel is 0 + if (pkt.block_number == pkt.block_total - 1 && chan == 0) ++(*c).txcount; } + } catch(...) { + (*c).txcount = (*c).txmax; + } + ++c; + } +} - // Spin loop until exact grab time - //LOG(INFO) << "Spin Delay: " << (now / 40) << " = " << (40 - (now%40)); - target = now / 40; - while ((now/40) == target) { - _mm_pause(); // SSE2 nano pause intrinsic - now = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now()).time_since_epoch().count()+clock_adjust_; - }; - last_frame_ = now/40; - - try { - src->src->grab(); - } catch (std::exception &ex) { - LOG(ERROR) << "Exception when grabbing frame"; - LOG(ERROR) << ex.what(); - } - catch (...) { - LOG(ERROR) << "Unknown exception when grabbing frame"; - } +/*void Streamer::_encodeHQAndTransmit(StreamSource *src, const cv::Mat &c1, const cv::Mat &c2, int block) { + bool hasChan2 = (!c2.empty() && src->src->getChannel() != ftl::rgbd::kChanNone); - //now = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now()).time_since_epoch().count()+clock_adjust_; - //if (now%40 > 0) LOG(INFO) << "Grab in: " << (now%40) << "ms"; + LOG(INFO) << "Encode HQ: " << block; - //std::chrono::duration<double> elapsed = - // std::chrono::high_resolution_clock::now() - start; - //LOG(INFO) << "Grab in " << elapsed.count() << "s"; + vector<unsigned char> c1buff; + vector<unsigned char> c2buff; - src->jobs--; - _swap(src); + if (block == -1) { + src->hq_encoder_c1->encode(c1, c1buff, src->hq_bitrate, false); + if (hasChan2) src->hq_encoder_c2->encode(c2, c2buff, src->hq_bitrate, false); + } else { + //bool delta = (chunk+src->frame) % 8 > 0; // Do XOR or not + int chunk_width = c1.cols / chunk_dim_; + int chunk_height = c1.rows / chunk_dim_; + + // Build chunk heads + int cx = (block % chunk_dim_) * chunk_width; + int cy = (block / chunk_dim_) * chunk_height; + cv::Rect roi(cx,cy,chunk_width,chunk_height); + //vector<unsigned char> rgb_buf; + cv::Mat chunkRGB = c1(roi); + src->hq_encoder_c1->encode(chunkRGB, c1buff, src->hq_bitrate, false); + + if (hasChan2) { + cv::Mat chunkDepth = c2(roi); + src->hq_encoder_c2->encode(chunkDepth, c2buff, src->hq_bitrate, false); + } + } - // Mark job as finished - std::unique_lock<std::mutex> lk(job_mtx_); - --jobs_; - job_cv_.notify_one(); - }); + // Lock to prevent clients being added / removed + SHARED_LOCK(src->mutex,lk); + auto c = src->clients.begin(); + while (c != src->clients.end()) { + const int b = (*c).bitrate; + if (b >= kQualityThreshold) continue; // Not a HQ request + + try { + // TODO:(Nick) Send pose + short pre_transmit_latency = short(ftl::timer::get_time() - frame_no_); + if (!net_->send((*c).peerid, (*c).uri, frame_no_, pre_transmit_latency, uint8_t(src->hq_bitrate), block, c1buff, c2buff)) { + // Send failed so mark as client stream completed + (*c).txcount = (*c).txmax; + } else { + ++(*c).txcount; + //LOG(INFO) << "SENT CHUNK : " << frame_no_ << "-" << chunk; + } + } catch(...) { + (*c).txcount = (*c).txmax; + } + ++c; + } +} - // Create jobs for each chunk - for (int i=0; i<kChunkCount; ++i) { - // Add chunk job to thread pool - ftl::pool.push([this,src,i](int id) { - int chunk = i; - try { - if (!src->rgb.empty() && (src->src->getChannel() == ftl::rgbd::kChanNone || !src->depth.empty())) { - _encodeAndTransmit(src, chunk); - } - } catch(...) { - LOG(ERROR) << "Encode Exception: " << chunk; - } +void Streamer::_encodeLQAndTransmit(StreamSource *src, const cv::Mat &c1, const cv::Mat &c2, int block) { + bool hasChan2 = (!c2.empty() && src->src->getChannel() != ftl::rgbd::kChanNone); - src->jobs--; - _swap(src); - std::unique_lock<std::mutex> lk(job_mtx_); - --jobs_; - job_cv_.notify_one(); - }); + LOG(INFO) << "Encode LQ: " << block; + + vector<unsigned char> c1buff; + vector<unsigned char> c2buff; + + if (block == -1) { + src->lq_encoder_c1->encode(c1, c1buff, src->lq_bitrate, false); + if (hasChan2) src->lq_encoder_c2->encode(c2, c2buff, src->lq_bitrate, false); + } else { + //bool delta = (chunk+src->frame) % 8 > 0; // Do XOR or not + int chunk_width = c1.cols / chunk_dim_; + int chunk_height = c1.rows / chunk_dim_; + + // Build chunk heads + int cx = (block % chunk_dim_) * chunk_width; + int cy = (block / chunk_dim_) * chunk_height; + cv::Rect roi(cx,cy,chunk_width,chunk_height); + //vector<unsigned char> rgb_buf; + cv::Mat chunkRGB = c1(roi); + //cv::resize(chunkRGB, downrgb, cv::Size(ABRController::getColourWidth(b) / chunk_dim_, ABRController::getColourHeight(b) / chunk_dim_)); + + src->lq_encoder_c1->encode(chunkRGB, c1buff, src->lq_bitrate, false); + + if (hasChan2) { + cv::Mat chunkDepth = c2(roi); + //cv::resize(chunkDepth, tmp, cv::Size(ABRController::getDepthWidth(b) / chunk_dim_, ABRController::getDepthHeight(b) / chunk_dim_), 0, 0, cv::INTER_NEAREST); + src->lq_encoder_c2->encode(chunkDepth, c2buff, src->lq_bitrate, false); } } -} -void Streamer::_encodeAndTransmit(StreamSource *src, int chunk) { - bool hasChan2 = (!src->depth.empty() && src->src->getChannel() != ftl::rgbd::kChanNone); + // Lock to prevent clients being added / removed + SHARED_LOCK(src->mutex,lk); + auto c = src->clients.begin(); + while (c != src->clients.end()) { + const int b = (*c).bitrate; + if (b < kQualityThreshold) continue; // Not an LQ request + + try { + // TODO:(Nick) Send pose + short pre_transmit_latency = short(ftl::timer::get_time() - frame_no_); + if (!net_->send((*c).peerid, (*c).uri, frame_no_, pre_transmit_latency, uint8_t(src->hq_bitrate), block, c1buff, c2buff)) { + // Send failed so mark as client stream completed + (*c).txcount = (*c).txmax; + } else { + ++(*c).txcount; + //LOG(INFO) << "SENT CHUNK : " << frame_no_ << "-" << chunk; + } + } catch(...) { + (*c).txcount = (*c).txmax; + } + ++c; + } +}*/ + +/*void Streamer::_encodeImagesAndTransmit(StreamSource *src, const cv::Mat &rgb, const cv::Mat &depth, int chunk) { + bool hasChan2 = (!depth.empty() && src->src->getChannel() != ftl::rgbd::kChanNone); - bool delta = (chunk+src->frame) % 8 > 0; // Do XOR or not - int chunk_width = src->rgb.cols / kChunkDim; - int chunk_height = src->rgb.rows / kChunkDim; + //bool delta = (chunk+src->frame) % 8 > 0; // Do XOR or not + int chunk_width = rgb.cols / chunk_dim_; + int chunk_height = rgb.rows / chunk_dim_; // Build chunk heads - int cx = (chunk % kChunkDim) * chunk_width; - int cy = (chunk / kChunkDim) * chunk_height; + int cx = (chunk % chunk_dim_) * chunk_width; + int cy = (chunk / chunk_dim_) * chunk_height; cv::Rect roi(cx,cy,chunk_width,chunk_height); - vector<unsigned char> rgb_buf; - cv::Mat chunkRGB = src->rgb(roi); + //vector<unsigned char> rgb_buf; + cv::Mat chunkRGB = rgb(roi); cv::Mat chunkDepth; //cv::Mat chunkDepthPrev = src->prev_depth(roi); cv::Mat d2, d3; - vector<unsigned char> d_buf; + //vector<unsigned char> d_buf; if (hasChan2) { - chunkDepth = src->depth(roi); + chunkDepth = depth(roi); if (chunkDepth.type() == CV_32F) chunkDepth.convertTo(d2, CV_16UC1, 1000); // 16*10); else d2 = chunkDepth; //if (delta) d3 = (d2 * 2) - chunkDepthPrev; @@ -438,65 +668,68 @@ void Streamer::_encodeAndTransmit(StreamSource *src, int chunk) { //d2.copyTo(chunkDepthPrev); } - // For each allowed bitrate setting (0 = max quality) - for (unsigned int b=0; b<10; ++b) { - { - //SHARED_LOCK(src->mutex,lk); - if (src->clients[b].size() == 0) continue; - } - - // Max bitrate means no changes - if (b == 0) { - _encodeChannel1(chunkRGB, rgb_buf, b); - if (hasChan2) _encodeChannel2(d2, d_buf, src->src->getChannel(), b); - - // Otherwise must downscale and change compression params - // TODO:(Nick) could reuse downscales - } else { - cv::Mat downrgb, downdepth; - cv::resize(chunkRGB, downrgb, cv::Size(bitrate_settings[b].width / kChunkDim, bitrate_settings[b].height / kChunkDim)); - if (hasChan2) cv::resize(d2, downdepth, cv::Size(bitrate_settings[b].width / kChunkDim, bitrate_settings[b].height / kChunkDim)); - - _encodeChannel1(downrgb, rgb_buf, b); - if (hasChan2) _encodeChannel2(downdepth, d_buf, src->src->getChannel(), b); + // TODO: Verify these don't allocate memory if not needed. + // TODO: Reuse these buffers to reduce allocations. + vector<unsigned char> brgb[ftl::rgbd::detail::kMaxBitrateLevels]; + vector<unsigned char> bdepth[ftl::rgbd::detail::kMaxBitrateLevels]; + + // Lock to prevent clients being added / removed + SHARED_LOCK(src->mutex,lk); + auto c = src->clients.begin(); + while (c != src->clients.end()) { + const int b = (*c).bitrate; + + if (brgb[b].empty()) { + // Max bitrate means no changes + if (b == 0) { + _encodeImageChannel1(chunkRGB, brgb[b], b); + if (hasChan2) _encodeImageChannel2(d2, bdepth[b], src->src->getChannel(), b); + + // Otherwise must downscale and change compression params + } else { + cv::Mat downrgb, downdepth; + cv::resize(chunkRGB, downrgb, cv::Size(ABRController::getColourWidth(b) / chunk_dim_, ABRController::getColourHeight(b) / chunk_dim_)); + if (hasChan2) cv::resize(d2, downdepth, cv::Size(ABRController::getDepthWidth(b) / chunk_dim_, ABRController::getDepthHeight(b) / chunk_dim_), 0, 0, cv::INTER_NEAREST); + + _encodeImageChannel1(downrgb, brgb[b], b); + if (hasChan2) _encodeImageChannel2(downdepth, bdepth[b], src->src->getChannel(), b); + } } - //if (chunk == 0) LOG(INFO) << "Sending chunk " << chunk << " : size = " << (d_buf.size()+rgb_buf.size()) << "bytes"; - - // Lock to prevent clients being added / removed - SHARED_LOCK(src->mutex,lk); - auto c = src->clients[b].begin(); - while (c != src->clients[b].end()) { - try { - // TODO:(Nick) Send pose and timestamp - if (!net_->send((*c).peerid, (*c).uri, frame_no_, chunk, delta, rgb_buf, d_buf)) { - // Send failed so mark as client stream completed - (*c).txcount = (*c).txmax; - } else { - ++(*c).txcount; - } - } catch(...) { + try { + // TODO:(Nick) Send pose + short pre_transmit_latency = short(ftl::timer::get_time() - frame_no_); + if (!net_->send((*c).peerid, (*c).uri, frame_no_, pre_transmit_latency, uint8_t(b), chunk, brgb[b], bdepth[b])) { + // Send failed so mark as client stream completed (*c).txcount = (*c).txmax; + } else { + ++(*c).txcount; + //LOG(INFO) << "SENT CHUNK : " << frame_no_ << "-" << chunk; } - ++c; + } catch(...) { + (*c).txcount = (*c).txmax; } + ++c; } } -void Streamer::_encodeChannel1(const cv::Mat &in, vector<unsigned char> &out, unsigned int b) { - vector<int> jpgparams = {cv::IMWRITE_JPEG_QUALITY, bitrate_settings[b].jpg_quality}; +void Streamer::_encodeImageChannel1(const cv::Mat &in, vector<unsigned char> &out, unsigned int b) { + vector<int> jpgparams = {cv::IMWRITE_JPEG_QUALITY, ABRController::getColourQuality(b)}; cv::imencode(".jpg", in, out, jpgparams); } -bool Streamer::_encodeChannel2(const cv::Mat &in, vector<unsigned char> &out, ftl::rgbd::channel_t c, unsigned int b) { +bool Streamer::_encodeImageChannel2(const cv::Mat &in, vector<unsigned char> &out, ftl::rgbd::channel_t c, unsigned int b) { if (c == ftl::rgbd::kChanNone) return false; // NOTE: Should not happen if (isFloatChannel(c) && in.type() == CV_16U && in.channels() == 1) { - vector<int> params = {cv::IMWRITE_PNG_COMPRESSION, bitrate_settings[b].png_compression}; - cv::imencode(".png", in, out, params); + vector<int> params = {cv::IMWRITE_PNG_COMPRESSION, ABRController::getDepthQuality(b)}; + if (!cv::imencode(".png", in, out, params)) { + LOG(ERROR) << "PNG Encoding error"; + return false; + } return true; } else if (!isFloatChannel(c) && in.type() == CV_8UC3) { - vector<int> params = {cv::IMWRITE_JPEG_QUALITY, bitrate_settings[b].jpg_quality}; + vector<int> params = {cv::IMWRITE_JPEG_QUALITY, ABRController::getColourQuality(b)}; cv::imencode(".jpg", in, out, params); return true; } else { @@ -510,4 +743,4 @@ Source *Streamer::get(const std::string &uri) { SHARED_LOCK(mutex_,slk); if (sources_.find(uri) != sources_.end()) return sources_[uri]->src; else return nullptr; -} +}*/ diff --git a/components/rgbd-sources/src/virtual.cpp b/components/rgbd-sources/src/virtual.cpp new file mode 100644 index 0000000000000000000000000000000000000000..62b404155e1f302111e99183347d87c9dbda3a0b --- /dev/null +++ b/components/rgbd-sources/src/virtual.cpp @@ -0,0 +1,138 @@ +#include <ftl/rgbd/virtual.hpp> + +using ftl::rgbd::VirtualSource; +using ftl::rgbd::Source; +using ftl::rgbd::Channel; + +class VirtualImpl : public ftl::rgbd::detail::Source { + public: + explicit VirtualImpl(ftl::rgbd::Source *host, const ftl::rgbd::Camera ¶ms) : ftl::rgbd::detail::Source(host) { + params_ = params; + capabilities_ = ftl::rgbd::kCapMovable | ftl::rgbd::kCapVideo | ftl::rgbd::kCapStereo; + } + + ~VirtualImpl() { + + } + + bool capture(int64_t ts) override { + timestamp_ = ts; + return true; + } + + bool retrieve() override { + return true; + } + + bool compute(int n, int b) override { + if (callback) { + frame.reset(); + + try { + callback(frame); + } catch (std::exception &e) { + LOG(ERROR) << "Exception in render callback: " << e.what(); + } catch (...) { + LOG(ERROR) << "Unknown exception in render callback"; + } + + if (frame.hasChannel(Channel::Colour) && frame.hasChannel(Channel::Depth)) { + frame.download(Channel::Colour + Channel::Depth); + cv::swap(frame.get<cv::Mat>(Channel::Colour), rgb_); + cv::swap(frame.get<cv::Mat>(Channel::Depth), depth_); + LOG(INFO) << "Written: " << rgb_.cols; + } else { + LOG(ERROR) << "Missing colour or depth frame in rendering"; + } + + auto cb = host_->callback(); + if (cb) cb(timestamp_, rgb_, depth_); + } + return true; + } + + bool isReady() override { return true; } + + std::function<void(ftl::rgbd::Frame &)> callback; + ftl::rgbd::Frame frame; +}; + +VirtualSource::VirtualSource(ftl::config::json_t &cfg) : Source(cfg) { + auto params = params_; + impl_ = new VirtualImpl(this, params); +} + +VirtualSource::~VirtualSource() { + +} + +void VirtualSource::onRender(const std::function<void(ftl::rgbd::Frame &)> &f) { + dynamic_cast<VirtualImpl*>(impl_)->callback = f; +} + +/* +void Source::writeFrames(int64_t ts, const cv::Mat &rgb, const cv::Mat &depth) { + if (!impl_) { + UNIQUE_LOCK(mutex_,lk); + rgb.copyTo(rgb_); + depth.copyTo(depth_); + timestamp_ = ts; + } +} + +void Source::writeFrames(int64_t ts, const ftl::cuda::TextureObject<uchar4> &rgb, const ftl::cuda::TextureObject<uint> &depth, cudaStream_t stream) { + if (!impl_) { + UNIQUE_LOCK(mutex_,lk); + timestamp_ = ts; + rgb_.create(rgb.height(), rgb.width(), CV_8UC4); + cudaSafeCall(cudaMemcpy2DAsync(rgb_.data, rgb_.step, rgb.devicePtr(), rgb.pitch(), rgb_.cols * sizeof(uchar4), rgb_.rows, cudaMemcpyDeviceToHost, stream)); + depth_.create(depth.height(), depth.width(), CV_32SC1); + cudaSafeCall(cudaMemcpy2DAsync(depth_.data, depth_.step, depth.devicePtr(), depth.pitch(), depth_.cols * sizeof(uint), depth_.rows, cudaMemcpyDeviceToHost, stream)); + //cudaSafeCall(cudaStreamSynchronize(stream)); // TODO:(Nick) Don't wait here. + stream_ = stream; + //depth_.convertTo(depth_, CV_32F, 1.0f / 1000.0f); + } else { + LOG(ERROR) << "writeFrames cannot be done on this source: " << getURI(); + } +} + +void Source::writeFrames(int64_t ts, const ftl::cuda::TextureObject<uchar4> &rgb, const ftl::cuda::TextureObject<float> &depth, cudaStream_t stream) { + if (!impl_) { + UNIQUE_LOCK(mutex_,lk); + timestamp_ = ts; + rgb.download(rgb_, stream); + //rgb_.create(rgb.height(), rgb.width(), CV_8UC4); + //cudaSafeCall(cudaMemcpy2DAsync(rgb_.data, rgb_.step, rgb.devicePtr(), rgb.pitch(), rgb_.cols * sizeof(uchar4), rgb_.rows, cudaMemcpyDeviceToHost, stream)); + depth.download(depth_, stream); + //depth_.create(depth.height(), depth.width(), CV_32FC1); + //cudaSafeCall(cudaMemcpy2DAsync(depth_.data, depth_.step, depth.devicePtr(), depth.pitch(), depth_.cols * sizeof(float), depth_.rows, cudaMemcpyDeviceToHost, stream)); + + stream_ = stream; + cudaSafeCall(cudaStreamSynchronize(stream_)); + cv::cvtColor(rgb_,rgb_, cv::COLOR_BGRA2BGR); + cv::cvtColor(rgb_,rgb_, cv::COLOR_Lab2BGR); + + if (callback_) callback_(timestamp_, rgb_, depth_); + } +} + +void Source::writeFrames(int64_t ts, const ftl::cuda::TextureObject<uchar4> &rgb, const ftl::cuda::TextureObject<uchar4> &rgb2, cudaStream_t stream) { + if (!impl_) { + UNIQUE_LOCK(mutex_,lk); + timestamp_ = ts; + rgb.download(rgb_, stream); + //rgb_.create(rgb.height(), rgb.width(), CV_8UC4); + //cudaSafeCall(cudaMemcpy2DAsync(rgb_.data, rgb_.step, rgb.devicePtr(), rgb.pitch(), rgb_.cols * sizeof(uchar4), rgb_.rows, cudaMemcpyDeviceToHost, stream)); + rgb2.download(depth_, stream); + //depth_.create(depth.height(), depth.width(), CV_32FC1); + //cudaSafeCall(cudaMemcpy2DAsync(depth_.data, depth_.step, depth.devicePtr(), depth.pitch(), depth_.cols * sizeof(float), depth_.rows, cudaMemcpyDeviceToHost, stream)); + + stream_ = stream; + cudaSafeCall(cudaStreamSynchronize(stream_)); + cv::cvtColor(rgb_,rgb_, cv::COLOR_BGRA2BGR); + cv::cvtColor(rgb_,rgb_, cv::COLOR_Lab2BGR); + cv::cvtColor(depth_,depth_, cv::COLOR_BGRA2BGR); + cv::cvtColor(depth_,depth_, cv::COLOR_Lab2BGR); + } +} +*/ \ No newline at end of file diff --git a/components/rgbd-sources/test/CMakeLists.txt b/components/rgbd-sources/test/CMakeLists.txt index 96e1441c5da6134eb17070d77e5ce9dff3a355f1..78bb6cec7e8c411ffbfe982a1b65320f56439bd5 100644 --- a/components/rgbd-sources/test/CMakeLists.txt +++ b/components/rgbd-sources/test/CMakeLists.txt @@ -5,6 +5,29 @@ add_executable(source_unit ) target_include_directories(source_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include") target_link_libraries(source_unit - ftlcommon Eigen3::Eigen ${CUDA_LIBRARIES}) + ftlcommon ftlcodecs ftlnet Eigen3::Eigen ${CUDA_LIBRARIES}) add_test(SourceUnitTest source_unit) + +### Channel Unit ############################################################### +add_executable(channel_unit + ./tests.cpp + ./channel_unit.cpp +) +target_include_directories(channel_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include") +target_link_libraries(channel_unit + ftlcommon) + +add_test(ChannelUnitTest channel_unit) + +### Frame Unit ################################################################# +add_executable(frame_unit + ./tests.cpp + ./frame_unit.cpp + ../src/frame.cpp +) +target_include_directories(frame_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include") +target_link_libraries(frame_unit + ftlcommon ftlcodecs) + +add_test(FrameUnitTest frame_unit) diff --git a/components/rgbd-sources/test/channel_unit.cpp b/components/rgbd-sources/test/channel_unit.cpp new file mode 100644 index 0000000000000000000000000000000000000000..25171678540f1970088d84e1e842865a4249455e --- /dev/null +++ b/components/rgbd-sources/test/channel_unit.cpp @@ -0,0 +1,88 @@ +#include "catch.hpp" +#include <ftl/rgbd/channels.hpp> + +using ftl::rgbd::Channel; +using ftl::rgbd::Channels; + +TEST_CASE("channel casting", "") { + SECTION("cast channel to channels") { + Channels cs(Channel::Depth); + + REQUIRE( (unsigned int)cs > 0 ); + REQUIRE( cs.count() == 1 ); + } + + SECTION("cast channels to channel") { + Channels cs(Channel::Depth); + Channel c = (Channel)cs; + + REQUIRE( c == Channel::Depth ); + } +} + +TEST_CASE("Channel or-ing", "") { + SECTION("Add channel to channel mask") { + Channels cs(Channel::Depth); + + cs |= Channel::Right; + + REQUIRE( (cs.count() == 2) ); + REQUIRE( cs.has(Channel::Right) ); + REQUIRE( cs.has(Channel::Depth) ); + } + + SECTION("Combine multiple channels in assignment") { + Channels cs; + + cs = Channel::Right | Channel::Flow | Channel::Left; + + REQUIRE( (cs.count() == 3) ); + REQUIRE( cs.has(Channel::Right) ); + REQUIRE( cs.has(Channel::Flow) ); + REQUIRE( cs.has(Channel::Left) ); + } + + SECTION("Combine multiple channels at init") { + Channels cs = Channel::Right | Channel::Flow | Channel::Left; + + REQUIRE( (cs.count() == 3) ); + REQUIRE( cs.has(Channel::Right) ); + REQUIRE( cs.has(Channel::Flow) ); + REQUIRE( cs.has(Channel::Left) ); + } +} + +TEST_CASE("Channel adding", "") { + SECTION("Add channel to channel mask") { + Channels cs(Channel::Depth); + + cs += Channel::Right; + + REQUIRE( (cs.count() == 2) ); + REQUIRE( cs.has(Channel::Right) ); + REQUIRE( cs.has(Channel::Depth) ); + } + + SECTION("Combine multiple channels in assignment") { + Channels cs; + + cs = Channel::Right + Channel::Flow + Channel::Left; + + REQUIRE( (cs.count() == 3) ); + REQUIRE( cs.has(Channel::Right) ); + REQUIRE( cs.has(Channel::Flow) ); + REQUIRE( cs.has(Channel::Left) ); + } +} + +TEST_CASE("Channel subtracting", "") { + SECTION("Remove channel from channel mask") { + Channels cs = Channel::Right | Channel::Flow | Channel::Left; + + cs -= Channel::Flow; + + REQUIRE( (cs.count() == 2) ); + REQUIRE( cs.has(Channel::Right) ); + REQUIRE( cs.has(Channel::Left) ); + } +} diff --git a/components/rgbd-sources/test/frame_unit.cpp b/components/rgbd-sources/test/frame_unit.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6ad528a28fd677e207b05362c0021f7d302784dc --- /dev/null +++ b/components/rgbd-sources/test/frame_unit.cpp @@ -0,0 +1,283 @@ +#include "catch.hpp" +#include <ftl/rgbd/frame.hpp> + +using ftl::rgbd::Frame; +using ftl::rgbd::Channel; +using ftl::rgbd::Channels; +using ftl::rgbd::Format; + +TEST_CASE("Frame::create() cpu mat", "") { + SECTION("in empty channel with format") { + Frame f; + auto &m = f.create<cv::Mat>(Channel::Colour, Format<float4>(200,200)); + + REQUIRE( m.type() == CV_32FC4 ); + REQUIRE( m.cols == 200 ); + REQUIRE( m.rows == 200 ); + } + + SECTION("in non-empty channel with format") { + Frame f; + f.create<cv::Mat>(Channel::Colour, Format<float>(200,100)); + auto &m = f.create<cv::Mat>(Channel::Colour, Format<float4>(200,200)); + + REQUIRE( m.type() == CV_32FC4 ); + REQUIRE( m.cols == 200 ); + REQUIRE( m.rows == 200 ); + } +} + +TEST_CASE("Frame::get()", "") { + SECTION("get a non-existant host channel") { + Frame f; + bool hadexception = false; + + try { + f.get<cv::Mat>(Channel::Colour); + } catch (ftl::exception &e) { + hadexception = true; + } + + REQUIRE( hadexception ); + } + + SECTION("get a non-existant gpu channel") { + Frame f; + bool hadexception = false; + + try { + f.get<cv::cuda::GpuMat>(Channel::Colour); + } catch (ftl::exception &e) { + hadexception = true; + } + + REQUIRE( hadexception ); + } + + SECTION("get a valid host channel") { + Frame f; + bool hadexception = false; + + try { + f.create<cv::Mat>(Channel::Colour, Format<uchar3>(1024,1024)); + auto &m = f.get<cv::Mat>(Channel::Colour); + + REQUIRE( m.type() == CV_8UC3 ); + REQUIRE( m.cols == 1024 ); + REQUIRE( m.rows == 1024 ); + } catch (ftl::exception &e) { + hadexception = true; + } + + REQUIRE( !hadexception ); + } + + SECTION("get a valid gpu channel") { + Frame f; + bool hadexception = false; + + try { + f.create<cv::cuda::GpuMat>(Channel::Colour, Format<uchar3>(1024,1024)); + auto &m = f.get<cv::cuda::GpuMat>(Channel::Colour); + + REQUIRE( m.type() == CV_8UC3 ); + REQUIRE( m.cols == 1024 ); + REQUIRE( m.rows == 1024 ); + } catch (ftl::exception &e) { + hadexception = true; + } + + REQUIRE( !hadexception ); + } + + SECTION("get a cpu mat from gpu channel") { + Frame f; + bool hadexception = false; + + try { + f.create<cv::cuda::GpuMat>(Channel::Colour, Format<uchar3>(1024,1024)); + REQUIRE( f.isGPU(Channel::Colour) ); + + auto &m = f.get<cv::Mat>(Channel::Colour); + + REQUIRE( f.isCPU(Channel::Colour) ); + REQUIRE( m.type() == CV_8UC3 ); + REQUIRE( m.cols == 1024 ); + REQUIRE( m.rows == 1024 ); + } catch (ftl::exception &e) { + hadexception = true; + } + + REQUIRE( !hadexception ); + } + + SECTION("get a gpu mat from cpu channel") { + Frame f; + bool hadexception = false; + + try { + f.create<cv::Mat>(Channel::Colour, Format<uchar3>(1024,1024)); + REQUIRE( f.isCPU(Channel::Colour) ); + + auto &m = f.get<cv::cuda::GpuMat>(Channel::Colour); + + REQUIRE( f.isGPU(Channel::Colour) ); + REQUIRE( m.type() == CV_8UC3 ); + REQUIRE( m.cols == 1024 ); + REQUIRE( m.rows == 1024 ); + } catch (ftl::exception &e) { + hadexception = true; + } + + REQUIRE( !hadexception ); + } +} + +TEST_CASE("Frame::createTexture()", "") { + SECTION("Missing format and no existing mat") { + Frame f; + bool hadexception = false; + + try { + f.createTexture<float>(Channel::Depth); + } catch (ftl::exception &e) { + hadexception = true; + } + + REQUIRE( hadexception ); + } + + SECTION("Missing format but with existing host mat") { + Frame f; + bool hadexception = false; + + try { + f.create<cv::Mat>(Channel::Depth, Format<float>(100,100)); + auto &t = f.createTexture<float>(Channel::Depth); + + REQUIRE( t.width() == 100 ); + REQUIRE( t.cvType() == CV_32FC1 ); + } catch (ftl::exception &e) { + hadexception = true; + } + + REQUIRE( !hadexception ); + } + + SECTION("Missing format but with incorrect existing host mat") { + Frame f; + bool hadexception = false; + + try { + f.create<cv::Mat>(Channel::Depth, Format<uchar4>(100,100)); + f.createTexture<float>(Channel::Depth); + } catch (ftl::exception &e) { + hadexception = true; + } + + REQUIRE( hadexception ); + } + + SECTION("With format and no existing mat") { + Frame f; + bool hadexception = false; + + try { + auto &t = f.createTexture<float>(Channel::Depth, Format<float>(1024,1024)); + REQUIRE( t.cvType() == CV_32FC1 ); + REQUIRE( t.cudaTexture() > 0 ); + REQUIRE( t.devicePtr() != nullptr ); + + auto &m = f.get<cv::cuda::GpuMat>(Channel::Depth); + REQUIRE( m.data == reinterpret_cast<uchar*>(t.devicePtr()) ); + REQUIRE( m.type() == CV_32FC1 ); + } catch (ftl::exception &e) { + hadexception = true; + } + + REQUIRE( !hadexception ); + } + + SECTION("Unchanged type is same texture object") { + Frame f; + bool hadexception = false; + + try { + auto &t = f.createTexture<float>(Channel::Depth, Format<float>(1024,1024)); + REQUIRE( t.cvType() == CV_32FC1 ); + + auto tex = t.cudaTexture(); + float *ptr = t.devicePtr(); + + REQUIRE( ptr != nullptr ); + + auto &t2 = f.createTexture<float>(Channel::Depth, Format<float>(1024,1024)); + + REQUIRE( tex == t2.cudaTexture() ); + REQUIRE( ptr == t2.devicePtr() ); + } catch (ftl::exception &e) { + hadexception = true; + } + + REQUIRE( !hadexception ); + } +} + +TEST_CASE("Frame::getTexture()", "") { + SECTION("Missing texture") { + Frame f; + bool hadexception = false; + + try { + f.getTexture<float>(Channel::Depth); + } catch (ftl::exception &e) { + hadexception = true; + } + + REQUIRE( hadexception ); + } + + SECTION("Texture of incorrect type") { + Frame f; + bool hadexception = false; + + try { + f.createTexture<uchar4>(Channel::Depth, Format<uchar4>(100,100)); + f.getTexture<float>(Channel::Depth); + } catch (ftl::exception &e) { + hadexception = true; + } + + REQUIRE( hadexception ); + } + + SECTION("Valid texture get") { + Frame f; + bool hadexception = false; + + try { + f.createTexture<uchar4>(Channel::Colour, Format<uchar4>(100,100)); + auto &t = f.getTexture<uchar4>(Channel::Colour); + + REQUIRE( t.cvType() == CV_8UC4 ); + REQUIRE( t.width() == 100 ); + } catch (ftl::exception &e) { + hadexception = true; + } + + REQUIRE( !hadexception ); + } +} + +TEST_CASE("Frame::swapTo()", "") { + SECTION("Single host channel to empty frame") { + Frame f1; + Frame f2; + + f1.create<cv::Mat>(Channel::Colour, Format<uchar3>(100,100)); + f1.swapTo(Channels::All(), f2); + + REQUIRE( f2.hasChannel(Channel::Colour) ); + REQUIRE( (f2.get<cv::Mat>(Channel::Colour).cols == 100) ); + } +} diff --git a/components/rgbd-sources/test/source_unit.cpp b/components/rgbd-sources/test/source_unit.cpp index b27b72e070f6e2116632b967699243a9e80926d8..dca38be2ffb6e06b8be416c8de71a7a799c77867 100644 --- a/components/rgbd-sources/test/source_unit.cpp +++ b/components/rgbd-sources/test/source_unit.cpp @@ -10,66 +10,79 @@ static std::string last_type = ""; namespace ftl { namespace rgbd { +class Snapshot {}; + class SnapshotReader { public: - SnapshotReader(const std::string &) {} + explicit SnapshotReader(const std::string &) {} + Snapshot readArchive() { return Snapshot(); }; }; namespace detail { class ImageSource : public ftl::rgbd::detail::Source { public: - ImageSource(ftl::rgbd::Source *host) : ftl::rgbd::detail::Source(host) { + explicit ImageSource(ftl::rgbd::Source *host) : ftl::rgbd::detail::Source(host) { last_type = "image"; } ImageSource(ftl::rgbd::Source *host, const std::string &f) : ftl::rgbd::detail::Source(host) { last_type = "image"; } - bool grab(int n, int b) { return true; }; + bool capture(int64_t ts) { return true; } + bool retrieve() { return true; } + bool compute(int n, int b) { return true; }; bool isReady() { return true; }; }; class StereoVideoSource : public ftl::rgbd::detail::Source { public: - StereoVideoSource(ftl::rgbd::Source *host) : ftl::rgbd::detail::Source(host) { + explicit StereoVideoSource(ftl::rgbd::Source *host) : ftl::rgbd::detail::Source(host) { last_type = "video"; } StereoVideoSource(ftl::rgbd::Source *host, const std::string &f) : ftl::rgbd::detail::Source(host) { last_type = "video"; } - bool grab(int n, int b) { return true; }; + bool capture(int64_t ts) { return true; } + bool retrieve() { return true; } + bool compute(int n, int b) { return true; }; bool isReady() { return true; }; }; class NetSource : public ftl::rgbd::detail::Source { public: - NetSource(ftl::rgbd::Source *host) : ftl::rgbd::detail::Source(host) { + explicit NetSource(ftl::rgbd::Source *host) : ftl::rgbd::detail::Source(host) { last_type = "net"; } - bool grab(int n, int b) { return true; }; + bool capture(int64_t ts) { return true; } + bool retrieve() { return true; } + bool compute(int n, int b) { return true; }; bool isReady() { return true; }; }; class SnapshotSource : public ftl::rgbd::detail::Source { public: - SnapshotSource(ftl::rgbd::Source *host, ftl::rgbd::SnapshotReader &r, const std::string &) : ftl::rgbd::detail::Source(host) { + SnapshotSource(ftl::rgbd::Source *host, ftl::rgbd::Snapshot &r, const std::string &) : ftl::rgbd::detail::Source(host) { last_type = "snapshot"; } - bool grab(int n, int b) { return true; }; + bool capture(int64_t ts) { return true; } + bool retrieve() { return true; } + bool compute(int n, int b) { return true; }; bool isReady() { return true; }; }; class RealsenseSource : public ftl::rgbd::detail::Source { public: - RealsenseSource(ftl::rgbd::Source *host) : ftl::rgbd::detail::Source(host) { + explicit RealsenseSource(ftl::rgbd::Source *host) : ftl::rgbd::detail::Source(host) { last_type = "realsense"; } - bool grab(int n, int b) { return true; }; + bool capture(int64_t ts) { return true; } + bool retrieve() { return true; } + bool compute(int n, int b) { return true; }; bool isReady() { return true; }; }; @@ -79,7 +92,9 @@ class MiddleburySource : public ftl::rgbd::detail::Source { last_type = "middlebury"; } - bool grab(int n, int b) { return true; }; + bool capture(int64_t ts) { return true; } + bool retrieve() { return true; } + bool compute(int n, int b) { return true; }; bool isReady() { return true; }; }; @@ -107,11 +122,11 @@ using ftl::rgbd::Source; using ftl::config::json_t; TEST_CASE("ftl::create<Source>(cfg)", "[rgbd]") { - json_t global = {{"$id","ftl://test"}}; + json_t global = json_t{{"$id","ftl://test"}}; ftl::config::configure(global); SECTION("with valid image file uri") { - json_t cfg = { + json_t cfg = json_t{ {"$id","ftl://test/1"}, {"uri","file://" FTL_SOURCE_DIRECTORY "/components/rgbd-sources/test/data/image.png"} }; @@ -124,7 +139,7 @@ TEST_CASE("ftl::create<Source>(cfg)", "[rgbd]") { } SECTION("with valid video file uri") { - json_t cfg = { + json_t cfg = json_t{ {"$id","ftl://test/2"}, {"uri","file://" FTL_SOURCE_DIRECTORY "/components/rgbd-sources/test/data/video.mp4"} }; @@ -137,7 +152,7 @@ TEST_CASE("ftl::create<Source>(cfg)", "[rgbd]") { } SECTION("with valid net uri") { - json_t cfg = { + json_t cfg = json_t{ {"$id","ftl://test/2"}, {"uri","ftl://utu.fi/dummy"} }; @@ -150,7 +165,7 @@ TEST_CASE("ftl::create<Source>(cfg)", "[rgbd]") { } SECTION("with an invalid uri") { - json_t cfg = { + json_t cfg = json_t{ {"$id","ftl://test/2"}, {"uri","not a uri"} }; @@ -162,7 +177,7 @@ TEST_CASE("ftl::create<Source>(cfg)", "[rgbd]") { } SECTION("with an invalid file uri") { - json_t cfg = { + json_t cfg = json_t{ {"$id","ftl://test/2"}, {"uri","file:///not/a/file"} }; @@ -174,7 +189,7 @@ TEST_CASE("ftl::create<Source>(cfg)", "[rgbd]") { } SECTION("with a missing file") { - json_t cfg = { + json_t cfg = json_t{ {"$id","ftl://test/2"}, {"uri","file:///data/image2.png"} }; @@ -187,11 +202,11 @@ TEST_CASE("ftl::create<Source>(cfg)", "[rgbd]") { } TEST_CASE("Source::set(uri)", "[rgbd]") { - json_t global = {{"$id","ftl://test"}}; + json_t global = json_t{{"$id","ftl://test"}}; ftl::config::configure(global); SECTION("change to different valid URI type") { - json_t cfg = { + json_t cfg = json_t{ {"$id","ftl://test/1"}, {"uri","file://" FTL_SOURCE_DIRECTORY "/components/rgbd-sources/test/data/image.png"} }; diff --git a/components/scene-sources/include/ftl/scene/framescene.hpp b/components/scene-sources/include/ftl/scene/framescene.hpp new file mode 100644 index 0000000000000000000000000000000000000000..0235a60331899ca125514e0905c845bafd1e466c --- /dev/null +++ b/components/scene-sources/include/ftl/scene/framescene.hpp @@ -0,0 +1,28 @@ +#ifndef _FTL_SCENE_FRAMESCENE_HPP_ +#define _FTL_SCENE_FRAMESCENE_HPP_ + +#include <ftl/scene/scene.hpp> + +namespace ftl { +namespace scene { + +/** + * A scene represented internally as a set of image frames that together + * define a point cloud. + */ +class FrameScene : public ftl::scene::Scene { + public: + FrameScene(); + ~FrameScene(); + + bool update(ftl::rgbd::FrameSet &); + + bool render(ftl::rgbd::Source *, ftl::rgbd::Frame &); + bool encode(std::vector<uint8_t> &); + bool decode(const std::vector<uint8_t> &); +}; + +} +} + +#endif // _FTL_SCENE_FRAMESCENE_HPP_ diff --git a/components/scene-sources/include/ftl/scene/scene.hpp b/components/scene-sources/include/ftl/scene/scene.hpp new file mode 100644 index 0000000000000000000000000000000000000000..856819cf403982d54ea3fd12a3cd195b9d437919 --- /dev/null +++ b/components/scene-sources/include/ftl/scene/scene.hpp @@ -0,0 +1,21 @@ +#ifndef _FTL_RECONSTRUCT_SCENE_HPP_ +#define _FTL_RECONSTRUCT_SCENE_HPP_ + +namespace ftl { +namespace scene { + +class Scene { + public: + Scene(); + virtual ~Scene(); + + virtual bool render(ftl::rgbd::Source *, ftl::rgbd::Frame &)=0; + + virtual bool encode(std::vector<uint8_t> &)=0; + virtual bool decode(const std::vector<uint8_t> &)=0; +}; + +} // scene +} // ftl + +#endif // _FTL_RECONSTRUCT_SCENE_HPP_ diff --git a/config/config.jsonc b/config/config.jsonc index 080b0969a7367be4a2eb65bf9e7efaf2e7c20582..17055e8a32bb76bf8f189d38d2fb394fb0c30613 100644 --- a/config/config.jsonc +++ b/config/config.jsonc @@ -163,15 +163,7 @@ ], "display": { "$ref": "#displays/none" }, "virtual": { "$ref": "#virtual_cams/default" }, - "voxelhash": { "$ref": "#hash_conf/default" }, - "merge": { - "register": false, - "targetsource" : "ftl://utu.fi#vision_default/source", - "maxerror": 25, - "iterations" : 10, - "delay" : 500, - "patternsize" : [9, 6] - } + "voxelhash": { "$ref": "#hash_conf/default" } }, "reconstruction_lab": { @@ -185,15 +177,6 @@ "display": { "$ref": "#displays/none" }, "virtual": { "$ref": "#virtual_cams/default" }, "voxelhash": { "$ref": "#hash_conf/default" }, - "merge": { - "targetsource" : "ftl://utu.fi/node4#vision_default/source", - "register": false, - "chain": false, - "maxerror": 25, - "iterations" : 10, - "delay" : 500, - "patternsize" : [9, 6] - }, "stream": {} }, @@ -207,20 +190,9 @@ "display": { "$ref": "#displays/left" }, "virtual": { "$ref": "#virtual_cams/default" }, "voxelhash": { "$ref": "#hash_conf/default" }, - "registration": { - "reference-source" : "ftl://utu.fi/node4#vision_default/source", - "calibration" : { - "max_error": 25, - "run": false, - "iterations" : 10, - "delay" : 500, - "patternsize" : [9, 6] - } - }, "stream": {} }, - "gui_node5": { "net": { "peers": ["tcp://ftl-node-5:9001"] @@ -246,5 +218,34 @@ "net": { "peers": ["ws://localhost:8080/"] } + }, + + "registration_default" : { + "n_views" : 500, + "load_input": false, + "save_input": true, + "save_extrinsic" : false, + "save_intrinsic" : false, + "optimize_intrinsic" : true, + "calibration_data_dir" : "", + "output_directory" : "/smb/ftl-master.local/Shared/Config/", + "net": { + "peers": [ + "tcp://10.0.0.3:9001", + "tcp://10.0.0.4:9001", + "tcp://10.0.0.5:9001", + "tcp://10.0.0.6:9001", + "tcp://10.0.0.7:9001", + "tcp://10.0.0.8:9001" + ] + }, + "sources": [ + {"type": "net", "uri":"ftl://utu.fi/node1#vision_default/source"}, + {"type": "net", "uri":"ftl://utu.fi/node2#vision_default/source"}, + {"type": "net", "uri":"ftl://utu.fi/node3#vision_default/source"}, + {"type": "net", "uri":"ftl://utu.fi/node4#vision_default/source"}, + {"type": "net", "uri":"ftl://utu.fi/node5#vision_default/source"}, + {"type": "net", "uri":"ftl://utu.fi/node6#vision_default/source"} + ] } } diff --git a/config/config_nick.jsonc b/config/config_nick.jsonc index 5e3a29a40abd6da9c577f185584f937c94cf44d0..53673a170693bd7ffd6d4386977417897fa55b8a 100644 --- a/config/config_nick.jsonc +++ b/config/config_nick.jsonc @@ -128,7 +128,15 @@ "SDFUseGradients": false, "showBlockBorders": false }, - "baseline": 0.5, + "baseline": 0.05, + "focal": 700, + "width": 1280, + "height": 720, + "maxDepth": 15.0, + "minDepth": 0.05, + "splatting": true, + "texturing": true, + "upsampling": false, "uri": "device:virtual" } }, @@ -151,12 +159,12 @@ "mls": true, "voxels": false, "clipping": false, - "bbox_x_max": 1.5, - "bbox_x_min": -1.5, + "bbox_x_max": 0.6, + "bbox_x_min": -0.6, "bbox_y_max": 3.0, "bbox_y_min": -3.0, - "bbox_z_max": 2.5, - "bbox_z_min": 0.0, + "bbox_z_max": 3.5, + "bbox_z_min": 2.0, "cudaDevice": 1 }, "rs": { @@ -507,6 +515,34 @@ "stream": {} }, + "reconstruction_snap10": { + "net": { + "peers": [], + "listen": "tcp://*:9002" + }, + "sources": [ + {"uri":"file:///home/nick/Pictures/FTL/snaptest10.tar.gz#0", "index": "camera0"}, + {"uri":"file:///home/nick/Pictures/FTL/snaptest10.tar.gz#1", "index": "camera1"}, + {"uri":"file:///home/nick/Pictures/FTL/snaptest10.tar.gz#2", "index": "camera2"}, + {"uri":"file:///home/nick/Pictures/FTL/snaptest10.tar.gz#3", "index": "camera3"}, + {"uri":"file:///home/nick/Pictures/FTL/snaptest10.tar.gz#4", "index": "camera4"} + ], + "display": { "$ref": "#displays/left" }, + "virtual": { "$ref": "#virtual_cams/default" }, + "voxelhash": { "$ref": "#hash_conf/default" }, + "merge": { + "$id": "ftl://blah/blah", + "targetsource" : "ftl://utu.fi/node3#vision_default/source", + "register": false, + "chain": false, + "maxerror": 100, + "iterations" : 10, + "delay" : 500, + "patternsize" : [9, 6] + }, + "stream": {} + }, + "reconstruction_lab": { "net": { "peers": ["tcp://ftl-node-4:9001", diff --git a/config/config_vision.jsonc b/config/config_vision.jsonc new file mode 100644 index 0000000000000000000000000000000000000000..b73446cefe06aaaf3663b8fe2823822878b5a1a8 --- /dev/null +++ b/config/config_vision.jsonc @@ -0,0 +1,129 @@ +{ + //"$id": "ftl://utu.fi", + "$schema": "", + "calibrations": { + "default": { + "use_intrinsics": true, + "use_extrinsics": true, + "alpha": 0.0 + } + }, + + "disparity": { + "libsgm": { + "algorithm": "libsgm", + "width": 1280, + "height": 720, + "use_cuda": true, + "minimum": 0, + "maximum": 256, + "tau": 0.0, + "gamma": 0.0, + "window_size": 5, + "sigma": 1.5, + "lambda": 8000.0, + "uniqueness": 0.65, + "use_filter": true, + "P1": 8, + "P2": 130, + "filter_radius": 11, + "filter_iter": 2, + "use_off": true, + "off_size": 24, + "off_threshold": 0.75, + "T": 60, + "T_add": 0, + "T_del": 25, + "T_x" : 3.0, + "alpha" : 0.6, + "beta" : 1.7, + "epsilon" : 15.0 + }, + + "rtcensus": { + "algorithm": "rtcensus", + "use_cuda": true, + "minimum": 0, + "maximum": 256, + "tau": 0.0, + "gamma": 0.0, + "window_size": 5, + "sigma": 1.5, + "lambda": 8000.0, + "use_filter": true, + "filter_radius": 3, + "filter_iter": 4 + } + }, + + "sources": { + "stereocam": { + "uri": "device:video", + "feed": { + "flip": false, + "nostereo": false, + "scale": 1.0, + "flip_vert": false, + "max_fps": 500, + "width": 1280, + "height": 720, + "crosshair": false + }, + "use_optflow" : true, + "calibration": { "$ref": "#calibrations/default" }, + "disparity": { "$ref": "#disparity/libsgm" } + }, + "stereovid": {}, + "localhost": {} + + }, + + "vision_default": { + "fps": 20, + "source": { "$ref": "#sources/stereocam" }, + "middlebury": { "$ref": "#middlebury/none" }, + "display": { "$ref": "#displays/none" }, + "net": { "$ref": "#net/default_vision" }, + "stream": { } + }, + + // Listen to localhost + "net": { + "default_vision": { + "listen": "tcp://*:9001", + "peers": [], + "tcp_send_buffer": 100000 //204800 + }, + "default_reconstruct": { + "listen": "tcp://*:9002", + "peers": [] + } + }, + + "displays": { + "none": { + "flip_vert": false, + "disparity": false, + "points": false, + "depth": false, + "left": false, + "right": false + }, + "left": { + "flip_vert": false, + "disparity": false, + "points": false, + "depth": false, + "left": true, + "right": false + } + }, + + "middlebury": { + "none": { + "dataset": "", + "threshold": 10.0, + "scale": 0.25 + } + } +} diff --git a/web-service/src/index.js b/web-service/src/index.js index f4096ecdcb17b32510a3baa6e74245771a49e02f..c6b37247ac0c0e63ad0eefbd8b81ad5594d6b432 100644 --- a/web-service/src/index.js +++ b/web-service/src/index.js @@ -11,32 +11,56 @@ let peer_uris = {}; let uri_data = {}; +/** + * A client stream request object. Each source maintains a list of clients who + * are wanting frames from that source. Clients can only request N frames at a + * time, after that if no new request is received then the client is removed. + * + * @param {Peer} peer Peer websocket wrapper + * @param {number} N Number of frames requested + * @param {number} rate Bitrate index requested + * @param {string} dest URI destination + */ function RGBDClient(peer, N, rate, dest) { this.peer = peer; - this.txmax = N; + this.txmax = N*16; // 16 is for 16 blocks per frame... this will change in the near future this.rate = rate; this.dest = dest; this.txcount = 0; } -RGBDClient.prototype.push = function(uri, rgb, depth) { - this.peer.send(uri, rgb, depth); +/** + * Actually send a frame over network to the client. + */ +RGBDClient.prototype.push = function(uri, frame, ttime, chunk, rgb, depth) { + this.peer.send(uri, frame, ttime, chunk, rgb, depth); this.txcount++; } +/** + * A video stream. Each peer provides a list of these streams. Each stream can + * receive frames from the source and forward those frames to any clients. + * Therefore each of these stream objects maintains a list of clients and + * loops over them whenever a new frame is received. + * + * @param {string} uri Address of the stream + * @param {Peer} peer Origin of the stream + */ function RGBDStream(uri, peer) { this.uri = uri; this.peer = peer; this.title = ""; - this.rgb = null; - this.depth = null; + this.rgb = null; // TODO: No longer works as an image + this.depth = null; // TODO: No longer works as an image this.pose = null; this.clients = []; this.rxcount = 10; this.rxmax = 10; - peer.bind(uri, (rgb, depth) => { - this.pushFrames(rgb, depth); + // Add RPC handler to receive frames from the source + peer.bind(uri, (frame, ttime, chunk, rgb, depth) => { + // Forward frames to all clients + this.pushFrames(frame, ttime, chunk, rgb, depth); this.rxcount++; if (this.rxcount >= this.rxmax && this.clients.length > 0) { this.subscribe(); @@ -60,16 +84,16 @@ RGBDStream.prototype.addClient = function(peer, N, rate, dest) { RGBDStream.prototype.subscribe = function() { this.rxcount = 0; this.rxmax = 10; - console.log("Subscribe to ", this.uri); + //console.log("Subscribe to ", this.uri); this.peer.send("get_stream", this.uri, 10, 0, [Peer.uuid], this.uri); } -RGBDStream.prototype.pushFrames = function(rgb, depth) { +RGBDStream.prototype.pushFrames = function(frame, ttime, chunk, rgb, depth) { this.rgb = rgb; this.depth = depth; for (let i=0; i < this.clients.length; i++) { - this.clients[i].push(this.uri, rgb, depth); + this.clients[i].push(this.uri, frame, ttime, chunk, rgb, depth); } let i=0; @@ -139,7 +163,7 @@ app.ws('/', (ws, req) => { let p = new Peer(ws); p.on("connect", (peer) => { - console.log("Node connected..."); + console.log("Node connected...", peer.string_id); peer_uris[peer.string_id] = []; peer_by_id[peer.string_id] = peer; @@ -150,6 +174,7 @@ app.ws('/', (ws, req) => { peer.name = obj.title; peer.master = (obj.kind == "master"); console.log("Peer name = ", peer.name); + console.log("Details: ", details); checkStreams(peer); }); @@ -175,6 +200,15 @@ app.ws('/', (ws, req) => { checkStreams(p); }); + // Used to sync clocks + p.bind("__ping__", () => { + return Date.now(); + }); + + p.bind("node_details", () => { + return ['{"title": "FTL Web-Service", "id": "0", "kind": "master"}']; + }); + p.bind("list_streams", () => { return Object.keys(uri_data); }); @@ -189,15 +223,25 @@ app.ws('/', (ws, req) => { } }); - p.proxy("source_calibration", (cb, uri) => { + // Requests camera calibration information + p.proxy("source_details", (cb, uri, chan) => { let peer = uri_data[uri].peer; if (peer) { - peer.rpc("source_calibration", cb, uri); + peer.rpc("source_details", cb, uri, chan); } }); - p.bind("set_pose", (uri, vec) => { + // Get the current position of a camera + p.proxy("get_pose", (cb, uri) => { //console.log("SET POSE"); + let peer = uri_data[uri].peer; + if (peer) { + peer.rpc("get_pose", cb, uri); + } + }); + + // Change the position of a camera + p.bind("set_pose", (uri, vec) => { let peer = uri_data[uri].peer; if (peer) { uri_data[uri].pose = vec; @@ -205,6 +249,7 @@ app.ws('/', (ws, req) => { } }); + // Request from frames from a source p.bind("get_stream", (uri, N, rate, pid, dest) => { let peer = uri_data[uri].peer; if (peer) { @@ -213,6 +258,7 @@ app.ws('/', (ws, req) => { } }); + // Register a new stream p.bind("add_stream", (uri) => { console.log("Adding stream: ", uri); //uri_to_peer[streams[i]] = peer; diff --git a/web-service/src/peer.js b/web-service/src/peer.js index b1fc40dd8e6f7198a5857fcaec92d08bc9628307..51cd78e6a9ad07ec17f478646b3b60171e6280bc 100644 --- a/web-service/src/peer.js +++ b/web-service/src/peer.js @@ -6,6 +6,7 @@ const kConnecting = 1; const kConnected = 2; const kDisconnected = 3; +// Generate a unique id for this webservice let my_uuid = new Uint8Array(16); my_uuid[0] = 44; my_uuid = Buffer.from(my_uuid); @@ -13,6 +14,10 @@ my_uuid = Buffer.from(my_uuid); const kMagic = 0x0009340053640912; const kVersion = 0; +/** + * Wrap a web socket with a MsgPack RCP protocol that works with our C++ version. + * @param {websocket} ws Websocket object + */ function Peer(ws) { this.sock = ws; this.status = kConnecting; @@ -79,26 +84,34 @@ function Peer(ws) { Peer.uuid = my_uuid; +/** + * @private + */ Peer.prototype._dispatchNotification = function(name, args) { if (this.bindings.hasOwnProperty(name)) { + //console.log("Notification for: ", name); this.bindings[name].apply(this, args); } else { console.log("Missing handler for: ", name); } } +/** + * @private + */ Peer.prototype._dispatchCall = function(name, id, args) { if (this.bindings.hasOwnProperty(name)) { - console.log("Call for:", name, id); - let res = this.bindings[name].apply(this, args); + //console.log("Call for:", name, id); try { + let res = this.bindings[name].apply(this, args); this.sock.send(encode([1,id,name,res])); } catch(e) { + console.error("Could to dispatch or return call"); this.close(); } } else if (this.proxies.hasOwnProperty(name)) { - console.log("Proxy for:", name, id); + //console.log("Proxy for:", name, id); args.unshift((res) => { try { this.sock.send(encode([1,id,name,res])); @@ -112,6 +125,9 @@ Peer.prototype._dispatchCall = function(name, id, args) { } } +/** + * @private + */ Peer.prototype._dispatchResponse = function(id, res) { if (this.callbacks.hasOwnProperty(id)) { this.callbacks[id].call(this, res); @@ -121,6 +137,14 @@ Peer.prototype._dispatchResponse = function(id, res) { } } +/** + * Register an RPC handler that will be called from a remote machine. Remotely + * passed arguments are provided to the given function as normal arguments, and + * if the function returns a value, it will be returned over the network also. + * + * @param {string} name The name of the function + * @param {function} f A function or lambda to be callable remotely + */ Peer.prototype.bind = function(name, f) { if (this.bindings.hasOwnProperty(name)) { //console.error("Duplicate bind to same procedure"); @@ -130,6 +154,10 @@ Peer.prototype.bind = function(name, f) { } } +/** + * Allow an RPC call to pass through to another machine with minimal local + * processing. + */ Peer.prototype.proxy = function(name, f) { if (this.proxies.hasOwnProperty(name)) { //console.error("Duplicate proxy to same procedure"); @@ -139,6 +167,13 @@ Peer.prototype.proxy = function(name, f) { } } +/** + * Call a procedure on a remote machine. + * + * @param {string} name Name of the procedure + * @param {function} cb Callback to receive return value as argument + * @param {...} args Any number of arguments to also pass to remote procedure + */ Peer.prototype.rpc = function(name, cb, ...args) { let id = this.cbid++; this.callbacks[id] = cb; @@ -158,6 +193,12 @@ Peer.prototype.sendB = function(name, args) { } } +/** + * Call a remote procedure but with no return value expected. + * + * @param {string} name Name of the procedure + * @param {...} args Any number of arguments to also pass to remote procedure + */ Peer.prototype.send = function(name, ...args) { try { this.sock.send(encode([0, name, args])); @@ -171,6 +212,9 @@ Peer.prototype.close = function() { this.status = kDisconnected; } +/** + * @private + */ Peer.prototype._notify = function(evt, ...args) { if (this.events.hasOwnProperty(evt)) { for (let i=0; i<this.events[evt].length; i++) { @@ -180,6 +224,13 @@ Peer.prototype._notify = function(evt, ...args) { } } +/** + * Register a callback for socket events. Events include: 'connect', + * 'disconnect' and 'error'. + * + * @param {string} evt Event name + * @param {function} f Callback on event + */ Peer.prototype.on = function(evt, f) { if (!this.events.hasOwnProperty(evt)) { this.events[evt] = [];