Lagrange
Loading...
Searching...
No Matches
shared_utils.h
1/*
2 * Copyright 2025 Adobe. All rights reserved.
3 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License. You may obtain a copy
5 * of the License at http://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software distributed under
8 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 * OF ANY KIND, either express or implied. See the License for the specific language
10 * governing permissions and limitations under the License.
11 */
12#pragma once
13
14#include <lagrange/AttributeValueType.h>
15#include <lagrange/Logger.h>
16#include <lagrange/image/Array3D.h>
17#include <lagrange/image/View3D.h>
18#include <lagrange/scene/Scene.h>
19#include <lagrange/scene/scene_utils.h>
20#include <lagrange/texproc/TextureRasterizer.h>
21#include <lagrange/transform_mesh.h>
22
23#include <tbb/parallel_for.h>
24
25// NOTE: These shared utils are used in our cli examples and Python bindings. They depend on the
26// lagrange::scene module. But we do not want to create a strong dependency between
27// lagrange::texproc and lagrange::scene, so this file is included directly via relative path in the
28// examples and Python bindings C++ files. To avoid confusion with internal src/ files, we place
29// this file is a separate "shared/" folder.
30
31namespace lagrange::texproc {
32
33using Array3Df = image::experimental::Array3D<float>;
34using View3Df = image::experimental::View3D<float>;
35
36Array3Df convert_from(const scene::ImageBufferExperimental& image)
37{
38 size_t nc = std::min(image.num_channels, size_t(3));
39 auto result = image::experimental::create_image<float>(image.width, image.height, nc);
40
41 auto copy_buffer = [&](auto scalar) {
42 using T = std::decay_t<decltype(scalar)>;
43 constexpr bool IsChar = std::is_integral_v<T> && sizeof(T) == 1;
44 la_runtime_assert(sizeof(T) * 8 == image.get_bits_per_element());
45 auto rawbuf = reinterpret_cast<const T*>(image.data.data());
46 for (size_t y = 0, i = 0; y < image.height; ++y) {
47 for (size_t x = 0; x < image.width; ++x) {
48 for (size_t c = 0; c < image.num_channels; ++c) {
49 if (c >= nc) {
50 ++i;
51 continue;
52 }
53 if constexpr (IsChar) {
54 result(x, y, c) = static_cast<float>(rawbuf[i++]) / 255.f;
55 } else {
56 result(x, y, c) = rawbuf[i++];
57 }
58 }
59 }
60 }
61 };
62
63 switch (image.element_type) {
64 case AttributeValueType::e_uint8_t: copy_buffer(uint8_t()); break;
65 case AttributeValueType::e_int8_t: copy_buffer(int8_t()); break;
66 case AttributeValueType::e_uint32_t: copy_buffer(uint32_t()); break;
67 case AttributeValueType::e_int32_t: copy_buffer(int32_t()); break;
68 case AttributeValueType::e_float: copy_buffer(float()); break;
69 case AttributeValueType::e_double: copy_buffer(double()); break;
70 default: throw std::runtime_error("Unsupported image scalar type");
71 }
72
73 return result;
74}
75
76// Extract a single uv unwrapped mesh and optionally its base color tensor from a scene.
77template <typename Scalar, typename Index>
78std::tuple<SurfaceMesh<Scalar, Index>, std::optional<Array3Df>> single_mesh_from_scene(
79 const scene::Scene<Scalar, Index>& scene)
80{
81 using ElementId = scene::ElementId;
82
83 // Find mesh nodes in the scene
84 std::vector<ElementId> mesh_node_ids;
85 for (ElementId node_id = 0; node_id < scene.nodes.size(); ++node_id) {
86 const auto& node = scene.nodes[node_id];
87 if (!node.meshes.empty()) {
88 mesh_node_ids.push_back(node_id);
89 }
90 }
91
92 if (mesh_node_ids.size() != 1) {
93 throw std::runtime_error(
94 fmt::format(
95 "Input scene contains {} mesh nodes. Expected exactly 1 mesh node.",
96 mesh_node_ids.size()));
97 }
98 const auto& mesh_node = scene.nodes[mesh_node_ids.front()];
99
100 if (mesh_node.meshes.size() != 1) {
101 throw std::runtime_error(
102 fmt::format(
103 "Input scene has a mesh node with {} instance per node. Expected "
104 "exactly 1 instance per node",
105 mesh_node.meshes.size()));
106 }
107 const auto& mesh_instance = mesh_node.meshes.front();
108
109 [[maybe_unused]] const auto mesh_id = mesh_instance.mesh;
110 la_debug_assert(mesh_id < scene.meshes.size());
111 SurfaceMesh<Scalar, Index> mesh = scene.meshes[mesh_instance.mesh];
112 {
113 // Apply node local->world transform
114 auto world_from_mesh =
115 scene::utils::compute_global_node_transform(scene, mesh_node_ids.front())
116 .template cast<Scalar>();
117 transform_mesh(mesh, world_from_mesh);
118 }
119
120 // Find base texture if available
121 if (auto num_mats = mesh_instance.materials.size(); num_mats != 1) {
122 logger().warn(
123 "Mesh node has {} materials. Expected exactly 1 material. Ignoring materials.",
124 num_mats);
125 return {mesh, std::nullopt};
126 }
127 const auto& material = scene.materials[mesh_instance.materials.front()];
128 if (material.base_color_texture.texcoord != 0) {
129 logger().warn(
130 "Mesh node material texcoord is {} != 0. Expected 0. Ignoring texcoord.",
131 material.base_color_texture.texcoord);
132 }
133 const auto texture_id = material.base_color_texture.index;
134 la_debug_assert(texture_id < scene.textures.size());
135 const auto& texture = scene.textures[texture_id];
136
137 const auto image_id = texture.image;
138 la_debug_assert(image_id < scene.images.size());
139 const auto& image_ = scene.images[image_id].image;
140 Array3Df image = convert_from(image_);
141
142 return {mesh, image};
143}
144
145template <typename Scalar, typename Index>
146std::vector<CameraOptions> cameras_from_scene(const scene::Scene<Scalar, Index>& scene)
147{
148 using ElementId = scene::ElementId;
149
150 // Find cameras in the scene
151 std::vector<CameraOptions> cameras;
152 for (ElementId node_id = 0; node_id < scene.nodes.size(); ++node_id) {
153 using namespace scene::utils;
154 const auto& node = scene.nodes[node_id];
155 if (!node.cameras.empty()) {
156 auto world_from_node = compute_global_node_transform(scene, node_id);
157 for (auto camera_id : node.cameras) {
158 const auto& scene_camera = scene.cameras[camera_id];
159 CameraOptions camera;
160 camera.view_transform = camera_view_transform(scene_camera, world_from_node);
161 camera.projection_transform = camera_projection_transform(scene_camera);
162 cameras.push_back(camera);
163 }
164 }
165 }
166
167 return cameras;
168}
169
170template <typename Scalar, typename Index>
171std::vector<std::pair<Array3Df, Array3Df>> rasterize_textures_from_renders(
172 const lagrange::scene::Scene<Scalar, Index>& scene,
173 std::optional<Array3Df> base_texture_in,
174 const std::vector<View3Df>& renders,
175 const std::optional<size_t> tex_width,
176 const std::optional<size_t> tex_height,
177 const float low_confidence_ratio,
178 const std::optional<float> base_confidence)
179{
180 // Load mesh, base texture and cameras from input scene
181 auto [mesh, base_texture] = single_mesh_from_scene(scene);
182 auto cameras = cameras_from_scene(scene);
183 lagrange::logger().info("Found {} cameras in the input scene", cameras.size());
184
185 if (base_texture_in.has_value()) {
186 if (base_texture.has_value()) {
187 lagrange::logger().warn(
188 "Input scene already contains a base texture. Overriding with user-provided "
189 "texture.");
190 }
191 base_texture = std::move(base_texture_in);
192 }
193
194 // Load rendered images to unproject
195 for (const auto& render : renders) {
196 size_t img_width = render.extent(0);
197 size_t img_height = render.extent(1);
198 la_runtime_assert(img_width == renders.front().extent(0), "Render width must all be equal");
200 img_height == renders.front().extent(1),
201 "Render height must all be equal");
202 }
204 renders.size() == cameras.size(),
205 "Number of renders must match number of cameras");
206
207 std::vector<std::pair<Array3Df, Array3Df>> textures_and_weights;
208
209 // Use base texture with a low confidence
210 if (base_confidence.has_value() && base_confidence.value() == 0) {
211 if (base_texture.has_value()) {
212 lagrange::logger().warn(
213 "Base confidence is 0, ignoring base texture in the input scene.");
214 }
215 } else {
216 if (base_texture.has_value()) {
217 const float default_confidence = base_confidence.value_or(0.3f);
218 lagrange::logger().info(
219 "Using base texture with uniform confidence: {}",
220 default_confidence);
221 const auto base_image = base_texture.value();
223 base_image.extent(0),
224 base_image.extent(1),
225 1);
226 for (size_t i = 0; i < base_weights.extent(0); ++i) {
227 for (size_t j = 0; j < base_weights.extent(1); ++j) {
228 base_weights(i, j, 0) = default_confidence;
229 }
230 }
231 textures_and_weights.emplace_back(base_image, std::move(base_weights));
232 } else {
233 if (base_confidence.has_value()) {
234 lagrange::logger().warn(
235 "No base texture was found in the input scene. Ignoring user-provided base "
236 "confidence: {}",
237 base_confidence.value());
238 }
239 }
240 }
241
242 // Check base texture size against expected size
243 TextureRasterizerOptions rasterizer_options;
244 if (textures_and_weights.empty()) {
245 rasterizer_options.width = tex_width.value_or(1024);
246 rasterizer_options.height = tex_height.value_or(1024);
247 lagrange::logger().info(
248 "No base texture found. Using rasterization size: {}x{}",
249 rasterizer_options.width,
250 rasterizer_options.height);
251 } else {
252 const auto& base_image = textures_and_weights.front().first;
253 la_runtime_assert(!tex_width.has_value() || base_image.extent(0) == tex_width.value());
254 la_runtime_assert(!tex_height.has_value() || base_image.extent(1) == tex_height.value());
255 rasterizer_options.width = base_image.extent(0);
256 rasterizer_options.height = base_image.extent(1);
257 lagrange::logger().info(
258 "Using base texture size for rasterization: {}x{}",
259 rasterizer_options.width,
260 rasterizer_options.height);
261 }
262
263 // Unproject render in texture space and generate confidence map for each camera
264 size_t offset = textures_and_weights.size();
265 textures_and_weights.resize(offset + cameras.size());
266 const TextureRasterizer<Scalar, Index> rasterizer(mesh, rasterizer_options);
267 lagrange::logger().info("Computing confidence maps for {} cameras", cameras.size());
268 tbb::parallel_for(size_t(0), cameras.size(), [&](size_t i) {
269 textures_and_weights[offset + i] =
270 rasterizer.weighted_texture_from_render(renders[i], cameras[i]);
271 });
272
273 // Filter confidence across all cameras at each pixel
274 lagrange::logger().info(
275 "Filtering low confidence values using ratio threshold: {}",
276 low_confidence_ratio);
277 filter_low_confidences(textures_and_weights, low_confidence_ratio);
278
279 return textures_and_weights;
280}
281
282} // namespace lagrange::texproc
Given a mesh with UVs, unproject rendered images into a UV texture and confidence map.
Definition TextureRasterizer.h:72
LA_CORE_API spdlog::logger & logger()
Retrieves the current logger.
Definition Logger.cpp:40
SurfaceMesh< ToScalar, ToIndex > cast(const SurfaceMesh< FromScalar, FromIndex > &source_mesh, const AttributeFilter &convertible_attributes={}, std::vector< std::string > *converted_attributes_names=nullptr)
Cast a mesh to a mesh of different scalar and/or index type.
void transform_mesh(SurfaceMesh< Scalar, Index > &mesh, const Eigen::Transform< Scalar, Dimension, Eigen::Affine > &transform, const TransformOptions &options={})
Apply an affine transform to a mesh in-place.
Definition transform_mesh.cpp:198
#define la_runtime_assert(...)
Runtime assertion check.
Definition assert.h:174
#define la_debug_assert(...)
Debug assertion check.
Definition assert.h:194
Array3D< T > create_image(size_t width, size_t height, size_t num_channels)
Create an image with the given dimensions and number of channels.
Definition Array3D.h:46
void filter_low_confidences(span< std::pair< image::experimental::Array3D< float >, image::experimental::Array3D< float > > > textures_and_weights, float low_ratio_threshold)
Discard low-confidence values.
Parameters for computing the rendering of a mesh.
Definition TextureRasterizer.h:29
Eigen::Affine3f view_transform
Camera view transform (world space -> view space).
Definition TextureRasterizer.h:31
Options for computing the texture map and confidence from a rendering.
Definition TextureRasterizer.h:44