Skip to content

Tensor(张量)

Tensor(张量)是对一个数据 Blob(数据块)的”视图”,带有形状(shape)、步长(stride)和一个数据指针。它是一个多维、同构的矩阵,其中的元素具有相同的数据类型。Open3D 使用它来执行数值运算,同时也支持 GPU 运算。

import open3d.core as o3c
import numpy as np

Tensor 可以从列表(list)、NumPy 数组(numpy array)或另一个 tensor 创建。通过向构造函数传入 o3c.Dtype 和/或 o3c.Device,可以构造具有特定数据类型和设备的 tensor。如果不传入,默认数据类型会从数据中推断,默认设备为 CPU。需要注意的是,当从列表或 NumPy 数组创建 tensor 时,底层内存不会共享,而是会创建一份拷贝。

# Tensor from list.
a = o3c.Tensor([0, 1, 2])
print("Created from list:\n{}".format(a))
# Tensor from Numpy.
a = o3c.Tensor(np.array([0, 1, 2]))
print("\nCreated from numpy array:\n{}".format(a))
# Dtype and inferred from list.
a_float = o3c.Tensor([0.0, 1.0, 2.0])
print("\nDefault dtype and device:\n{}".format(a_float))
# Specify dtype.
a = o3c.Tensor(np.array([0, 1, 2]), dtype=o3c.Dtype.Float64)
print("\nSpecified data type:\n{}".format(a))
# Specify device.
a = o3c.Tensor(np.array([0, 1, 2]), device=o3c.Device("CUDA:0"))
print("\nSpecified device:\n{}".format(a))
Created from list:
[0 1 2]
Tensor[shape={3}, Int64, CPU:0, 0x55d0575a48f0]
Created from numpy array:
[0 1 2]
Tensor[shape={3}, Int64, CPU:0, 0x55d056caa8d0]
Default dtype and device:
[0.0 1.0 2.0]
Tensor[shape={3}, Float64, CPU:0, 0x55d05759c330]
Specified data type:
[0.0 1.0 2.0]
Tensor[shape={3}, Float64, CPU:0, 0x55d057568ca0]
Specified device:
[0 1 2]
Tensor[shape={3}, Int64, CUDA:0, 0x7f40ff000000]

也可以通过调用拷贝构造函数,从另一个 tensor 创建新的 tensor。这是一种浅拷贝(shallow copy),data_ptr 会被拷贝,但它所指向的内存不会被拷贝。

# Shallow copy constructor.
vals = np.array([1, 2, 3])
src = o3c.Tensor(vals)
dst = src
src[0] += 10
# Changes in one will get reflected in other.
print("Source tensor:\n{}".format(src))
print("\nTarget tensor:\n{}".format(dst))
Source tensor:
[11 2 3]
Tensor[shape={3}, Int64, CPU:0, 0x55d057b157a0]
Target tensor:
[11 2 3]
Tensor[shape={3}, Int64, CPU:0, 0x55d057b157a0]
vals = np.array((range(24))).reshape(2, 3, 4)
a = o3c.Tensor(vals, dtype=o3c.Dtype.Float64, device=o3c.Device("CUDA:0"))
print(f"a.shape: {a.shape}")
print(f"a.strides: {a.strides}")
print(f"a.dtype: {a.dtype}")
print(f"a.device: {a.device}")
print(f"a.ndim: {a.ndim}")
a.shape: SizeVector[2, 3, 4]
a.strides: SizeVector[12, 4, 1]
a.dtype: Float64
a.device: CUDA:0
a.ndim: 3

我们可以在主机(host)和多个设备(device)之间传输 tensor。

