Lagrange
Loading...
Searching...
No Matches
image_utils.h
1/*
2 * Copyright 2026 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/image/Array3D.h>
15#include <lagrange/image/View3D.h>
16#include <lagrange/python/binding.h>
17#include <lagrange/python/tensor_utils.h>
18#include <lagrange/utils/assert.h>
19
20#include <utility>
21
22namespace lagrange::python {
23
24namespace nb = nanobind;
25
26using ImageShape = nb::shape<-1, -1, -1>;
27
28template <typename Scalar>
29using ImageTensor = nb::ndarray<Scalar, ImageShape, nb::numpy, nb::c_contig, nb::device::cpu>;
30
31// Numpy indexes tensors as (row, col, channel), but our mdspan uses (x, y, channel)
32// coordinates, so we need to transpose the first two dimensions.
33
34template <typename Scalar>
35auto tensor_to_image_view(const ImageTensor<Scalar>& tensor) -> image::experimental::View3D<Scalar>
36{
37 const image::experimental::dextents<size_t, 3> shape{
38 tensor.shape(1),
39 tensor.shape(0),
40 tensor.shape(2),
41 };
42 const std::array<size_t, 3> strides{
43 static_cast<size_t>(tensor.stride(1)),
44 static_cast<size_t>(tensor.stride(0)),
45 static_cast<size_t>(tensor.stride(2)),
46 };
47 const image::experimental::layout_stride::mapping mapping{shape, strides};
48 image::experimental::View3D<Scalar> view{
49 static_cast<float*>(tensor.data()),
50 mapping,
51 };
52 return view;
53}
54
55template <typename Scalar>
56void copy_tensor_to_image_view(
57 const ImageTensor<Scalar>& tensor,
58 image::experimental::View3D<Scalar> image)
59{
60 const auto width = static_cast<unsigned int>(tensor.shape(1));
61 const auto height = static_cast<unsigned int>(tensor.shape(0));
62 const auto num_channels = static_cast<unsigned int>(tensor.shape(2));
64 image.extent(0) == width && image.extent(1) == height && image.extent(2) == num_channels,
65 "Tensor and mdspan dimensions do not match");
66 for (unsigned int j = 0; j < height; j++) {
67 for (unsigned int i = 0; i < width; i++) {
68 for (unsigned int c = 0; c < num_channels; c++) {
69 image(i, j, c) = tensor(j, i, c);
70 }
71 }
72 }
73}
74
75template <typename Scalar>
76Tensor<Scalar> image_array_to_tensor(image::experimental::Array3D<Scalar>&& image_)
77{
78 // Move the array onto the heap and hand ownership to a capsule, so the tensor
79 // keeps its backing storage alive even after the source `Array3D` is gone.
80 using Array = image::experimental::Array3D<Scalar>;
81 auto* image = new Array(std::move(image_));
82 nb::capsule owner(image, [](void* p) noexcept { delete static_cast<Array*>(p); });
83 return Tensor<Scalar>(
84 static_cast<Scalar*>(image->data()),
85 {
86 image->extent(1),
87 image->extent(0),
88 image->extent(2),
89 },
90 owner,
91 {
92 static_cast<int64_t>(image->stride(1)),
93 static_cast<int64_t>(image->stride(0)),
94 static_cast<int64_t>(image->stride(2)),
95 });
96}
97
98} // namespace lagrange::python
@ Scalar
Mesh attribute must have exactly 1 channel.
Definition AttributeFwd.h:56
#define la_runtime_assert(...)
Runtime assertion check.
Definition assert.h:177