diff --git a/volume-cartographer/apps/src/vc_obj2tifxyz.cpp b/volume-cartographer/apps/src/vc_obj2tifxyz.cpp index 0773065952..daba7b9709 100644 --- a/volume-cartographer/apps/src/vc_obj2tifxyz.cpp +++ b/volume-cartographer/apps/src/vc_obj2tifxyz.cpp @@ -1,6 +1,7 @@ #include "vc/core/util/Surface.hpp" #include "vc/core/util/QuadSurface.hpp" #include "vc/core/util/Slicing.hpp" +#include "vc/core/util/TifxyzIdentity.hpp" #include #include @@ -518,7 +519,7 @@ int main(int argc, char *argv[]) std::cout << "usage: " << argv[0] << " [stretch_factor] [mesh_units]" << " [--uv-metric] [--uv-to-obj=] [--uv-downsample=]" - << " [--grid-cap=]" << std::endl; + << " [--grid-cap=] [--uuid=]" << std::endl; std::cout << "Converts an OBJ file to tifxyz format" << std::endl; std::cout << std::endl; std::cout << "Parameters:" << std::endl; @@ -530,6 +531,7 @@ int main(int argc, char *argv[]) std::cout << " --uv-to-obj= : OBJ units per 1 UV unit (default: 1.0). Only used with --uv-metric." << std::endl; std::cout << " --uv-downsample= : Uniform UV decimation factor (>=1.0). Reduces grid by ~f^2." << std::endl; std::cout << " --grid-cap= : Upper bound on total grid pixels. Implies extra decimation if needed." << std::endl; + std::cout << " --uuid= : Metadata UUID. Defaults to the output-directory basename." << std::endl; std::cout << " --tifxyz-source=: Original tifxyz being flattened. Its meta.json scale sizes the" << std::endl; std::cout << " output grid to the input sampling density (output scale == input" << std::endl; std::cout << " scale). If /approval.tif exists, it is resampled onto the new" << std::endl; @@ -552,6 +554,7 @@ int main(int argc, char *argv[]) float uv_downsample = 1.0f; uint64_t grid_cap = 0; std::string tifxyz_source; // original tifxyz dir: provides scale + approval mask + std::string uuid_override; // Backward-compatible parsing: // positional numbers: stretch_factor, mesh_units @@ -599,6 +602,14 @@ int main(int argc, char *argv[]) tifxyz_source = a.substr(std::string("--tifxyz-source=").size()); continue; } + if (starts_with(a, "--uuid=")) { + uuid_override = a.substr(std::string("--uuid=").size()); + if (uuid_override.empty()) { + std::cerr << "Invalid value for --uuid (must be non-empty)\n"; + return EXIT_FAILURE; + } + continue; + } // numbers (legacy positional) if (consumed_numbers == 0) { stretch_factor = std::atof(a.c_str()); @@ -707,10 +718,8 @@ int main(int argc, char *argv[]) } // Generate a UUID for the surface - std::string uuid = output_dir.filename().string(); - if (uuid.empty()) { - uuid = obj_path.stem().string(); - } + const std::string uuid = + vc::util::resolveTifxyzUuid(output_dir, obj_path, uuid_override); std::cout << "Saving to tifxyz format..." << std::endl; diff --git a/volume-cartographer/apps/src/vc_obj2tifxyz_legacy.cpp b/volume-cartographer/apps/src/vc_obj2tifxyz_legacy.cpp index 577c85bd1d..50f36348b8 100644 --- a/volume-cartographer/apps/src/vc_obj2tifxyz_legacy.cpp +++ b/volume-cartographer/apps/src/vc_obj2tifxyz_legacy.cpp @@ -1,4 +1,5 @@ #include "vc/core/util/QuadSurface.hpp" +#include "vc/core/util/TifxyzIdentity.hpp" #include #include @@ -304,13 +305,14 @@ class ObjToTifxyzConverter { int main(int argc, char *argv[]) { - if (argc < 3 || argc > 4) { - std::cout << "usage: " << argv[0] << " [step_size]" << std::endl; + if (argc < 3) { + std::cout << "usage: " << argv[0] << " [step_size] [--uuid=]" << std::endl; std::cout << "Converts an OBJ file to tifxyz format" << std::endl; std::cout << std::endl; std::cout << "Parameters:" << std::endl; std::cout << " step_size: UV units per grid cell (default: 20)" << std::endl; std::cout << " Scale will be 1/step_size (default: 0.05)" << std::endl; + std::cout << " --uuid=: Metadata UUID. Defaults to the output-directory basename." << std::endl; std::cout << std::endl; std::cout << "Example: " << argv[0] << " mesh.obj output_dir" << std::endl; std::cout << "Example: " << argv[0] << " mesh.obj output_dir 10" << std::endl; @@ -320,13 +322,29 @@ int main(int argc, char *argv[]) std::filesystem::path obj_path = argv[1]; std::filesystem::path output_dir = argv[2]; float step_size = 20.0f; - - if (argc >= 4) { - step_size = std::atof(argv[3]); + std::string uuid_override; + + bool consumed_step_size = false; + for (int i = 3; i < argc; ++i) { + const std::string arg = argv[i]; + if (arg.rfind("--uuid=", 0) == 0) { + uuid_override = arg.substr(std::string("--uuid=").size()); + if (uuid_override.empty()) { + std::cerr << "Invalid value for --uuid (must be non-empty)" << std::endl; + return EXIT_FAILURE; + } + continue; + } + if (consumed_step_size) { + std::cerr << "Unknown argument: " << arg << std::endl; + return EXIT_FAILURE; + } + step_size = std::atof(arg.c_str()); if (step_size <= 0) { - std::cerr << "Invalid step size: " << step_size << std::endl; + std::cerr << "Invalid step size: " << arg << std::endl; return EXIT_FAILURE; } + consumed_step_size = true; } if (!std::filesystem::exists(obj_path)) { @@ -358,10 +376,8 @@ int main(int argc, char *argv[]) } // Generate a UUID for the surface - std::string uuid = output_dir.filename().string(); - if (uuid.empty()) { - uuid = obj_path.stem().string(); - } + const std::string uuid = + vc::util::resolveTifxyzUuid(output_dir, obj_path, uuid_override); std::cout << "Saving to tifxyz format..." << std::endl; diff --git a/volume-cartographer/core/include/vc/core/util/TifxyzIdentity.hpp b/volume-cartographer/core/include/vc/core/util/TifxyzIdentity.hpp new file mode 100644 index 0000000000..5ac625bdda --- /dev/null +++ b/volume-cartographer/core/include/vc/core/util/TifxyzIdentity.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include + +namespace vc::util { + +/** Resolve the UUID written to a TIFXYZ meta.json. + * + * An explicit UUID lets production pipelines keep artifact directories named + * after a processing role (for example, "output_tifxyz") without giving + * unrelated surfaces the same collection identity. With no override, retain + * the historical output-directory-basename behavior. + */ +inline std::string resolveTifxyzUuid( + const std::filesystem::path& outputDirectory, + const std::filesystem::path& inputPath, + const std::string& explicitUuid = {}) +{ + if (!explicitUuid.empty()) { + return explicitUuid; + } + + std::string uuid = outputDirectory.filename().string(); + if (uuid.empty()) { + uuid = inputPath.stem().string(); + } + if (uuid.empty()) { + throw std::invalid_argument( + "cannot derive TIFXYZ UUID from an empty output directory and input path"); + } + return uuid; +} + +} // namespace vc::util diff --git a/volume-cartographer/core/test/CMakeLists.txt b/volume-cartographer/core/test/CMakeLists.txt index 6310646c5e..ad03b9c0ab 100644 --- a/volume-cartographer/core/test/CMakeLists.txt +++ b/volume-cartographer/core/test/CMakeLists.txt @@ -66,6 +66,8 @@ endfunction() vc_add_test(NAME test_smoke LIBS doctest::doctest) +vc_add_test(NAME test_tifxyz_identity) + vc_add_test(NAME test_appendmask_render) vc_add_test(NAME test_chunked_plane_sampler_fallback) diff --git a/volume-cartographer/core/test/test_tifxyz_identity.cpp b/volume-cartographer/core/test/test_tifxyz_identity.cpp new file mode 100644 index 0000000000..f7f8996091 --- /dev/null +++ b/volume-cartographer/core/test/test_tifxyz_identity.cpp @@ -0,0 +1,31 @@ +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include + +#include "vc/core/util/TifxyzIdentity.hpp" + +TEST_CASE("explicit TIFXYZ UUID overrides a generic output directory") +{ + CHECK(vc::util::resolveTifxyzUuid( + "/tmp/output_tifxyz", "/tmp/mesh.obj", "PHerc1447-segment") == + "PHerc1447-segment"); +} + +TEST_CASE("TIFXYZ UUID keeps the historical output-directory default") +{ + CHECK(vc::util::resolveTifxyzUuid( + "/tmp/output_tifxyz", "/tmp/mesh.obj") == "output_tifxyz"); +} + +TEST_CASE("TIFXYZ UUID falls back to the input stem for a trailing output path") +{ + CHECK(vc::util::resolveTifxyzUuid( + std::filesystem::path("/tmp/output/"), "/tmp/source-mesh.obj") == + "source-mesh"); +} + +TEST_CASE("TIFXYZ UUID rejects completely empty identity inputs") +{ + CHECK_THROWS_AS( + vc::util::resolveTifxyzUuid(std::filesystem::path{}, std::filesystem::path{}), + std::invalid_argument); +}