# Host -> Device.
a_cpu = o3c.Tensor([0, 1, 2])
a_gpu = a_cpu.cuda(0)
print(a_gpu)
# Device -> Host.
a_gpu = o3c.Tensor([0, 1, 2], device=o3c.Device("CUDA:0"))
a_cpu = a_gpu.cpu()
print(a_cpu)
# Device -> another Device.
a_gpu_0 = o3c.Tensor([0, 1, 2], device=o3c.Device("CUDA:0"))
a_gpu_1 = a_gpu_0.cuda(0)
print(a_gpu_1)
[0 1 2]
Tensor[shape={3}, Int64, CUDA:0, 0x7f40ff000000]
[0 1 2]
Tensor[shape={3}, Int64, CPU:0, 0x55d05c254780]
[0 1 2]
Tensor[shape={3}, Int64, CUDA:0, 0x7f40ff000000]

Open3D 定义了若干种标量 tensor 数据类型。

数据类型dtype字节数(byte_size)
未初始化 Tensoro3c.Dtype.Undefined—
32 位浮点数o3c.Dtype.Float324
64 位浮点数o3c.Dtype.Float648
8 位有符号整数o3c.Dtype.Int81
16 位有符号整数o3c.Dtype.Int162
32 位有符号整数o3c.Dtype.Int324
64 位有符号整数o3c.Dtype.Int648
8 位无符号整数o3c.Dtype.UInt81
16 位无符号整数o3c.Dtype.UInt162
32 位无符号整数o3c.Dtype.UInt324
64 位无符号整数o3c.Dtype.UInt648
布尔值o3c.Dtype.Bool1

我们可以对 tensor 的数据类型进行转换。强制转换可能会导致数据丢失。

# E.g. float -> int
a = o3c.Tensor([0.1, 1.5, 2.7])
b = a.to(o3c.Dtype.Int32)
print(a)
print(b)
[0.1 1.5 2.7]
Tensor[shape={3}, Float64, CPU:0, 0x55d056510450]
[0 1 2]
Tensor[shape={3}, Int32, CPU:0, 0x55d0565104d0]
# E.g. int -> float
a = o3c.Tensor([1, 2, 3])
b = a.to(o3c.Dtype.Float32)
print(a)
print(b)
[1 2 3]
Tensor[shape={3}, Int64, CPU:0, 0x55d0565103c0]
[1.0 2.0 3.0]
Tensor[shape={3}, Float32, CPU:0, 0x55d05c9cddd0]

通过向构造函数传入 NumPy 数组(o3c.Tensor(np.array(...)))创建的 tensor,不会与该 NumPy 数组共享内存。若要实现内存共享,可以使用 o3c.Tensor.from_numpy(...) 和 o3c.Tensor.numpy(...)。二者中任意一方的改动都会反映到另一方。

# Using constructor.
np_a = np.ones((5,), dtype=np.int32)
o3_a = o3c.Tensor(np_a)
print(f"np_a: {np_a}")
print(f"o3_a: {o3_a}")
print("")
# Changes to numpy array will not reflect as memory is not shared.
np_a[0] += 100
o3_a[1] += 200
print(f"np_a: {np_a}")
print(f"o3_a: {o3_a}")
np_a: [1 1 1 1 1]
o3_a: [1 1 1 1 1]
Tensor[shape={5}, Int32, CPU:0, 0x55d057b15480]
np_a: [101 1 1 1 1]
o3_a: [1 201 1 1 1]
Tensor[shape={5}, Int32, CPU:0, 0x55d057b15480]
# From numpy.
np_a = np.ones((5,), dtype=np.int32)
o3_a = o3c.Tensor.from_numpy(np_a)
# Changes to numpy array reflects on open3d Tensor and vice versa.
np_a[0] += 100
o3_a[1] += 200
print(f"np_a: {np_a}")
print(f"o3_a: {o3_a}")
np_a: [101 201 1 1 1]
o3_a: [101 201 1 1 1]
Tensor[shape={5}, Int32, CPU:0, 0x55d057b15a60]
# To numpy.
o3_a = o3c.Tensor([1, 1, 1, 1, 1], dtype=o3c.Dtype.Int32)
np_a = o3_a.numpy()
# Changes to numpy array reflects on open3d Tensor and vice versa.
np_a[0] += 100
o3_a[1] += 200
print(f"np_a: {np_a}")
print(f"o3_a: {o3_a}")
# For CUDA Tensor, call cpu() before calling numpy().
o3_a = o3c.Tensor([1, 1, 1, 1, 1], device=o3c.Device("CUDA:0"))
print(f"\no3_a.cpu().numpy(): {o3_a.cpu().numpy()}")
np_a: [101 201 1 1 1]
o3_a: [101 201 1 1 1]
Tensor[shape={5}, Int32, CPU:0, 0x55d056cc3d30]
o3_a.cpu().numpy(): [1 1 1 1 1]

