Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically align UV map to specified volume axis #57

Merged
merged 12 commits into from
Nov 13, 2023
119 changes: 100 additions & 19 deletions apps/src/Render.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ enum class FlatteningAlgorithm { ABF = 0, LSCM, Orthographic };
// Available texturing algorithms
enum class Method { Composite = 0, Intersection, Integral, Thickness };

static auto GetGeneralOpts() -> po::options_description
namespace
{

auto GetGeneralOpts() -> po::options_description
{
// clang-format off
po::options_description opts("General Options");
Expand All @@ -48,7 +51,7 @@ static auto GetGeneralOpts() -> po::options_description
return opts;
}

static auto GetIOOpts() -> po::options_description
auto GetIOOpts() -> po::options_description
{
// clang-format off
po::options_description opts("Input/Output Options");
Expand All @@ -71,7 +74,7 @@ static auto GetIOOpts() -> po::options_description
return opts;
}

static auto GetMeshingOpts() -> po::options_description
auto GetMeshingOpts() -> po::options_description
{
// clang-format off
po::options_description opts("Meshing Options");
Expand Down Expand Up @@ -110,7 +113,7 @@ static auto GetMeshingOpts() -> po::options_description
return opts;
}

static auto GetUVOpts() -> po::options_description
auto GetUVOpts() -> po::options_description
{
// clang-format off
po::options_description opts("Flattening & UV Options");
Expand All @@ -122,11 +125,17 @@ static auto GetUVOpts() -> po::options_description
" 2 = Orthographic Projection")
("uv-reuse", "If input-mesh is specified, attempt to use its existing "
"UV map instead of generating a new one.")
("uv-align-to-axis", po::value<UVMap::AlignmentAxis>()->default_value(UVMap::AlignmentAxis::ZPos, "+Z"),
"Rotate the UV map so that the specified volume direction is aligned "
"as well as possible to \'up\' in the texture image (-Y). "
"Performed before uv-rotate and uv-flip. Options: None, +Z, -Z, "
"+Y, -Y, +X, -X")
("uv-rotate", po::value<double>(), "Rotate the generated UV map by an "
"angle in degrees (counterclockwise).")
"angle in degrees (counterclockwise). Performed after "
"uv-align-to-axis and before uv-flip.")
("uv-flip", po::value<int>(),
"Flip the UV map along an axis. If uv-rotate is specified, flip is "
"performed after rotation.\n"
"Flip the UV map along an axis. Performed after uv-align-to-axis "
"and uv-rotate.\n"
"Axis along which to flip:\n"
" 0 = Vertical\n"
" 1 = Horizontal\n"
Expand All @@ -145,7 +154,7 @@ static auto GetUVOpts() -> po::options_description
return opts;
}

static auto GetFilteringOpts() -> po::options_description
auto GetFilteringOpts() -> po::options_description
{
// clang-format off
po::options_description opts("Generic Texture Filtering Options");
Expand Down Expand Up @@ -179,7 +188,7 @@ static auto GetFilteringOpts() -> po::options_description
return opts;
}

static auto GetCompositeOpts() -> po::options_description
auto GetCompositeOpts() -> po::options_description
{
// clang-format off
po::options_description opts("Composite Texture Options");
Expand All @@ -196,7 +205,7 @@ static auto GetCompositeOpts() -> po::options_description
return opts;
}

static auto GetIntegralOpts() -> po::options_description
auto GetIntegralOpts() -> po::options_description
{
// clang-format off
po::options_description opts("Integral Texture Options");
Expand Down Expand Up @@ -227,7 +236,7 @@ static auto GetIntegralOpts() -> po::options_description
return opts;
}

static auto GetThicknessOpts() -> po::options_description
auto GetThicknessOpts() -> po::options_description
{
// clang-format off
po::options_description opts("Thickness Texture Options");
Expand All @@ -240,19 +249,20 @@ static auto GetThicknessOpts() -> po::options_description

return opts;
}
} // namespace

auto main(int argc, char* argv[]) -> int
{
///// Parse the command line options /////
po::options_description all("Usage");
all.add(GetGeneralOpts())
.add(GetIOOpts())
.add(GetMeshingOpts())
.add(GetUVOpts())
.add(GetFilteringOpts())
.add(GetCompositeOpts())
.add(GetIntegralOpts())
.add(GetThicknessOpts());
all.add(::GetGeneralOpts())
.add(::GetIOOpts())
.add(::GetMeshingOpts())
.add(::GetUVOpts())
.add(::GetFilteringOpts())
.add(::GetCompositeOpts())
.add(::GetIntegralOpts())
.add(::GetThicknessOpts());

// Parse the cmd line
po::variables_map parsed;
Expand Down Expand Up @@ -541,6 +551,19 @@ auto main(int argc, char* argv[]) -> int
}
}

