diff --git a/CMakeLists.txt b/CMakeLists.txt index 40ed871..9c6d455 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,28 @@ set(OPENCV_DIR "OPENCV_DIR-NOTFOUND" CACHE PATH "Path to the OPENCV installation set(OPENSPLAT_MAX_CUDA_COMPATIBILITY OFF CACHE BOOL "Build for maximum CUDA device compatibility") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +# Read version +file(READ "VERSION" APP_VERSION) + +# Read git commit +set(GIT_REV "") +execute_process(COMMAND git rev-parse --short HEAD + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + OUTPUT_VARIABLE GIT_REV + ERROR_QUIET) +string(REGEX REPLACE "\n$" "" GIT_REV "${GIT_REV}") +if (NOT "${GIT_REV}" STREQUAL "") + set(DAPP_VERSION "${APP_VERSION} (git commit ${GIT_REV})") + set(DAPP_REVISION "${GIT_REV}") +else() + set(DAPP_VERSION "${APP_VERSION}") + set(DAPP_REVISION "dev") +endif() + +message("OpenSplat Version: ${DAPP_VERSION}") +add_compile_options("-DAPP_VERSION=\"${DAPP_VERSION}\"") +add_compile_options("-DAPP_REVISION=\"${DAPP_REVISION}\"") + # Don't complain about the override from NANOFLANN_BUILD_EXAMPLES set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) # Use time-of-extraction for FetchContent'ed files modification time diff --git a/README.md b/README.md index 85e8af9..031b0f6 100644 --- a/README.md +++ b/README.md @@ -212,7 +212,7 @@ cd build ./opensplat /path/to/banana -n 2000 ``` -The program will generate an output `splat.ply` file which can then be dragged and dropped in one of the many [viewers](https://github.com/MrNeRF/awesome-3D-gaussian-splatting?tab=readme-ov-file#viewers) such as https://playcanvas.com/viewer. You can also edit/cleanup the scene using https://playcanvas.com/supersplat/editor +The program will generate an output `splat.ply` file which can then be dragged and dropped in one of the many [viewers](https://github.com/MrNeRF/awesome-3D-gaussian-splatting?tab=readme-ov-file#viewers) such as https://playcanvas.com/viewer. You can also edit/cleanup the scene using https://playcanvas.com/supersplat/editor. The program will also output a `cameras.json` file in the same directory which can be used by some viewers. To run on your own data, choose the path to an existing [COLMAP](https://colmap.github.io/), [OpenSfM](https://github.com/mapillary/OpenSfM), [ODM](https://github.com/OpenDroneMap/ODM) or [nerfstudio](https://docs.nerf.studio/quickstart/custom_dataset.html) project. The project must have sparse points included (random initialization is not supported, see https://github.com/pierotofy/OpenSplat/issues/7). diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..9c1218c --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.1.3 \ No newline at end of file diff --git a/constants.hpp b/constants.hpp index 71cc026..627225f 100644 --- a/constants.hpp +++ b/constants.hpp @@ -4,4 +4,12 @@ #define PI 3.14159265358979323846 #define FLOAT_EPS 1e-9f +#ifndef APP_VERSION +#define APP_VERSION "dev" +#endif + +#ifndef APP_REVISION +#define APP_REVISION "dev" +#endif + #endif \ No newline at end of file diff --git a/input_data.cpp b/input_data.cpp index 7f1c588..1312818 100644 --- a/input_data.cpp +++ b/input_data.cpp @@ -1,9 +1,11 @@ #include +#include #include "input_data.hpp" #include "cv_utils.hpp" namespace fs = std::filesystem; using namespace torch::indexing; +using json = nlohmann::json; namespace ns{ InputData inputDataFromNerfStudio(const std::string &projectRoot); } namespace cm{ InputData inputDataFromColmap(const std::string &projectRoot); } @@ -145,3 +147,47 @@ std::tuple, Camera *> InputData::getCameras(bool validate, c return std::make_tuple(cams, valCam); } } + + +void InputData::saveCameras(const std::string &filename, bool keepCrs){ + json j = json::array(); + + for (size_t i = 0; i < cameras.size(); i++){ + Camera &cam = cameras[i]; + + json camera = json::object(); + camera["id"] = i; + camera["img_name"] = fs::path(cam.filePath).filename().string(); + camera["width"] = cam.width; + camera["height"] = cam.height; + camera["fx"] = cam.fx; + camera["fy"] = cam.fy; + + torch::Tensor R = cam.camToWorld.index({Slice(None, 3), Slice(None, 3)}); + torch::Tensor T = cam.camToWorld.index({Slice(None, 3), Slice(3,4)}).squeeze(); + + // Flip z and y + R = torch::matmul(R, torch::diag(torch::tensor({1.0f, -1.0f, -1.0f}))); + + if (keepCrs) T = (T / scale) + translation; + + std::vector position(3); + std::vector> rotation(3, std::vector(3)); + for (int i = 0; i < 3; i++) { + position[i] = T[i].item(); + for (int j = 0; j < 3; j++) { + rotation[i][j] = R[i][j].item(); + } + } + + camera["position"] = position; + camera["rotation"] = rotation; + j.push_back(camera); + } + + std::ofstream of(filename); + of << j; + of.close(); + + std::cout << "Wrote " << filename << std::endl; +} \ No newline at end of file diff --git a/input_data.hpp b/input_data.hpp index eeabcc3..4a2bb4a 100644 --- a/input_data.hpp +++ b/input_data.hpp @@ -33,7 +33,6 @@ struct Camera{ width(width), height(height), fx(fx), fy(fy), cx(cx), cy(cy), k1(k1), k2(k2), k3(k3), p1(p1), p2(p2), camToWorld(camToWorld), filePath(filePath) {} - torch::Tensor getIntrinsicsMatrix(); bool hasDistortionParameters(); std::vector undistortionParameters(); @@ -57,6 +56,8 @@ struct InputData{ Points points; std::tuple, Camera *> getCameras(bool validate, const std::string &valImage = "random"); + + void saveCameras(const std::string &filename, bool keepCrs); }; InputData inputDataFromX(const std::string &projectRoot); diff --git a/model.cpp b/model.cpp index 1f20642..9e7f7d8 100644 --- a/model.cpp +++ b/model.cpp @@ -61,7 +61,6 @@ torch::Tensor Model::forward(Camera& cam, int step){ const int height = static_cast(static_cast(cam.height) / scaleFactor); const int width = static_cast(static_cast(cam.width) / scaleFactor); - // TODO: these can be moved to Camera and computed only once? torch::Tensor R = cam.camToWorld.index({Slice(None, 3), Slice(None, 3)}); torch::Tensor T = cam.camToWorld.index({Slice(None, 3), Slice(3,4)}); diff --git a/opensplat.cpp b/opensplat.cpp index 470e6f2..ee37e6d 100644 --- a/opensplat.cpp +++ b/opensplat.cpp @@ -4,13 +4,14 @@ #include "input_data.hpp" #include "utils.hpp" #include "cv_utils.hpp" +#include "constants.hpp" #include namespace fs = std::filesystem; using namespace torch::indexing; int main(int argc, char *argv[]){ - cxxopts::Options options("opensplat", "Open Source 3D Gaussian Splats generator"); + cxxopts::Options options("opensplat", "Open Source 3D Gaussian Splats generator - " APP_VERSION); options.add_options() ("i,input", "Path to nerfstudio project", cxxopts::value()) ("o,output", "Path where to save output scene", cxxopts::value()->default_value("splat.ply")) @@ -37,6 +38,7 @@ int main(int argc, char *argv[]){ ("split-screen-size", "Split gaussians that are larger than this percentage of screen space", cxxopts::value()->default_value("0.05")) ("h,help", "Print usage") + ("version", "Print version") ; options.parse_positional({ "input" }); options.positional_help("[colmap/nerfstudio/opensfm/odm project path]"); @@ -50,11 +52,16 @@ int main(int argc, char *argv[]){ return EXIT_FAILURE; } + if (result.count("version")){ + std::cout << APP_VERSION << std::endl; + return EXIT_SUCCESS; + } if (result.count("help") || !result.count("input")) { std::cout << options.help() << std::endl; return EXIT_SUCCESS; } + const std::string projectRoot = result["input"].as(); const std::string outputScene = result["output"].as(); const int saveEvery = result["save-every"].as(); @@ -94,6 +101,7 @@ int main(int argc, char *argv[]){ try{ InputData inputData = inputDataFromX(projectRoot); + parallel_for(inputData.cameras.begin(), inputData.cameras.end(), [&downScaleFactor](Camera &cam){ cam.loadImage(downScaleFactor); }); @@ -146,6 +154,7 @@ int main(int argc, char *argv[]){ } } + inputData.saveCameras((fs::path(outputScene).parent_path() / "cameras.json").string(), keepCrs); model.save(outputScene); // model.saveDebugPly("debug.ply"); diff --git a/simple_trainer.cpp b/simple_trainer.cpp index 93bc443..9912c5d 100644 --- a/simple_trainer.cpp +++ b/simple_trainer.cpp @@ -22,7 +22,7 @@ using namespace torch::indexing; namespace fs = std::filesystem; int main(int argc, char **argv){ - cxxopts::Options options("simple_trainer", "Test program for gsplat execution"); + cxxopts::Options options("simple_trainer", "Test program for gsplat execution - " APP_VERSION); options.add_options() ("cpu", "Force CPU execution") ("width", "Test image width", cxxopts::value()->default_value("256")) @@ -32,6 +32,7 @@ int main(int argc, char **argv){ ("lr", "Learning rate", cxxopts::value()->default_value("0.01")) ("render", "Save rendered images to folder", cxxopts::value()->default_value("")) ("h,help", "Print usage") + ("version", "Print version") ; cxxopts::ParseResult result; try { @@ -48,6 +49,11 @@ int main(int argc, char **argv){ return EXIT_SUCCESS; } + if (result.count("version")) { + std::cout << APP_VERSION << std::endl; + return EXIT_SUCCESS; + } + int width = result["width"].as(), height = result["height"].as(); int numPoints = result["points"].as();