通过 DLPack 内存映射与 PyTorch 交互

Section titled “通过 DLPack 内存映射与 PyTorch 交互”

我们可以在 tensor 与 DLManagedTensor 之间相互转换。

import torch
import torch.utils.dlpack
# From PyTorch
th_a = torch.ones((5,)).cuda(0)
o3_a = o3c.Tensor.from_dlpack(torch.utils.dlpack.to_dlpack(th_a))
print(f"th_a: {th_a}")
print(f"o3_a: {o3_a}")
print("")
# Changes to PyTorch array reflects on open3d Tensor and vice versa
th_a[0] = 100
o3_a[1] = 200
print(f"th_a: {th_a}")
print(f"o3_a: {o3_a}")
th_a: tensor([1., 1., 1., 1., 1.],)
o3_a: [1.0 1.0 1.0 1.0 1.0]
Tensor[shape={5}, Float32, CUDA:0, 0x7f409be00000]
th_a: tensor([100., 200., 1., 1., 1.],)
o3_a: [100.0 200.0 1.0 1.0 1.0]
Tensor[shape={5}, Float32, CUDA:0, 0x7f409be00000]
# To PyTorch
o3_a = o3c.Tensor([1, 1, 1, 1, 1], device=o3c.Device("CUDA:0"))
th_a = torch.utils.dlpack.from_dlpack(o3_a.to_dlpack())
o3_a = o3c.Tensor.from_dlpack(torch.utils.dlpack.to_dlpack(th_a))
print(f"th_a: {th_a}")
print(f"o3_a: {o3_a}")
print("")
# Changes to PyTorch array reflects on open3d Tensor and vice versa
th_a[0] = 100
o3_a[1] = 200
print(f"th_a: {th_a}")
print(f"o3_a: {o3_a}")
th_a: tensor([1, 1, 1, 1, 1],)
o3_a: [1 1 1 1 1]
Tensor[shape={5}, Int64, CUDA:0, 0x7f40ff000200]
th_a: tensor([100, 200, 1, 1, 1],)
o3_a: [100 200 1 1 1]
Tensor[shape={5}, Int64, CUDA:0, 0x7f40ff000200]

支持的逐元素二元运算包括:

  1. Add(+)
  2. Sub(-)
  3. Mul(*)
  4. Div(/)
  5. Add_(+=)
  6. Sub_(-=)
  7. Mul_(*=)
  8. Div_(/=)

需要注意的是,操作数必须位于相同的设备(Device)上、具有相同的数据类型(dtype),并且满足广播(broadcast)兼容性。

a = o3c.Tensor([1, 1, 1], dtype=o3c.Dtype.Float32)
b = o3c.Tensor([2, 2, 2], dtype=o3c.Dtype.Float32)
print("a +>{}".format(a + b))
print("a ->{}".format(a - b))
print("a *>{}".format(a * b))
print("a />{}".format(a / b))
a + 3.0 3.0]
Tensor[shape={3}, Float32, CPU:0, 0x55d0573a0ed0]
a - -1.0 -1.0]
Tensor[shape={3}, Float32, CPU:0, 0x55d05ed01410]
a * 2.0 2.0]
Tensor[shape={3}, Float32, CPU:0, 0x55d05ed0a180]
a / 0.5 0.5]
Tensor[shape={3}, Float32, CPU:0, 0x55d05ed013f0]

