ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
AdvancedIndexing.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 
13 
14 namespace cloudViewer {
15 namespace core {
16 
18  const std::vector<Tensor>& index_tensors) {
19  bool index_dim_started = false;
20  bool index_dim_ended = false;
21  for (const Tensor& index_tensor : index_tensors) {
22  if (index_tensor.NumDims() == 0) {
23  // This dimension is sliced.
24  if (index_dim_started) {
25  index_dim_ended = true;
26  }
27  } else {
28  // This dimension is indexed.
29  if (index_dim_ended) {
30  return true;
31  }
32  if (!index_dim_started) {
33  index_dim_started = true;
34  }
35  }
36  }
37  return false;
38 }
39 
40 std::pair<Tensor, std::vector<Tensor>>
42  const Tensor& tensor, const std::vector<Tensor>& index_tensors) {
43  int64_t ndims = tensor.NumDims();
44  std::vector<int64_t> permutation;
45  std::vector<Tensor> permuted_index_tensors;
46  for (int64_t i = 0; i < ndims; ++i) {
47  if (index_tensors[i].NumDims() != 0) {
48  permutation.push_back(i);
49  permuted_index_tensors.emplace_back(index_tensors[i]);
50  }
51  }
52  for (int64_t i = 0; i < ndims; ++i) {
53  if (index_tensors[i].NumDims() == 0) {
54  permutation.push_back(i);
55  permuted_index_tensors.emplace_back(index_tensors[i]);
56  }
57  }
58  return std::make_pair(tensor.Permute(permutation),
59  std::move(permuted_index_tensors));
60 }
61 
62 std::pair<std::vector<Tensor>, SizeVector>
64  const std::vector<Tensor>& index_tensors) {
65  SizeVector replacement_shape({}); // {} can be broadcasted to any shape.
66  for (const Tensor& index_tensor : index_tensors) {
67  if (index_tensor.NumDims() != 0) {
68  replacement_shape = shape_util::BroadcastedShape(
69  replacement_shape, index_tensor.GetShape());
70  }
71  }
72 
73  std::vector<Tensor> expanded_tensors;
74  for (const Tensor& index_tensor : index_tensors) {
75  if (index_tensor.NumDims() == 0) {
76  expanded_tensors.push_back(index_tensor);
77  } else {
78  expanded_tensors.push_back(index_tensor.Expand(replacement_shape));
79  }
80  }
81 
82  return std::make_pair(expanded_tensors, replacement_shape);
83 }
84 
86  int64_t dims_before,
87  int64_t dims_indexed,
88  SizeVector replacement_shape) {
89  SizeVector shape = tensor.GetShape();
90  SizeVector strides = tensor.GetStrides();
91  int64_t end = dims_before + dims_indexed;
92  shape.erase(shape.begin() + dims_before, shape.begin() + end);
93  strides.erase(strides.begin() + dims_before, strides.begin() + end);
94  shape.insert(shape.begin() + dims_before, replacement_shape.begin(),
95  replacement_shape.end());
96  strides.insert(strides.begin() + dims_before, replacement_shape.size(), 0);
97  return tensor.AsStrided(shape, strides);
98 }
99 
101  const Tensor& index_tensor, int64_t dims_before, int64_t dims_after) {
102  SizeVector old_shape = index_tensor.GetShape();
103  SizeVector new_shape(dims_before + index_tensor.NumDims() + dims_after, 1);
104  std::copy(old_shape.begin(), old_shape.end(),
105  new_shape.begin() + dims_before);
106  Tensor reshaped = index_tensor.Reshape(new_shape);
107  return reshaped;
108 }
109 
111  // Dimension check
112  if (static_cast<int64_t>(index_tensors_.size()) > tensor_.NumDims()) {
114  "Number of index_tensors {} exceeds tensor dimension "
115  "{}.",
116  index_tensors_.size(), tensor_.NumDims());
117  }
118 
119  // Index tensors must be using int64.
120  // Boolean indexing tensors will be supported in the future by
121  // converting to int64_t tensors.
122  for (const Tensor& index_tensor : index_tensors_) {
123  if (index_tensor.GetDtype() != core::Int64) {
125  "Index tensor must have Int64 dtype, but {} was used.",
126  index_tensor.GetDtype().ToString());
127  }
128  }
129 
130  // Fill implied 0-d index tensors at the tail dimensions.
131  // 0-d index tensor represents a fully sliced dimension, i.e. [:] in Numpy.
132  // Partial slice e.g. [1:3] shall be handled outside of advanced indexing.
133  //
134  // E.g. Given A.shape == [5, 6, 7, 8],
135  // A[[1, 2], [3, 4]] is converted to
136  // A[[1, 2], [3, 4], :, :].
137  Tensor empty_index_tensor =
139  int64_t num_omitted_dims = tensor_.NumDims() - index_tensors_.size();
140  for (int64_t i = 0; i < num_omitted_dims; ++i) {
141  index_tensors_.push_back(empty_index_tensor);
142  }
143 
144  // Fill 0 to 0-d index tensors. The omitted indexing tensors is equivalent
145  // to always increment offset 0.
146  for (Tensor& index_tensor : index_tensors_) {
147  if (index_tensor.NumDims() == 0) {
148  index_tensor.Fill(0);
149  }
150  }
151 
152  // Transpose all indexed dimensions to front if indexed dimensions are
153  // splitted by sliced dimensions. The tensor being indexed are dimshuffled
154  // accordingly.
155  //
156  // E.g. Given A.shape == [5, 6, 7, 8],
157  // A[[1, 2], :, [3, 4], :] is converted to
158  // A.permute([0, 2, 1, 3])[[1, 2], [3, 4], :, :].
159  // The resulting shape is (2, 6, 8).
160  //
161  // See "Combining advanced and basic indexing" section of
162  // https://docs.scipy.org/doc/numpy/reference/arrays.indexing.html
164  std::tie(tensor_, index_tensors_) =
166  }
167 
168  // Put index tensors_ on the same device as tensor_.
169  for (size_t i = 0; i < index_tensors_.size(); ++i) {
170  if (index_tensors_[i].GetDevice() != tensor_.GetDevice()) {
172  }
173  }
174 
175  // Expand (broadcast with view) all index_tensors_ to a common shape,
176  // ignoring 0-d index_tensors_.
177  SizeVector replacement_shape;
178  std::tie(index_tensors_, replacement_shape) =
180 
181  int64_t dims_before = 0;
182  int64_t dims_after = 0;
183  int64_t dims_indexed = 0;
184  bool replacement_shape_inserted = false;
185  for (size_t dim = 0; dim < index_tensors_.size(); dim++) {
186  if (index_tensors_[dim].NumDims() == 0) {
187  if (dims_indexed == 0) {
188  dims_before++;
189  } else {
190  dims_after++;
191  }
193  } else {
194  if (!replacement_shape_inserted) {
196  replacement_shape.begin(),
197  replacement_shape.end());
198  replacement_shape_inserted = true;
199  }
200  dims_indexed++;
203  }
204  }
205 
206  // If the indexed_shape_ contains a dimension of size 0 but the
207  // replacement shape does not, the index is out of bounds. This is because
208  // there is no valid number to index an empty tensor.
209  // Normally, out of bounds is detected in the advanced indexing kernel. We
210  // detected here for more helpful error message.
211  auto contains_zero = [](const SizeVector& vals) -> bool {
212  return std::any_of(vals.begin(), vals.end(),
213  [](int64_t val) { return val == 0; });
214  };
215  if (contains_zero(indexed_shape_) && !contains_zero(replacement_shape)) {
216  utility::LogError("Index is out of bounds for dimension with size 0");
217  }
218 
219  // Restride tensor_ and index tensors_.
220  tensor_ = RestrideTensor(tensor_, dims_before, dims_indexed,
221  replacement_shape);
222  for (size_t dim = 0; dim < index_tensors_.size(); dim++) {
223  if (index_tensors_[dim].NumDims() != 0) {
225  dims_before, dims_after);
226  }
227  }
228 }
229 
231  const std::vector<Tensor>& index_tensors) {
232  std::vector<Tensor> res_index_tensors;
233  for (const Tensor& index_tensor : index_tensors) {
234  if (index_tensor.GetDtype() == core::Bool) {
235  std::vector<Tensor> non_zero_indices = index_tensor.NonZeroNumpy();
236  res_index_tensors.insert(res_index_tensors.end(),
237  non_zero_indices.begin(),
238  non_zero_indices.end());
239  } else {
240  res_index_tensors.push_back(index_tensor);
241  }
242  }
243  return res_index_tensors;
244 }
245 
246 } // namespace core
247 } // namespace cloudViewer
bool copy
Definition: VtkUtils.cpp:74
std::vector< Tensor > index_tensors_
The processed index tensors.
void RunPreprocess()
Preprocess tensor and index tensors.
static std::pair< std::vector< Tensor >, SizeVector > ExpandToCommonShapeExceptZeroDim(const std::vector< Tensor > &index_tensors)
static std::vector< Tensor > ExpandBoolTensors(const std::vector< Tensor > &index_tensors)
Expand boolean tensor to integer index.
static bool IsIndexSplittedBySlice(const std::vector< Tensor > &index_tensors)
static Tensor RestrideIndexTensor(const Tensor &index_tensor, int64_t dims_before, int64_t dims_after)
static std::pair< Tensor, std::vector< Tensor > > ShuffleIndexedDimsToFront(const Tensor &tensor, const std::vector< Tensor > &index_tensors)
static Tensor RestrideTensor(const Tensor &tensor, int64_t dims_before, int64_t dims_indexed, SizeVector replacement_shape)
iterator erase(const_iterator CI)
Definition: SmallVector.h:779
iterator insert(iterator I, T &&Elt)
Definition: SmallVector.h:853
int64_t NumDims() const
Definition: Tensor.h:1172
Tensor AsStrided(const SizeVector &new_shape, const SizeVector &new_strides) const
Create a Tensor view of specified shape and strides. The underlying buffer and data_ptr offsets remai...
Definition: Tensor.cpp:1061
Tensor Permute(const SizeVector &dims) const
Permute (dimension shuffle) the Tensor, returns a view.
Definition: Tensor.cpp:1028
SizeVector GetStrides() const
Definition: Tensor.h:1135
int64_t GetStride(int64_t dim) const
Definition: Tensor.h:1139
Device GetDevice() const override
Definition: Tensor.cpp:1435
Tensor Reshape(const SizeVector &dst_shape) const
Definition: Tensor.cpp:671
SizeVector GetShape() const
Definition: Tensor.h:1127
#define LogError(...)
Definition: Logging.h:60
SizeVector BroadcastedShape(const SizeVector &l_shape, const SizeVector &r_shape)
Returns the broadcasted shape of two shapes.
Definition: ShapeUtil.cpp:56
const Dtype Bool
Definition: Dtype.cpp:52
const Dtype Int64
Definition: Dtype.cpp:47
CLOUDVIEWER_HOST_DEVICE Pair< First, Second > make_pair(const First &_first, const Second &_second)
Definition: SlabTraits.h:49
Generic file read and write utility for python interface.