// Align to axis
auto uvAlignAxis = parsed["uv-align-to-axis"].as<UVMap::AlignmentAxis>();
if (uvAlignAxis != UVMap::AlignmentAxis::None) {
auto align = graph->insertNode<AlignUVMapToAxisNode>();
align->uvMapIn = *results["uvMap"];
align->mesh = *results["mesh"];
align->axis = uvAlignAxis;
results["uvMap"] = &align->uvMapOut;

// UV Mesh needs to be recalculated after transform
results.erase("uvMesh");
}

// Rotate
if (parsed.count("uv-rotate") > 0) {
auto rotate = graph->insertNode<RotateUVMapNode>();
Expand Down Expand Up @@ -764,3 +787,61 @@ auto main(int argc, char* argv[]) -> int
return EXIT_FAILURE;
}
}

///*** Custom program_options validators ***///

// UVAlignmentAxis
void validate(
boost::any& v,
const std::vector<std::string>& values,
UVMap::AlignmentAxis* /* target type */,
int /* unused */)
{
using namespace boost::program_options;
// argument only passed once
validators::check_first_occurrence(v);
// get a single string, error ir more than one
const auto& s = validators::get_single_string(values);
// cast to type
v = boost::any(boost::lexical_cast<UVMap::AlignmentAxis>(s));
}

namespace volcart
{
auto operator>>(std::istream& is, UVMap::AlignmentAxis& v) -> std::istream&
{
// get the first token
std::string s;
if (not(is >> s)) {
return is;
};

// find a match
vc::to_lower(s);
if (s == "none") {
v = UVMap::AlignmentAxis::None;
is.clear();
} else if (s == "+z") {
v = UVMap::AlignmentAxis::ZPos;
is.clear();
} else if (s == "-z") {
v = UVMap::AlignmentAxis::ZNeg;
is.clear();
} else if (s == "+y") {
v = UVMap::AlignmentAxis::YPos;
is.clear();
} else if (s == "-y") {
v = UVMap::AlignmentAxis::YNeg;
is.clear();
} else if (s == "+x") {
v = UVMap::AlignmentAxis::XPos;
is.clear();
} else if (s == "-x") {
v = UVMap::AlignmentAxis::XNeg;
is.clear();
} else {
is.setstate(std::ios_base::failbit);
}
return is;
}
} // namespace volcart
2 changes: 2 additions & 0 deletions apps/src/RenderTexturing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -582,5 +582,7 @@ vc::Color GetScaleColorOpt()
return vc::color::GREEN;
case ScaleColorOpt::Cyan:
return vc::color::CYAN;
default:
throw std::runtime_error("Invalid scale color option");
}
}
4 changes: 4 additions & 0 deletions core/include/vc/core/io/PointSetIO.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ class PointSetIO
return ReadOrderedPointSetBinary(path);
case IOMode::ASCII:
return ReadOrderedPointSetAscii(path);
default:
throw IOException("unsupported IOMode");
}
}

Expand All @@ -84,6 +86,8 @@ class PointSetIO
return ReadPointSetBinary(path);
case IOMode::ASCII:
return ReadPointSetAscii(path);
default:
throw IOException("unsupported IOMode");
}
}
/**@}*/
Expand Down
28 changes: 28 additions & 0 deletions core/include/vc/core/types/UVMap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ class UVMap
/** Flipping axis enumeration */
enum class FlipAxis { Vertical = 0, Horizontal, Both };

/** Align to axis enumeration */
enum class AlignmentAxis { None = 0, ZPos, ZNeg, YPos, YNeg, XPos, XNeg };

/**
* @brief Plot the UV points on an image
*
Expand All @@ -170,6 +173,31 @@ class UVMap
int height = -1,
const Color& color = color::LIGHT_GRAY) -> cv::Mat;

/**
* @brief Rotate a UVMap to align a specified volume axis to the -V
* direction of UV space
*
* We want to rotate the virtually unwrapped image so that the bottom of
* the content page is at the bottom of the virtually unwrapped image
* (\f( v ~= 1.\f)) and the top of the page is at the top of the image
* (\f( v ~= 0.\f)). As a consequence, this function aligns the given
* 3D axis to the -V direction of UV space rather than the +V direction.
*
* Usually, we want the alignment axis to be the volume's +Z axis. It is a
* convention in CT data for the +Z axis to travel the length of the
* sample, from the bottom to the top. Without _a priori_ knowledge, we
* assume that the bottom of the sample corresponds to the bottom of the
* content page, thus the +Z axis should align to the -V direction of UV
* space.
*
* @param uv The UVMap to be rotated.
* @param mesh Original 3D mesh for the given UVMap. Used as reference for
* the vertices' 3D positions.
* @param axis Volume axis to align to the -V axis.
*/
static void AlignToAxis(
UVMap& uv, const ITKMesh::Pointer& mesh, AlignmentAxis axis);

/** @brief Rotate a UVMap by a multiple of 90 degrees */
static void Rotate(UVMap& uv, Rotation rotation);

Expand Down
Loading