广播规则与 NumPy 的广播规则相同(参见 NumPy 广播规则)。类型转换会以避免数据丢失的方式自动进行。

# Automatic broadcasting.
a = o3c.Tensor.ones((2, 3), dtype=o3c.Dtype.Float32)
b = o3c.Tensor.ones((3,), dtype=o3c.Dtype.Float32)
print("a +>\n{}\n".format(a + b))
# Automatic type casting.
a = a[0]
print("a + 1 = {}".format(a + 1)) # Float + Int -> Float.
print("a +>{}".format(a + True)) # Float + Bool -> Float.
# Inplace.
a -= True
print("a = {}".format(a))
a + 2.0 2.0],
[2.0 2.0 2.0]]
Tensor[shape={2, 3}, 1}, Float32, CPU:0, 0x55d05ed0a440]
a + 1 = [2.0 2.0 2.0]
Tensor[shape={3}, Float32, CPU:0, 0x55d05ed0baa0]
a + 2.0 2.0]
Tensor[shape={3}, Float32, CPU:0, 0x55d0565103e0] 0.0 0.0]
Tensor[shape={3}, Float32, CPU:0, 0x55d05c2547a0]

支持的一元逐元素运算包括:

  1. sqrt、sqrt_(原地,inplace)
  2. sin、sin_
  3. cos、cos_
  4. neg、neg_
  5. exp、exp_
  6. abs、abs_
a = o3c.Tensor([4, 9, 16], dtype=o3c.Dtype.Float32)
print("a = {}\n".format(a))
print("a.sqrt = {}\n".format(a.sqrt()))
print("a.sin = {}\n".format(a.sin()))
print("a.cos = {}\n".format(a.cos()))
# Inplace operation
a.sqrt_()
print(a)
9.0 16.0]
Tensor[shape={3}, Float32, CPU:0, 0x55d05ed01410]
a.sqrt = [2.0 3.0 4.0]
Tensor[shape={3}, Float32, CPU:0, 0x55d0beec40a0]
a.sin = [-0.756802 0.412118 -0.287903]
Tensor[shape={3}, Float32, CPU:0, 0x55d056510330]
a.cos = [-0.653644 -0.91113 -0.957659]
Tensor[shape={3}, Float32, CPU:0, 0x55d05ed013d0]
[2.0 3.0 4.0]
Tensor[shape={3}, Float32, CPU:0, 0x55d05ed01410]

Open3D 支持以下归约(reduction)运算:

  1. sum —— 返回沿给定轴求和后的 tensor。
  2. mean —— 返回沿给定轴求均值后的 tensor。
  3. prod —— 返回沿给定轴求积后的 tensor。
  4. min —— 返回沿给定轴取最小值后的 tensor。
  5. max —— 返回沿给定轴取最大值后的 tensor。
  6. argmin —— 返回沿给定轴最小值索引的 tensor。
  7. argmax —— 返回沿给定轴最大值索引的 tensor。
vals = np.array(range(24)).reshape((2, 3, 4))
a = o3c.Tensor(vals)
print("a.sum = {}\n".format(a.sum()))
print("a.min = {}\n".format(a.min()))
print("a.ArgMax = {}\n".format(a.argmax()))
a.sum = 276
Tensor[shape={}, Int64, CPU:0, 0x55d056cc3d50]
a.min = 0
Tensor[shape={}, Int64, CPU:0, 0x55d0beec4080]
a.ArgMax = 23
Tensor[shape={}, Int64, CPU:0, 0x55d05ed0a440]
# With specified dimension.
vals = np.array(range(24)).reshape((2, 3, 4))
a = o3c.Tensor(vals)
print("Along>\n{}".format(a.sum(dim=(0))))
print("Along 2)\n{}\n".format(a.sum(dim=(0, 2))))
# Retention of reduced dimension.
print("Shape without retention : {}".format(a.sum(dim=(0, 2)).shape))
print("Shape with retention : {}".format(a.sum(dim=(0, 2), keepdim=True).shape))
Along
[[12 14 16 18],
[20 22 24 26],
[28 30 32 34]]
Tensor[shape={3, 4}, 1}, Int64, CPU:0, 0x55d08bffade0]
Along 2)
[60 92 124]
Tensor[shape={3}, Int64, CPU:0, 0x55d0583dd120]
Shape without retention : SizeVector[3]
Shape with retention : SizeVector[1, 3, 1]

