1 /* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
2
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6
7 http://www.apache.org/licenses/LICENSE-2.0
8
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15
16 #include "tensorflow/python/lib/core/ndarray_tensor.h"
17
18 #include <cstring>
19 #include <optional>
20
21 #include "tensorflow/c/eager/tfe_context_internal.h"
22 #include "tensorflow/c/tf_tensor_internal.h"
23 #include "tensorflow/core/lib/core/coding.h"
24 #include "tensorflow/core/lib/core/errors.h"
25 #include "tensorflow/core/lib/gtl/inlined_vector.h"
26 #include "tensorflow/core/platform/types.h"
27 #include "tensorflow/python/lib/core/bfloat16.h"
28 #include "tensorflow/python/lib/core/ndarray_tensor_bridge.h"
29 #include "tensorflow/python/lib/core/numpy.h"
30
31 namespace tensorflow {
32 namespace {
33
numpy_type_name(int numpy_type)34 char const* numpy_type_name(int numpy_type) {
35 switch (numpy_type) {
36 #define TYPE_CASE(s) \
37 case s: \
38 return #s
39
40 TYPE_CASE(NPY_BOOL);
41 TYPE_CASE(NPY_BYTE);
42 TYPE_CASE(NPY_UBYTE);
43 TYPE_CASE(NPY_SHORT);
44 TYPE_CASE(NPY_USHORT);
45 TYPE_CASE(NPY_INT);
46 TYPE_CASE(NPY_UINT);
47 TYPE_CASE(NPY_LONG);
48 TYPE_CASE(NPY_ULONG);
49 TYPE_CASE(NPY_LONGLONG);
50 TYPE_CASE(NPY_ULONGLONG);
51 TYPE_CASE(NPY_FLOAT);
52 TYPE_CASE(NPY_DOUBLE);
53 TYPE_CASE(NPY_LONGDOUBLE);
54 TYPE_CASE(NPY_CFLOAT);
55 TYPE_CASE(NPY_CDOUBLE);
56 TYPE_CASE(NPY_CLONGDOUBLE);
57 TYPE_CASE(NPY_OBJECT);
58 TYPE_CASE(NPY_STRING);
59 TYPE_CASE(NPY_UNICODE);
60 TYPE_CASE(NPY_VOID);
61 TYPE_CASE(NPY_DATETIME);
62 TYPE_CASE(NPY_TIMEDELTA);
63 TYPE_CASE(NPY_HALF);
64 TYPE_CASE(NPY_NTYPES);
65 TYPE_CASE(NPY_NOTYPE);
66 TYPE_CASE(NPY_CHAR);
67 TYPE_CASE(NPY_USERDEF);
68 default:
69 return "not a numpy type";
70 }
71 }
72
PyArrayDescr_to_TF_DataType(PyArray_Descr * descr,TF_DataType * out_tf_datatype)73 Status PyArrayDescr_to_TF_DataType(PyArray_Descr* descr,
74 TF_DataType* out_tf_datatype) {
75 PyObject* key;
76 PyObject* value;
77 Py_ssize_t pos = 0;
78
79 // Return an error if the fields attribute is null.
80 // Occurs with an improper conversion attempt to resource.
81 if (descr->fields == nullptr) {
82 return errors::Internal("Unexpected numpy data type");
83 }
84
85 if (PyDict_Next(descr->fields, &pos, &key, &value)) {
86 // In Python 3, the keys of numpy custom struct types are unicode, unlike
87 // Python 2, where the keys are bytes.
88 const char* key_string =
89 PyBytes_Check(key) ? PyBytes_AsString(key)
90 : PyBytes_AsString(PyUnicode_AsASCIIString(key));
91 if (!key_string) {
92 return errors::Internal("Corrupt numpy type descriptor");
93 }
94 tensorflow::string key = key_string;
95 // The typenames here should match the field names in the custom struct
96 // types constructed in test_util.py.
97 // TODO(mrry,keveman): Investigate Numpy type registration to replace this
98 // hard-coding of names.
99 if (key == "quint8") {
100 *out_tf_datatype = TF_QUINT8;
101 } else if (key == "qint8") {
102 *out_tf_datatype = TF_QINT8;
103 } else if (key == "qint16") {
104 *out_tf_datatype = TF_QINT16;
105 } else if (key == "quint16") {
106 *out_tf_datatype = TF_QUINT16;
107 } else if (key == "qint32") {
108 *out_tf_datatype = TF_QINT32;
109 } else if (key == "resource") {
110 *out_tf_datatype = TF_RESOURCE;
111 } else {
112 return errors::Internal("Unsupported numpy data type");
113 }
114 return OkStatus();
115 }
116 return errors::Internal("Unsupported numpy data type");
117 }
118
PyArray_TYPE_to_TF_DataType(PyArrayObject * array,TF_DataType * out_tf_datatype)119 Status PyArray_TYPE_to_TF_DataType(PyArrayObject* array,
120 TF_DataType* out_tf_datatype) {
121 int pyarray_type = PyArray_TYPE(array);
122 PyArray_Descr* descr = PyArray_DESCR(array);
123 switch (pyarray_type) {
124 case NPY_FLOAT16:
125 *out_tf_datatype = TF_HALF;
126 break;
127 case NPY_FLOAT32:
128 *out_tf_datatype = TF_FLOAT;
129 break;
130 case NPY_FLOAT64:
131 *out_tf_datatype = TF_DOUBLE;
132 break;
133 case NPY_INT32:
134 *out_tf_datatype = TF_INT32;
135 break;
136 case NPY_UINT8:
137 *out_tf_datatype = TF_UINT8;
138 break;
139 case NPY_UINT16:
140 *out_tf_datatype = TF_UINT16;
141 break;
142 case NPY_UINT32:
143 *out_tf_datatype = TF_UINT32;
144 break;
145 case NPY_UINT64:
146 *out_tf_datatype = TF_UINT64;
147 break;
148 case NPY_INT8:
149 *out_tf_datatype = TF_INT8;
150 break;
151 case NPY_INT16:
152 *out_tf_datatype = TF_INT16;
153 break;
154 case NPY_INT64:
155 *out_tf_datatype = TF_INT64;
156 break;
157 case NPY_BOOL:
158 *out_tf_datatype = TF_BOOL;
159 break;
160 case NPY_COMPLEX64:
161 *out_tf_datatype = TF_COMPLEX64;
162 break;
163 case NPY_COMPLEX128:
164 *out_tf_datatype = TF_COMPLEX128;
165 break;
166 case NPY_OBJECT:
167 case NPY_STRING:
168 case NPY_UNICODE:
169 *out_tf_datatype = TF_STRING;
170 break;
171 case NPY_VOID:
172 // Quantized types are currently represented as custom struct types.
173 // PyArray_TYPE returns NPY_VOID for structs, and we should look into
174 // descr to derive the actual type.
175 // Direct feeds of certain types of ResourceHandles are represented as a
176 // custom struct type.
177 return PyArrayDescr_to_TF_DataType(descr, out_tf_datatype);
178 default:
179 if (pyarray_type == Bfloat16NumpyType()) {
180 *out_tf_datatype = TF_BFLOAT16;
181 break;
182 } else if (pyarray_type == NPY_ULONGLONG) {
183 // NPY_ULONGLONG is equivalent to NPY_UINT64, while their enum values
184 // might be different on certain platforms.
185 *out_tf_datatype = TF_UINT64;
186 break;
187 } else if (pyarray_type == NPY_LONGLONG) {
188 // NPY_LONGLONG is equivalent to NPY_INT64, while their enum values
189 // might be different on certain platforms.
190 *out_tf_datatype = TF_INT64;
191 break;
192 } else if (pyarray_type == NPY_INT) {
193 // NPY_INT is equivalent to NPY_INT32, while their enum values might be
194 // different on certain platforms.
195 *out_tf_datatype = TF_INT32;
196 break;
197 } else if (pyarray_type == NPY_UINT) {
198 // NPY_UINT is equivalent to NPY_UINT32, while their enum values might
199 // be different on certain platforms.
200 *out_tf_datatype = TF_UINT32;
201 break;
202 }
203 return errors::Internal("Unsupported numpy type: ",
204 numpy_type_name(pyarray_type));
205 }
206 return OkStatus();
207 }
208
PyObjectToString(PyObject * obj,const char ** ptr,Py_ssize_t * len,PyObject ** ptr_owner)209 Status PyObjectToString(PyObject* obj, const char** ptr, Py_ssize_t* len,
210 PyObject** ptr_owner) {
211 *ptr_owner = nullptr;
212 if (PyBytes_Check(obj)) {
213 char* buf;
214 if (PyBytes_AsStringAndSize(obj, &buf, len) != 0) {
215 return errors::Internal("Unable to get element as bytes.");
216 }
217 *ptr = buf;
218 return OkStatus();
219 } else if (PyUnicode_Check(obj)) {
220 #if (PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 3))
221 *ptr = PyUnicode_AsUTF8AndSize(obj, len);
222 if (*ptr != nullptr) return OkStatus();
223 #else
224 PyObject* utemp = PyUnicode_AsUTF8String(obj);
225 char* buf;
226 if (utemp != nullptr && PyBytes_AsStringAndSize(utemp, &buf, len) != -1) {
227 *ptr = buf;
228 *ptr_owner = utemp;
229 return Status::OK();
230 }
231 Py_XDECREF(utemp);
232 #endif
233 return errors::Internal("Unable to convert element to UTF-8");
234 } else {
235 return errors::Internal("Unsupported object type ", obj->ob_type->tp_name);
236 }
237 }
238
239 // Iterate over the string array 'array', extract the ptr and len of each string
240 // element and call f(ptr, len).
241 template <typename F>
PyBytesArrayMap(PyArrayObject * array,F f)242 Status PyBytesArrayMap(PyArrayObject* array, F f) {
243 Safe_PyObjectPtr iter = tensorflow::make_safe(
244 PyArray_IterNew(reinterpret_cast<PyObject*>(array)));
245 while (PyArray_ITER_NOTDONE(iter.get())) {
246 auto item = tensorflow::make_safe(PyArray_GETITEM(
247 array, static_cast<char*>(PyArray_ITER_DATA(iter.get()))));
248 if (!item) {
249 return errors::Internal("Unable to get element from the feed - no item.");
250 }
251 Py_ssize_t len;
252 const char* ptr;
253 PyObject* ptr_owner = nullptr;
254 TF_RETURN_IF_ERROR(PyObjectToString(item.get(), &ptr, &len, &ptr_owner));
255 f(ptr, len);
256 Py_XDECREF(ptr_owner);
257 PyArray_ITER_NEXT(iter.get());
258 }
259 return OkStatus();
260 }
261
262 // Encode the strings in 'array' into a contiguous buffer and return the base of
263 // the buffer. The caller takes ownership of the buffer.
EncodePyBytesArray(PyArrayObject * array,int64_t nelems,size_t * size,void ** buffer)264 Status EncodePyBytesArray(PyArrayObject* array, int64_t nelems, size_t* size,
265 void** buffer) {
266 // Encode all strings.
267 *size = nelems * sizeof(tensorflow::tstring);
268 std::unique_ptr<tensorflow::tstring[]> base_ptr(
269 new tensorflow::tstring[nelems]);
270 tensorflow::tstring* dst = base_ptr.get();
271
272 TF_RETURN_IF_ERROR(
273 PyBytesArrayMap(array, [&dst](const char* ptr, Py_ssize_t len) {
274 dst->assign(ptr, len);
275 dst++;
276 }));
277 *buffer = base_ptr.release();
278 return OkStatus();
279 }
280
CopyTF_TensorStringsToPyArray(const TF_Tensor * src,uint64 nelems,PyArrayObject * dst)281 Status CopyTF_TensorStringsToPyArray(const TF_Tensor* src, uint64 nelems,
282 PyArrayObject* dst) {
283 const void* tensor_data = TF_TensorData(src);
284 DCHECK(tensor_data != nullptr);
285 DCHECK_EQ(TF_STRING, TF_TensorType(src));
286
287 const tstring* tstr = static_cast<const tstring*>(tensor_data);
288
289 std::unique_ptr<TF_Status, decltype(&TF_DeleteStatus)> status(
290 TF_NewStatus(), TF_DeleteStatus);
291 auto iter = make_safe(PyArray_IterNew(reinterpret_cast<PyObject*>(dst)));
292 for (int64_t i = 0; i < static_cast<int64_t>(nelems); ++i) {
293 const tstring& tstr_i = tstr[i];
294 auto py_string =
295 make_safe(PyBytes_FromStringAndSize(tstr_i.data(), tstr_i.size()));
296 if (py_string == nullptr) {
297 return errors::Internal(
298 "failed to create a python byte array when converting element #", i,
299 " of a TF_STRING tensor to a numpy ndarray");
300 }
301
302 if (PyArray_SETITEM(dst, static_cast<char*>(PyArray_ITER_DATA(iter.get())),
303 py_string.get()) != 0) {
304 return errors::Internal("Error settings element #", i,
305 " in the numpy ndarray");
306 }
307 PyArray_ITER_NEXT(iter.get());
308 }
309 return OkStatus();
310 }
311
312 // Determine the dimensions of a numpy ndarray to be created to represent an
313 // output Tensor.
GetPyArrayDimensionsForTensor(const TF_Tensor * tensor,gtl::InlinedVector<npy_intp,4> * dims,int64_t * nelems)314 Status GetPyArrayDimensionsForTensor(const TF_Tensor* tensor,
315 gtl::InlinedVector<npy_intp, 4>* dims,
316 int64_t* nelems) {
317 dims->clear();
318 const int ndims = TF_NumDims(tensor);
319 if (TF_TensorType(tensor) == TF_RESOURCE) {
320 if (ndims != 0) {
321 return errors::InvalidArgument(
322 "Fetching of non-scalar resource tensors is not supported.");
323 }
324 dims->push_back(TF_TensorByteSize(tensor));
325 *nelems = dims->back();
326 } else {
327 *nelems = 1;
328 for (int i = 0; i < ndims; ++i) {
329 dims->push_back(TF_Dim(tensor, i));
330 *nelems *= dims->back();
331 }
332 }
333 return OkStatus();
334 }
335
336 // Determine the type description (PyArray_Descr) of a numpy ndarray to be
337 // created to represent an output Tensor.
GetPyArrayDescrForTensor(const TF_Tensor * tensor,PyArray_Descr ** descr)338 Status GetPyArrayDescrForTensor(const TF_Tensor* tensor,
339 PyArray_Descr** descr) {
340 if (TF_TensorType(tensor) == TF_RESOURCE) {
341 PyObject* field = PyTuple_New(3);
342 #if PY_MAJOR_VERSION < 3
343 PyTuple_SetItem(field, 0, PyBytes_FromString("resource"));
344 #else
345 PyTuple_SetItem(field, 0, PyUnicode_FromString("resource"));
346 #endif
347 PyTuple_SetItem(field, 1, PyArray_TypeObjectFromType(NPY_UBYTE));
348 PyTuple_SetItem(field, 2, PyLong_FromLong(1));
349 PyObject* fields = PyList_New(1);
350 PyList_SetItem(fields, 0, field);
351 int convert_result = PyArray_DescrConverter(fields, descr);
352 Py_CLEAR(fields);
353 if (convert_result != 1) {
354 return errors::Internal("Failed to create numpy array description for ",
355 "TF_RESOURCE-type tensor");
356 }
357 } else {
358 int type_num = -1;
359 TF_RETURN_IF_ERROR(
360 TF_DataType_to_PyArray_TYPE(TF_TensorType(tensor), &type_num));
361 *descr = PyArray_DescrFromType(type_num);
362 }
363
364 return OkStatus();
365 }
366
FastMemcpy(void * dst,const void * src,size_t size)367 inline void FastMemcpy(void* dst, const void* src, size_t size) {
368 // clang-format off
369 switch (size) {
370 // Most compilers will generate inline code for fixed sizes,
371 // which is significantly faster for small copies.
372 case 1: memcpy(dst, src, 1); break;
373 case 2: memcpy(dst, src, 2); break;
374 case 3: memcpy(dst, src, 3); break;
375 case 4: memcpy(dst, src, 4); break;
376 case 5: memcpy(dst, src, 5); break;
377 case 6: memcpy(dst, src, 6); break;
378 case 7: memcpy(dst, src, 7); break;
379 case 8: memcpy(dst, src, 8); break;
380 case 9: memcpy(dst, src, 9); break;
381 case 10: memcpy(dst, src, 10); break;
382 case 11: memcpy(dst, src, 11); break;
383 case 12: memcpy(dst, src, 12); break;
384 case 13: memcpy(dst, src, 13); break;
385 case 14: memcpy(dst, src, 14); break;
386 case 15: memcpy(dst, src, 15); break;
387 case 16: memcpy(dst, src, 16); break;
388 #if defined(PLATFORM_GOOGLE) || defined(PLATFORM_POSIX) && \
389 !defined(IS_MOBILE_PLATFORM)
390 // On Linux, memmove appears to be faster than memcpy for
391 // large sizes, strangely enough.
392 default: memmove(dst, src, size); break;
393 #else
394 default: memcpy(dst, src, size); break;
395 #endif
396 }
397 // clang-format on
398 }
399
400 } // namespace
401
402 // TODO(slebedev): revise TF_TensorToPyArray usages and switch to the
403 // aliased version where appropriate.
TF_TensorToMaybeAliasedPyArray(Safe_TF_TensorPtr tensor,PyObject ** out_ndarray)404 Status TF_TensorToMaybeAliasedPyArray(Safe_TF_TensorPtr tensor,
405 PyObject** out_ndarray) {
406 auto dtype = TF_TensorType(tensor.get());
407 if (dtype == TF_STRING || dtype == TF_RESOURCE) {
408 return TF_TensorToPyArray(std::move(tensor), out_ndarray);
409 }
410
411 TF_Tensor* moved = tensor.release();
412 int64_t nelems = -1;
413 gtl::InlinedVector<npy_intp, 4> dims;
414 TF_RETURN_IF_ERROR(GetPyArrayDimensionsForTensor(moved, &dims, &nelems));
415 return ArrayFromMemory(
416 dims.size(), dims.data(), TF_TensorData(moved),
417 static_cast<DataType>(dtype), [moved] { TF_DeleteTensor(moved); },
418 out_ndarray);
419 }
420
421 // Converts the given TF_Tensor to a numpy ndarray.
422 // If the returned status is OK, the caller becomes the owner of *out_array.
TF_TensorToPyArray(Safe_TF_TensorPtr tensor,PyObject ** out_ndarray)423 Status TF_TensorToPyArray(Safe_TF_TensorPtr tensor, PyObject** out_ndarray) {
424 // A fetched operation will correspond to a null tensor, and a None
425 // in Python.
426 if (tensor == nullptr) {
427 Py_INCREF(Py_None);
428 *out_ndarray = Py_None;
429 return OkStatus();
430 }
431 int64_t nelems = -1;
432 gtl::InlinedVector<npy_intp, 4> dims;
433 TF_RETURN_IF_ERROR(
434 GetPyArrayDimensionsForTensor(tensor.get(), &dims, &nelems));
435
436 // If the type is neither string nor resource we can reuse the Tensor memory.
437 TF_Tensor* original = tensor.get();
438 TF_Tensor* moved = TF_TensorMaybeMove(tensor.release());
439 if (moved != nullptr) {
440 if (ArrayFromMemory(
441 dims.size(), dims.data(), TF_TensorData(moved),
442 static_cast<DataType>(TF_TensorType(moved)),
443 [moved] { TF_DeleteTensor(moved); }, out_ndarray)
444 .ok()) {
445 return OkStatus();
446 }
447 }
448 tensor.reset(original);
449
450 // Copy the TF_TensorData into a newly-created ndarray and return it.
451 PyArray_Descr* descr = nullptr;
452 TF_RETURN_IF_ERROR(GetPyArrayDescrForTensor(tensor.get(), &descr));
453 Safe_PyObjectPtr safe_out_array =
454 tensorflow::make_safe(PyArray_Empty(dims.size(), dims.data(), descr, 0));
455 if (!safe_out_array) {
456 return errors::Internal("Could not allocate ndarray");
457 }
458 PyArrayObject* py_array =
459 reinterpret_cast<PyArrayObject*>(safe_out_array.get());
460 if (TF_TensorType(tensor.get()) == TF_STRING) {
461 Status s = CopyTF_TensorStringsToPyArray(tensor.get(), nelems, py_array);
462 if (!s.ok()) {
463 return s;
464 }
465 } else if (static_cast<size_t>(PyArray_NBYTES(py_array)) !=
466 TF_TensorByteSize(tensor.get())) {
467 return errors::Internal("ndarray was ", PyArray_NBYTES(py_array),
468 " bytes but TF_Tensor was ",
469 TF_TensorByteSize(tensor.get()), " bytes");
470 } else {
471 FastMemcpy(PyArray_DATA(py_array), TF_TensorData(tensor.get()),
472 PyArray_NBYTES(py_array));
473 }
474
475 *out_ndarray = safe_out_array.release();
476 return OkStatus();
477 }
478
NdarrayToTensor(TFE_Context * ctx,PyObject * ndarray,Safe_TF_TensorPtr * ret)479 Status NdarrayToTensor(TFE_Context* ctx, PyObject* ndarray,
480 Safe_TF_TensorPtr* ret) {
481 DCHECK(ret != nullptr);
482
483 // Make sure we dereference this array object in case of error, etc.
484 Safe_PyObjectPtr array_safe(make_safe(
485 PyArray_FromAny(ndarray, nullptr, 0, 0, NPY_ARRAY_CARRAY_RO, nullptr)));
486 if (!array_safe) return errors::InvalidArgument("Not a ndarray.");
487 PyArrayObject* array = reinterpret_cast<PyArrayObject*>(array_safe.get());
488
489 // Convert numpy dtype to TensorFlow dtype.
490 TF_DataType dtype = TF_FLOAT;
491 TF_RETURN_IF_ERROR(PyArray_TYPE_to_TF_DataType(array, &dtype));
492
493 int64_t nelems = 1;
494 gtl::InlinedVector<int64_t, 4> dims;
495 for (int i = 0; i < PyArray_NDIM(array); ++i) {
496 dims.push_back(PyArray_SHAPE(array)[i]);
497 nelems *= dims[i];
498 }
499
500 // Create a TF_Tensor based on the fed data. In the case of non-string data
501 // type, this steals a reference to array, which will be relinquished when
502 // the underlying buffer is deallocated. For string, a new temporary buffer
503 // is allocated into which the strings are encoded.
504 if (dtype == TF_RESOURCE) {
505 size_t size = PyArray_NBYTES(array);
506 array_safe.release();
507
508 if (ctx) {
509 *ret = make_safe(new TF_Tensor{tensorflow::unwrap(ctx)->CreateTensor(
510 static_cast<tensorflow::DataType>(dtype), {}, 0, PyArray_DATA(array),
511 size, &DelayedNumpyDecref, array)});
512 } else {
513 *ret = make_safe(TF_NewTensor(dtype, {}, 0, PyArray_DATA(array), size,
514 &DelayedNumpyDecref, array));
515 }
516
517 } else if (dtype != TF_STRING) {
518 size_t size = PyArray_NBYTES(array);
519 array_safe.release();
520 if (ctx) {
521 *ret = make_safe(new TF_Tensor{tensorflow::unwrap(ctx)->CreateTensor(
522 static_cast<tensorflow::DataType>(dtype), dims.data(), dims.size(),
523 PyArray_DATA(array), size, &DelayedNumpyDecref, array)});
524 } else {
525 *ret = make_safe(TF_NewTensor(dtype, dims.data(), dims.size(),
526 PyArray_DATA(array), size,
527 &DelayedNumpyDecref, array));
528 }
529
530 } else {
531 size_t size = 0;
532 void* encoded = nullptr;
533 TF_RETURN_IF_ERROR(EncodePyBytesArray(array, nelems, &size, &encoded));
534 if (ctx) {
535 *ret = make_safe(new TF_Tensor{tensorflow::unwrap(ctx)->CreateTensor(
536 static_cast<tensorflow::DataType>(dtype), dims.data(), dims.size(),
537 encoded, size,
538 [](void* data, size_t len, void* arg) {
539 delete[] reinterpret_cast<tensorflow::tstring*>(data);
540 },
541 nullptr)});
542 } else {
543 *ret = make_safe(TF_NewTensor(
544 dtype, dims.data(), dims.size(), encoded, size,
545 [](void* data, size_t len, void* arg) {
546 delete[] reinterpret_cast<tensorflow::tstring*>(data);
547 },
548 nullptr));
549 }
550 }
551
552 return OkStatus();
553 }
554
555 Status TF_TensorToTensor(const TF_Tensor* src, Tensor* dst);
556 TF_Tensor* TF_TensorFromTensor(const tensorflow::Tensor& src, Status* status);
557
NdarrayToTensor(PyObject * obj,Tensor * ret)558 Status NdarrayToTensor(PyObject* obj, Tensor* ret) {
559 Safe_TF_TensorPtr tf_tensor = make_safe(static_cast<TF_Tensor*>(nullptr));
560 Status s = NdarrayToTensor(nullptr /*ctx*/, obj, &tf_tensor);
561 if (!s.ok()) {
562 return s;
563 }
564 return TF_TensorToTensor(tf_tensor.get(), ret);
565 }
566
TensorToNdarray(const Tensor & t,PyObject ** ret)567 Status TensorToNdarray(const Tensor& t, PyObject** ret) {
568 Status status;
569 Safe_TF_TensorPtr tf_tensor = make_safe(TF_TensorFromTensor(t, &status));
570 if (!status.ok()) {
571 return status;
572 }
573 return TF_TensorToMaybeAliasedPyArray(std::move(tf_tensor), ret);
574 }
575
576 } // namespace tensorflow
577