ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
tensor_converter.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
9 
10 #include <Logging.h>
11 
12 #include "core/Tensor.h"
13 #ifdef _MSC_VER
14 #pragma warning(disable : 4996) // Use of [[deprecated]] feature
15 #endif
17 #include "pybind/core/core.h"
18 #include "pybind/pybind_utils.h"
19 
20 namespace cloudViewer {
21 namespace core {
25  Tensor t_cast = t;
26  if (dtype.has_value()) {
27  t_cast = t_cast.To(dtype.value());
28  }
29  if (device.has_value()) {
30  t_cast = t_cast.To(device.value());
31  }
32  return t_cast;
33 }
34 
36 py::array TensorToPyArray(const Tensor& tensor) {
37  if (!tensor.IsCPU()) {
39  "Can only convert CPU Tensor to numpy. Copy Tensor to CPU "
40  "before converting to numpy.");
41  }
42  py::dtype py_dtype =
43  py::dtype(pybind_utils::DtypeToArrayFormat(tensor.GetDtype()));
44  py::array::ShapeContainer py_shape(tensor.GetShape());
45  SizeVector strides = tensor.GetStrides();
46  int64_t element_byte_size = tensor.GetDtype().ByteSize();
47  for (auto& s : strides) {
48  s *= element_byte_size;
49  }
50  py::array::StridesContainer py_strides(strides);
51 
52  // `base_tensor` is a shallow copy of `tensor`. `base_tensor`
53  // is on the heap and is owned by py::capsule
54  // `base_tensor_capsule`. The capsule is referenced as the
55  // "base" of the numpy tensor returned by o3d.Tensor.numpy().
56  // When the "base" goes out-of-scope (e.g. when all numpy
57  // tensors referencing the base have gone out-of-scope), the
58  // deleter is called to free the `base_tensor`.
59  //
60  // This behavior is important when the original `tensor` goes
61  // out-of-scope while we still want to keep the data alive.
62  // e.g.
63  //
64  // ```python
65  // def get_np_tensor():
66  // o3d_t = o3d.Tensor(...)
67  // return o3d_t.numpy()
68  //
69  // # Now, `o3d_t` is out-of-scope, but `np_t` still
70  // # references the base tensor which references the
71  // # underlying data of `o3d_t`. Thus np_t is still valid.
72  // # When np_t goes out-of-scope, the underlying data will be
73  // # finally freed.
74  // np_t = get_np_tensor()
75  // ```
76  //
77  // See:
78  // https://stackoverflow.com/questions/44659924/returning-numpy-arrays-via-pybind11
79  Tensor* base_tensor = new Tensor(tensor);
80 
81  // See PyTorch's torch/csrc/Module.cpp
82  auto capsule_destructor = [](PyObject* data) {
83  Tensor* base_tensor = reinterpret_cast<Tensor*>(
84  PyCapsule_GetPointer(data, "open3d::Tensor"));
85  if (base_tensor) {
86  delete base_tensor;
87  } else {
88  PyErr_Clear();
89  }
90  };
91 
92  py::capsule base_tensor_capsule(base_tensor, "open3d::Tensor",
93  capsule_destructor);
94  return py::array(py_dtype, py_shape, py_strides, tensor.GetDataPtr(),
95  base_tensor_capsule);
96 }
97 
98 Tensor PyArrayToTensor(py::array array, bool inplace) {
99  py::buffer_info info = array.request();
100 
101  SizeVector shape(info.shape.begin(), info.shape.end());
102  SizeVector strides(info.strides.begin(), info.strides.end());
103  for (size_t i = 0; i < strides.size(); ++i) {
104  strides[i] /= info.itemsize;
105  }
106  Dtype dtype = pybind_utils::ArrayFormatToDtype(info.format, info.itemsize);
107  Device device("CPU:0");
108 
109  auto shared_array = std::make_shared<py::array>(array);
110  std::function<void(void*)> deleter = [shared_array](void*) mutable -> void {
111  py::gil_scoped_acquire acquire;
112  shared_array.reset();
113  };
114  auto blob = std::make_shared<Blob>(device, info.ptr, deleter);
115  Tensor t_inplace(shape, strides, info.ptr, dtype, blob);
116 
117  if (inplace) {
118  return t_inplace;
119  } else {
120  return t_inplace.Clone();
121  }
122 }
123 
124 Tensor PyListToTensor(const py::list& list,
126  utility::optional<Device> device) {
127  py::object numpy = py::module::import("numpy");
128  py::array np_array = numpy.attr("array")(list);
129  Tensor t = PyArrayToTensor(np_array, false);
130  return CastOptionalDtypeDevice(t, dtype, device);
131 }
132 
133 Tensor PyTupleToTensor(const py::tuple& tuple,
135  utility::optional<Device> device) {
136  py::object numpy = py::module::import("numpy");
137  py::array np_array = numpy.attr("array")(tuple);
138  Tensor t = PyArrayToTensor(np_array, false);
139  return CastOptionalDtypeDevice(t, dtype, device);
140 }
141 
142 Tensor DoubleToTensor(double scalar_value,
144  utility::optional<Device> device) {
145  Dtype dtype_value = core::Float64;
146  if (dtype.has_value()) {
147  dtype_value = dtype.value();
148  }
149  Device device_value("CPU:0");
150  if (device.has_value()) {
151  device_value = device.value();
152  }
153  return Tensor(std::vector<double>{scalar_value}, {}, core::Float64,
154  device_value)
155  .To(dtype_value);
156 }
157 
158 Tensor IntToTensor(int64_t scalar_value,
160  utility::optional<Device> device) {
161  Dtype dtype_value = core::Int64;
162  if (dtype.has_value()) {
163  dtype_value = dtype.value();
164  }
165  Device device_value("CPU:0");
166  if (device.has_value()) {
167  device_value = device.value();
168  }
169  return Tensor(std::vector<int64_t>{scalar_value}, {}, core::Int64,
170  device_value)
171  .To(dtype_value);
172 }
173 
174 Tensor BoolToTensor(bool scalar_value,
176  utility::optional<Device> device) {
177  Dtype dtype_value = core::Bool;
178  if (dtype.has_value()) {
179  dtype_value = dtype.value();
180  }
181  Device device_value("CPU:0");
182  if (device.has_value()) {
183  device_value = device.value();
184  }
185  return Tensor(std::vector<bool>{scalar_value}, {}, core::Bool, device_value)
186  .To(dtype_value);
187 }
188 
189 Tensor PyHandleToTensor(const py::handle& handle,
192  bool force_copy) {
193  // 1) bool
194  // 2) int
195  // 3) float (double)
196  // 4) list
197  // 5) tuple
198  // 6) numpy.ndarray (value will be copied)
199  // 7) Tensor (value will be copied)
200  if (py::isinstance<py::bool_>(handle)) {
201  return BoolToTensor(
202  static_cast<bool>(py::reinterpret_borrow<py::bool_>(handle)),
203  dtype, device);
204  } else if (py::isinstance<py::int_>(handle)) {
205  return IntToTensor(
206  static_cast<int64_t>(py::reinterpret_borrow<py::int_>(handle)),
207  dtype, device);
208  } else if (py::isinstance<py::float_>(handle)) {
209  return DoubleToTensor(
210  static_cast<double>(py::reinterpret_borrow<py::float_>(handle)),
211  dtype, device);
212  } else if (py::isinstance<py::list>(handle)) {
213  return PyListToTensor(py::reinterpret_borrow<py::list>(handle), dtype,
214  device);
215  } else if (py::isinstance<py::tuple>(handle)) {
216  return PyTupleToTensor(py::reinterpret_borrow<py::tuple>(handle), dtype,
217  device);
218  } else if (py::isinstance<py::array>(handle)) {
220  PyArrayToTensor(py::reinterpret_borrow<py::array>(handle),
221  /*inplace=*/!force_copy),
222  dtype, device);
223  } else if (py::isinstance<Tensor>(handle)) {
224  try {
225  Tensor* tensor = handle.cast<Tensor*>();
226  if (force_copy) {
227  return CastOptionalDtypeDevice(tensor->Clone(), dtype, device);
228  } else {
229  return CastOptionalDtypeDevice(*tensor, dtype, device);
230  }
231  } catch (...) {
232  utility::LogError("Cannot cast index to Tensor.");
233  }
234  } else {
236  "PyHandleToTensor has invalid input type {}.",
237  static_cast<std::string>(py::str(py::type::of(handle))));
238  }
239 }
240 
241 } // namespace core
242 } // namespace cloudViewer
int64_t ByteSize() const
Definition: Dtype.h:59
bool IsCPU() const
Definition: Device.h:95
Dtype GetDtype() const
Definition: Tensor.h:1164
SizeVector GetStrides() const
Definition: Tensor.h:1135
Tensor Clone() const
Copy Tensor to the same device.
Definition: Tensor.h:502
SizeVector GetShape() const
Definition: Tensor.h:1127
Tensor To(Dtype dtype, bool copy=false) const
Definition: Tensor.cpp:739
constexpr bool has_value() const noexcept
Definition: Optional.h:440
constexpr T const & value() const &
Definition: Optional.h:465
#define LogError(...)
Definition: Logging.h:60
Tensor PyHandleToTensor(const py::handle &handle, utility::optional< Dtype > dtype, utility::optional< Device > device, bool force_copy)
Tensor PyListToTensor(const py::list &list, utility::optional< Dtype > dtype, utility::optional< Device > device)
const Dtype Bool
Definition: Dtype.cpp:52
Tensor BoolToTensor(bool scalar_value, utility::optional< Dtype > dtype, utility::optional< Device > device)
const Dtype Int64
Definition: Dtype.cpp:47
py::array TensorToPyArray(const Tensor &tensor)
Convert Tensor class to py::array (Numpy array).
Tensor DoubleToTensor(double scalar_value, utility::optional< Dtype > dtype, utility::optional< Device > device)
Tensor IntToTensor(int64_t scalar_value, utility::optional< Dtype > dtype, utility::optional< Device > device)
const Dtype Float64
Definition: Dtype.cpp:43
static Tensor CastOptionalDtypeDevice(const Tensor &t, utility::optional< Dtype > dtype, utility::optional< Device > device)
Tensor PyArrayToTensor(py::array array, bool inplace)
Tensor PyTupleToTensor(const py::tuple &tuple, utility::optional< Dtype > dtype, utility::optional< Device > device)
core::Dtype ArrayFormatToDtype(const std::string &format, size_t byte_size)
std::string DtypeToArrayFormat(const core::Dtype &dtype)
void To(const core::Tensor &src, core::Tensor &dst, double scale, double offset)
Definition: Image.cpp:17
Generic file read and write utility for python interface.