基本切片(basic slicing)通过传入整数、切片对象(start:stop:step)、索引数组或布尔数组来完成。切片和索引会产生 tensor 的视图(view),因此对其的任何改动也会反映到原始 tensor 上。

vals = np.array(range(24)).reshape((2, 3, 4))
a = o3c.Tensor(vals)
print("a = \n{}\n".format(a))
# Indexing __getitem__.
print("a[1, 2] = {}\n".format(a[1, 2]))
# Slicing __getitem__.
print("a[1:] = \n{}\n".format(a[1:]))
# slice object.
print("a[:, 0:3:2, :] = \n{}\n".format(a[:, 0:3:2, :]))
# Combined __getitem__
print("a[:-1, 0:3:2, 2] = \n{}\n".format(a[:-1, 0:3:2, 2]))
1 2 3],
[4 5 6 7],
[8 9 10 11]],
[[12 13 14 15],
[16 17 18 19],
[20 21 22 23]]]
Tensor[shape={2, 3, 4}, 4, 1}, Int64, CPU:0, 0x55d05ed03150]
a[1, 2] = [20 21 22 23]
Tensor[shape={4}, Int64, CPU:0, 0x55d05ed031f0]
a[1:] =
[[[12 13 14 15],
[16 17 18 19],
[20 21 22 23]]]
Tensor[shape={1, 3, 4}, 4, 1}, Int64, CPU:0, 0x55d05ed031b0]
a[:, 0:3:2, :] =
[[[0 1 2 3],
[8 9 10 11]],
[[12 13 14 15],
[20 21 22 23]]]
Tensor[shape={2, 2, 4}, 8, 1}, Int64, CPU:0, 0x55d05ed03150]
a[:-1, 0:3:2, 2] =
[[2 10]]
Tensor[shape={1, 2}, 8}, Int64, CPU:0, 0x55d05ed03160]
vals = np.array(range(24)).reshape((2, 3, 4))
a = o3c.Tensor(vals)
# Changes get reflected.
b = a[:-1, 0:3:2, 2]
b[0] += 100
print("b = {}\n".format(b))
print("a = \n{}".format(a))
110]]
Tensor[shape={1, 2}, 8}, Int64, CPU:0, 0x55d05ed01160] 1 102 3],
[4 5 6 7],
[8 9 110 11]],
[[12 13 14 15],
[16 17 18 19],
[20 21 22 23]]]
Tensor[shape={2, 3, 4}, 4, 1}, Int64, CPU:0, 0x55d05ed01150]
vals = np.array(range(24)).reshape((2, 3, 4))
a = o3c.Tensor(vals)
# Example __setitem__
a[:, :, 2] += 100
print(a)
[[[0 1 102 3],
[4 5 106 7],
[8 9 110 11]],
[[12 13 114 15],
[16 17 118 19],
[20 21 122 23]]]
Tensor[shape={2, 3, 4}, 4, 1}, Int64, CPU:0, 0x55d0573a0ed0]

当传入索引数组、布尔数组,或它们与整数/切片对象的组合时,会触发高级索引(advanced indexing)。需要注意的是,高级索引始终返回数据的拷贝(copy)(这与返回视图的基本切片相反)。

整数数组索引允许根据多维索引从 tensor 中选取任意元素。传入的索引应当满足广播兼容性。

