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
36// FIXME this strips non-color channel, other variants of this function don't.
37Array3Df convert_from(const scene::ImageBufferExperimental& image)
38{
39 size_t nc = std::min(image.num_channels, size_t(3));
40 auto result = image::experimental::create_image<float>(image.width, image.height, nc);
41
42 auto copy_buffer = [&](auto scalar) {
43 using T = std::decay_t<decltype(scalar)>;
44 constexpr bool IsChar = std::is_integral_v<T> && sizeof(T) == 1;
45 la_runtime_assert(sizeof(T) * 8 == image.get_bits_per_element());
46 auto rawbuf = reinterpret_cast<const T*>(image.data.data());
47 for (size_t y = 0, i = 0; y < image.height; ++y) {
48 for (size_t x = 0; x < image.width; ++x) {
49 for (size_t c = 0; c < image.num_channels; ++c) {
50 if (c >= nc) {
51 ++i;
52 continue;
53 }
54 if constexpr (IsChar) {
55 result(x, y, c) = static_cast<float>(rawbuf[i++]) / 255.f;
56 } else {
57 result(x, y, c) = rawbuf[i++];
58 }
59 }
60 }
61 }
62 };
63
64 switch (image.element_type) {
65 case AttributeValueType::e_uint8_t: copy_buffer(uint8_t()); break;
66 case AttributeValueType::e_int8_t: copy_buffer(int8_t()); break;
67 case AttributeValueType::e_uint32_t: copy_buffer(uint32_t()); break;
68 case AttributeValueType::e_int32_t: copy_buffer(int32_t()); break;
69 case AttributeValueType::e_float: copy_buffer(float()); break;
70 case AttributeValueType::e_double: copy_buffer(double()); break;
71 default: throw std::runtime_error("Unsupported image scalar type");
72 }
73
74 return result;
75}
76
77// Extract a single uv unwrapped mesh and optionally its base color tensor from a scene.
78template <typename Scalar, typename Index>
79std::tuple<SurfaceMesh<Scalar, Index>, std::optional<Array3Df>> single_mesh_from_scene(
80 const scene::Scene<Scalar, Index>& scene)
81{
82 using ElementId = scene::ElementId;
83
84 // Find mesh nodes in the scene
85 std::vector<ElementId> mesh_node_ids;
86 for (ElementId node_id = 0; node_id < scene.nodes.size(); ++node_id) {
87 const auto& node = scene.nodes[node_id];
88 if (!node.meshes.empty()) {
89 mesh_node_ids.push_back(node_id);
90 }
91 }
92
93 if (mesh_node_ids.size() != 1) {
94 throw std::runtime_error(
95 fmt::format(
96 "Input scene contains {} mesh nodes. Expected exactly 1 mesh node.",
97 mesh_node_ids.size()));
98 }
99 const auto& mesh_node = scene.nodes[mesh_node_ids.front()];
100
101 if (mesh_node.meshes.size() != 1) {
102 throw std::runtime_error(
103 fmt::format(
104 "Input scene has a mesh node with {} instance per node. Expected "
105 "exactly 1 instance per node",
106 mesh_node.meshes.size()));
107 }
108 const auto& mesh_instance = mesh_node.meshes.front();
109
110 [[maybe_unused]] const auto mesh_id = mesh_instance.mesh;
111 la_debug_assert(mesh_id < scene.meshes.size());
112 SurfaceMesh<Scalar, Index> mesh = scene.meshes[mesh_instance.mesh];
113 {
114 // Apply node local->world transform
115 auto world_from_mesh =
116 scene::utils::compute_global_node_transform(scene, mesh_node_ids.front())
117 .template cast<Scalar>();
118 transform_mesh(mesh, world_from_mesh);
119 }
120
121 // Find base texture if available
122 if (auto num_mats = mesh_instance.materials.size(); num_mats != 1) {
123 logger().warn(
124 "Mesh node has {} materials. Expected exactly 1 material. Ignoring materials.",
125 num_mats);
126 return {mesh, std::nullopt};
127 }
128 const auto& material = scene.materials[mesh_instance.materials.front()];
129 if (material.base_color_texture.texcoord != 0) {
130 logger().warn(
131 "Mesh node material texcoord is {} != 0. Expected 0. Ignoring texcoord.",
132 material.base_color_texture.texcoord);
133 }
134 const auto texture_id = material.base_color_texture.index;
135 la_debug_assert(texture_id < scene.textures.size());
136 const auto& texture = scene.textures[texture_id];
137
138 const auto image_id = texture.image;
139 la_debug_assert(image_id < scene.images.size());
140 const auto& image_ = scene.images[image_id].image;
141 Array3Df image = convert_from(image_);
142
143 return {mesh, image};
144}
145
146template <typename Scalar, typename Index>
147std::vector<CameraOptions> cameras_from_scene(const scene::Scene<Scalar, Index>& scene)
148{
149 using ElementId = scene::ElementId;
150
151 // Find cameras in the scene
152 std::vector<CameraOptions> cameras;
153 for (ElementId node_id = 0; node_id < scene.nodes.size(); ++node_id) {
154 using namespace scene::utils;
155 const auto& node = scene.nodes[node_id];
156 if (!node.cameras.empty()) {
157 auto world_from_node = compute_global_node_transform(scene, node_id);
158 for (auto camera_id : node.cameras) {
159 const auto& scene_camera = scene.cameras[camera_id];
160 CameraOptions camera;
161 camera.view_transform = camera_view_transform(scene_camera, world_from_node);
162 camera.projection_transform = camera_projection_transform(scene_camera);
163 cameras.push_back(camera);
164 }
165 }
166 }
167
168 return cameras;
169}
170
171template <typename Scalar, typename Index>
172std::vector<std::pair<Array3Df, Array3Df>> rasterize_textures_from_renders(
173 const lagrange::scene::Scene<Scalar, Index>& scene,
174 std::optional<Array3Df> base_texture_in,
175 const std::vector<View3Df>& renders,
176 const std::optional<size_t> tex_width,
177 const std::optional<size_t> tex_height,
178 const float low_confidence_ratio,
179 const std::optional<float> base_confidence)
180{
181 // Load mesh, base texture and cameras from input scene
182 auto [mesh, base_texture] = single_mesh_from_scene(scene);
183 auto cameras = cameras_from_scene(scene);
184 lagrange::logger().info("Found {} cameras in the input scene", cameras.size());
185
186 if (base_texture_in.has_value()) {
187 if (base_texture.has_value()) {
188 lagrange::logger().warn(
189 "Input scene already contains a base texture. Overriding with user-provided "
190 "texture.");
191 }
192 base_texture = std::move(base_texture_in);
193 }
194
195 // Load rendered images to unproject
196 la_runtime_assert(!renders.empty(), "No rendered images to unproject");
197 for (const auto& render : renders) {
198 size_t img_width = render.extent(0);
199 size_t img_height = render.extent(1);
200 size_t img_channels = render.extent(2);
201 la_runtime_assert(img_width == renders.front().extent(0), "Render width must all be equal");
203 img_height == renders.front().extent(1),
204 "Render height must all be equal");
206 img_channels == renders.front().extent(2),
207 "Render num channels must all be equal");
208 }
210 renders.size() == cameras.size(),
211 "Number of renders must match number of cameras");
212
213 std::vector<std::pair<Array3Df, Array3Df>> textures_and_weights;
214
215 // Use base texture with a low confidence
216 if (base_confidence.has_value() && base_confidence.value() == 0) {
217 if (base_texture.has_value()) {
218 lagrange::logger().warn(
219 "Base confidence is 0, ignoring base texture in the input scene.");
220 }
221 } else {
222 if (base_texture.has_value()) {
223 const float default_confidence = base_confidence.value_or(0.3f);
224 lagrange::logger().info(
225 "Using base texture with uniform confidence: {}",
226 default_confidence);
227 const auto base_image = base_texture.value();
229 base_image.extent(0),
230 base_image.extent(1),
231 1);
232 for (size_t i = 0; i < base_weights.extent(0); ++i) {
233 for (size_t j = 0; j < base_weights.extent(1); ++j) {
234 base_weights(i, j, 0) = default_confidence;
235 }
236 }
237 textures_and_weights.emplace_back(base_image, std::move(base_weights));
238 } else {
239 if (base_confidence.has_value()) {
240 lagrange::logger().warn(
241 "No base texture was found in the input scene. Ignoring user-provided base "
242 "confidence: {}",
243 base_confidence.value());
244 }
245 }
246 }
247
248 // Check base texture size against expected size
249 TextureRasterizerOptions rasterizer_options;
250 if (textures_and_weights.empty()) {
251 rasterizer_options.width = tex_width.value_or(1024);
252 rasterizer_options.height = tex_height.value_or(1024);
253 lagrange::logger().info(
254 "No base texture found. Using rasterization size: {}x{}",
255 rasterizer_options.width,
256 rasterizer_options.height);
257 } else {
258 const auto& base_image = textures_and_weights.front().first;
259 la_runtime_assert(!tex_width.has_value() || base_image.extent(0) == tex_width.value());
260 la_runtime_assert(!tex_height.has_value() || base_image.extent(1) == tex_height.value());
261 rasterizer_options.width = base_image.extent(0);
262 rasterizer_options.height = base_image.extent(1);
264 renders.front().extent(2) == base_image.extent(2),
265 fmt::format(
266 "Input render image num channels (={}) must match base texture num channels (={})",
267 renders.front().extent(2),
268 base_image.extent(2)));
269 lagrange::logger().info(
270 "Using base texture size for rasterization: {}x{}",
271 rasterizer_options.width,
272 rasterizer_options.height);
273 }
274
275 // Unproject render in texture space and generate confidence map for each camera
276 size_t offset = textures_and_weights.size();
277 textures_and_weights.resize(offset + cameras.size());
278 const TextureRasterizer<Scalar, Index> rasterizer(mesh, rasterizer_options);
279 lagrange::logger().info("Computing confidence maps for {} cameras", cameras.size());
280 tbb::parallel_for(size_t(0), cameras.size(), [&](size_t i) {
281 textures_and_weights[offset + i] =
282 rasterizer.weighted_texture_from_render(renders[i], cameras[i]);
283 });
284
285 // Filter confidence across all cameras at each pixel
286 lagrange::logger().info(
287 "Filtering low confidence values using ratio threshold: {}",
288 low_confidence_ratio);
289 filter_low_confidences(textures_and_weights, low_confidence_ratio);
290
291 return textures_and_weights;
292}
293
294} // 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:199
#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