vals = np.array(range(24)).reshape((2, 3, 4))
a = o3c.Tensor(vals)
# Along each dimension, a specific element is selected.
print("a[[0, 1], [1, 2], [1, 0]] = {}\n".format(a[[0, 1], [1, 2], [1, 0]]))
# Changes not reflected as it is a copy.
b = a[[0, 0], [0, 1], [1, 1]]
b[0] += 100
print("b = {}\n".format(b))
print("a[[0, 0], [0, 1], [1, 1]] = {}".format(a[[0, 0], [0, 1], [1, 1]]))
a[[0, 1], [1, 2], [1, 0]] = [5 20]
Tensor[shape={2}, Int64, CPU:0, 0x55d05ed06570] 5]
Tensor[shape={2}, Int64, CPU:0, 0x55d05ed093e0]
a[[0, 0], [0, 1], [1, 1]] = [1 5]
Tensor[shape={2}, Int64, CPU:0, 0x55d05758e690]

当索引中至少有一个切片(:)、省略号(...)或 newaxis 时,行为可能会更复杂。这类似于对每个高级索引元素的索引结果进行拼接。在高级索引模式下,发送给高级索引引擎之前会进行一些预处理:

  1. 特定的索引位置会被转换为包含指定索引的 IndexTensor。
  2. 如果切片不是完整切片(full slice),则先对 tensor 进行切片,再对高级索引引擎使用完整切片。

dst = src[1, 0:2, [1, 2]] 分两步完成:temp = src[:, 0:2, :],然后 dst = temp[[1], :, [1, 2]]。

索引操作包含两部分:由基本索引定义的子空间,以及来自高级索引部分的子空间。

  • 高级索引被切片、省略号或 newaxis 分隔。例如 x[arr1, :, arr2]。
  • 高级索引彼此相邻。例如 x[..., arr1, arr2, :],而 x[arr1, :, 1] 不属于此类,因为这里的 1 是一个高级索引。

在第一种情况下,高级索引操作产生的维度会出现在结果数组的最前面,其后是子空间维度。在第二种情况下,高级索引操作产生的维度会插入到结果数组中与原数组相同的位置。

vals = np.array(range(24)).reshape((2, 3, 4))
a = o3c.Tensor(vals)
print("a[1, 0:2, [1, 2]] = \n{}\n".format(a[1, 0:2, [1, 2]]))
# Subtle difference in selection and advanced indexing.
print("a[(0, 1)] = {}\n".format(a[(0, 1)]))
print("a[[0, 1] = \n{}\n".format(a[[0, 1]]))
a = o3c.Tensor(np.array(range(120)).reshape((2, 3, 4, 5)))
# Interleaving slice and advanced indexing.
print("a[1, [[1, 2], [2, 1]], 0:4:2, [3, 4]] = \n{}\n".format(
a[1, [[1, 2], [2, 1]], 0:4:2, [3, 4]]))
a[1, 0:2, [1, 2]] =
[[13 17],
[14 18]]
Tensor[shape={2, 2}, 1}, Int64, CPU:0, 0x55d05eceb810]
a[(0, 1)] = [4 5 6 7]
Tensor[shape={4}, Int64, CPU:0, 0x55d05ed01170]
a[[0, 1] =
[[[0 1 2 3],
[4 5 6 7],
[8 9 10 11]],
[[12 13 14 15],
[16 17 18 19],
[20 21 22 23]]]
Tensor[shape={2, 3, 4}, 4, 1}, Int64, CPU:0, 0x55d05ed03150]
a[1, [[1, 2], [2, 1]], 0:4:2, [3, 4]] =
[[[83 93],
[104 114]],
[[103 113],
[84 94]]]
Tensor[shape={2, 2, 2}, 2, 1}, Int64, CPU:0, 0x55d056caa290]

当我们传入布尔数组作为索引,或索引由比较运算符返回时,会触发高级索引。布尔数组的维数应当与其所作用对象的维数完全一致。

a = o3c.Tensor(np.array([1, -1, -2, 3]))
print("a = {}\n".format(a))
# Add constant to all negative numbers.
a[a < 0] += 20
print("a = {}\n".format(a))
-1 -2 3]
Tensor[shape={4}, Int64, CPU:0, 0x55d05eceb810] 19 18 3]
Tensor[shape={4}, Int64, CPU:0, 0x55d05eceb810]

Open3D 支持以下逻辑运算符:

  1. logical_and —— 返回逐元素逻辑与(AND)的 tensor。
  2. logical_or —— 返回逐元素逻辑或(OR)的 tensor。
  3. logical_xor —— 返回逐元素逻辑异或(XOR)的 tensor。
  4. logical_not —— 返回逐元素逻辑非(NOT)的 tensor。
  5. all —— 当 tensor 中所有元素均为真时返回 true。
  6. any —— 当 tensor 中存在任意元素为真时返回 true。
  7. allclose —— 当两个 tensor 在容差范围内逐元素相等时返回 true。
  8. isclose —— 返回逐元素执行 allclose 操作后的 tensor。
  9. issame —— 当且仅当两个 tensor 完全相同(甚至底层内存也相同)时返回 true。
a = o3c.Tensor(np.array([True, False, True, False]))
b = o3c.Tensor(np.array([True, True, False, False]))
print("a AND>{}".format(a.logical_and(b)))
print("a OR>{}".format(a.logical_or(b)))
print("a XOR>{}".format(a.logical_xor(b)))
print("NOT>{}\n".format(a.logical_not()))
# Only works for boolean tensors.
print("a.any = {}".format(a.any()))
print("a.all = {}\n".format(a.all()))
# If tensor is not boolean, 0 will be treated as False, while non-zero as true.
# The tensor will be filled with 0 or 1 casted to tensor's dtype.
c = o3c.Tensor(np.array([2.0, 0.0, 3.5, 0.0]))
d = o3c.Tensor(np.array([0.0, 3.0, 1.5, 0.0]))
print("c AND>{}".format(c.logical_and(d)))
a AND False False False]
Tensor[shape={4}, Bool, CPU:0, 0x55d05757ad90]
a OR True True False]
Tensor[shape={4}, Bool, CPU:0, 0x55d05ed0a1a0]
a XOR True True False]
Tensor[shape={4}, Bool, CPU:0, 0x55d0bedd9040]
NOT True False True]
Tensor[shape={4}, Bool, CPU:0, 0x55d0bf0c0c50]
a.any = True
a.all = False
c AND False True False]
Tensor[shape={4}, Bool, CPU:0, 0x55d0bf0c0c50]
a = o3c.Tensor(np.array([1, 2, 3, 4]), dtype=o3c.Dtype.Float64)
b = o3c.Tensor(np.array([1, 1.99999, 3, 4]))
# Throws exception if the device/dtype is not same.
# Returns false if the shape is not same.
print("allclose : {}".format(a.allclose(b)))
# Throws exception if the device/dtype/shape is not same.
print("isclose : {}".format(a.isclose(b)))
# Returns false if the device/dtype/shape/ is not same.
print("issame : {}".format(a.issame(b)))
allclose : True
isclose : [True True True True]
Tensor[shape={4}, Bool, CPU:0, 0x55d0bedd9040]
issame : False
a = o3c.Tensor([0, 1, -1])
b = o3c.Tensor([0, 0, 0])
print("a >>{}".format(a > b))
print("a >=>{}".format(a >= b))
print("a <>{}".format(a < b))
print("a <=>{}".format(a <= b))
print("a ==>{}".format(a == b))
print("a !=>{}".format(a != b))
# Throws exception if device/dtype is not shape.
# If shape is not same, then tensors should be broadcast compatible.
print("a >>{}".format(a > b[0]))
a > True False]
Tensor[shape={3}, Bool, CPU:0, 0x55d05ed04b10]
a >= True False]
Tensor[shape={3}, Bool, CPU:0, 0x55d0a7cdbf60]
a < False True]
Tensor[shape={3}, Bool, CPU:0, 0x55d056caa2e0]
a <= False True]
Tensor[shape={3}, Bool, CPU:0, 0x55d0565103e0] False False]
Tensor[shape={3}, Bool, CPU:0, 0x55d05ed0a1a0]
a != True True]
Tensor[shape={3}, Bool, CPU:0, 0x55d0bf3f40e0]
a > True False]
Tensor[shape={3}, Bool, CPU:0, 0x55d05ed01130]

当 as_tuple 为 False(默认值)时,返回非零元素索引的 tensor。结果中的每一行包含输入中一个非零元素的索引。如果输入有 (n) 个维度,则结果 tensor 的大小为 ((z x n)),其中 (z) 是输入 tensor 中非零元素的总数。

当 as_tuple 为 True 时,返回一个由若干一维 tensor 组成的元组,每个维度对应一个,各 tensor 包含输入中所有非零元素的索引。如果输入有 (n) 个维度,则结果元组包含 (n) 个大小为 (z) 的 tensor,其中 (z) 是输入 tensor 中非零元素的总数。

a = o3c.Tensor([[3, 0, 0], [0, 4, 0], [5, 6, 0]])
print("a = \n{}\n".format(a))
print("a.nonzero() = \n{}\n".format(a.nonzero()))
print("a.nonzero(as_tuple = 1) = \n{}".format(a.nonzero(as_tuple=1)))
0 0],
[0 4 0],
[5 6 0]]
Tensor[shape={3, 3}, 1}, Int64, CPU:0, 0x55d056510470]
a.nonzero() =
[[0 1 2 2]
Tensor[shape={4}, Int64, CPU:0, 0x55d05ed0a290], [0 1 0 1]
Tensor[shape={4}, Int64, CPU:0, 0x55d0bf3f4090]]
a.nonzero(as_tuple = 1) =
[[0 1 2 2],
[0 1 0 1]]
Tensor[shape={2, 4}, 1}, Int64, CPU:0, 0x55d05758e690]

自 Open3D v0.16.0 起,tensor 可以使用 pickle 进行序列化和反序列化。这对于将 tensor 保存到磁盘或从磁盘加载非常有用。

import os
import pickle
import tempfile
a = o3c.Tensor([1, 2, 3, 4])
print(f'After serialization: {a}\n')
with tempfile.TemporaryDirectory() as path:
file_name = os.path.join(path, 'tensor')
pickle.dump(a, open(file_name, 'wb'))
b = pickle.load(open(file_name, 'rb'))
print(f'After deserialization: {b}\n')
# Pickle tensor on GPU.
a = o3c.Tensor([1, 2, 3, 4], device=o3c.Device('cuda:0'))
print(f'After serialization: {a}\n')
with tempfile.TemporaryDirectory() as path:
file_name = os.path.join(path, 'tensor')
pickle.dump(a, open(file_name, 'wb'))
b = pickle.load(open(file_name, 'rb'))
print(f'After deserialization: {b}\n')
# Pickle non-contiguous tensor.
a = o3c.Tensor.ones((100))
a = a[::2]
print(f'Contiguous: {a.is_contiguous()}\n')
with tempfile.TemporaryDirectory() as path:
file_name = os.path.join(path, 'tensor')
pickle.dump(a, open(file_name, 'wb'))
b = pickle.load(open(file_name, 'rb'))
print(f'Contiguous: {b.is_contiguous()}\n')
After serialization: [1 2 3 4]
Tensor[shape={4}, Int64, CPU:0, 0x559b079046e0]
After deserialization: [1 2 3 4]
Tensor[shape={4}, Int64, CPU:0, 0x559b06ba7fc0]
After serialization: [1 2 3 4]
Tensor[shape={4}, Int64, CUDA:0, 0x302000000]
After deserialization: [1 2 3 4]
Tensor[shape={4}, Int64, CUDA:0, 0x302000200]
Contiguous: False
Contiguous: True