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 Status::OK();
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 Status::OK();
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 Status::OK();
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 Status::OK();
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 Status::OK();
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 Status::OK();
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>(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 Status::OK();
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,tensorflow::int64 * nelems)314 Status GetPyArrayDimensionsForTensor(const TF_Tensor* tensor,
315 gtl::InlinedVector<npy_intp, 4>* dims,
316 tensorflow::int64* 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 Status::OK();
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(field);
353 Py_CLEAR(fields);
354 if (convert_result != 1) {
355 return errors::Internal("Failed to create numpy array description for ",
356 "TF_RESOURCE-type tensor");
357 }
358 } else {
359 int type_num = -1;
360 TF_RETURN_IF_ERROR(
361 TF_DataType_to_PyArray_TYPE(TF_TensorType(tensor), &type_num));
362 *descr = PyArray_DescrFromType(type_num);
363 }
364
365 return Status::OK();
366 }
367
FastMemcpy(void * dst,const void * src,size_t size)368 inline void FastMemcpy(void* dst, const void* src, size_t size) {
369 // clang-format off
370 switch (size) {
371 // Most compilers will generate inline code for fixed sizes,
372 // which is significantly faster for small copies.
373 case 1: memcpy(dst, src, 1); break;
374 case 2: memcpy(dst, src, 2); break;
375 case 3: memcpy(dst, src, 3); break;
376 case 4: memcpy(dst, src, 4); break;
377 case 5: memcpy(dst, src, 5); break;
378 case 6: memcpy(dst, src, 6); break;
379 case 7: memcpy(dst, src, 7); break;
380 case 8: memcpy(dst, src, 8); break;
381 case 9: memcpy(dst, src, 9); break;
382 case 10: memcpy(dst, src, 10); break;
383 case 11: memcpy(dst, src, 11); break;
384 case 12: memcpy(dst, src, 12); break;
385 case 13: memcpy(dst, src, 13); break;
386 case 14: memcpy(dst, src, 14); break;
387 case 15: memcpy(dst, src, 15); break;
388 case 16: memcpy(dst, src, 16); break;
389 #if defined(PLATFORM_GOOGLE) || defined(PLATFORM_POSIX) && \
390 !defined(IS_MOBILE_PLATFORM)
391 // On Linux, memmove appears to be faster than memcpy for
392 // large sizes, strangely enough.
393 default: memmove(dst, src, size); break;
394 #else
395 default: memcpy(dst, src, size); break;
396 #endif
397 }
398 // clang-format on
399 }
400
401 } // namespace
402
403 // TODO(slebedev): revise TF_TensorToPyArray usages and switch to the
404 // aliased version where appropriate.
TF_TensorToMaybeAliasedPyArray(Safe_TF_TensorPtr tensor,PyObject ** out_ndarray)405 Status TF_TensorToMaybeAliasedPyArray(Safe_TF_TensorPtr tensor,
406 PyObject** out_ndarray) {
407 auto dtype = TF_TensorType(tensor.get());
408 if (dtype == TF_STRING || dtype == TF_RESOURCE) {
409 return TF_TensorToPyArray(std::move(tensor), out_ndarray);
410 }
411
412 TF_Tensor* moved = tensor.release();
413 int64_t nelems = -1;
414 gtl::InlinedVector<npy_intp, 4> dims;
415 TF_RETURN_IF_ERROR(GetPyArrayDimensionsForTensor(moved, &dims, &nelems));
416 return ArrayFromMemory(
417 dims.size(), dims.data(), TF_TensorData(moved),
418 static_cast<DataType>(dtype), [moved] { TF_DeleteTensor(moved); },
419 out_ndarray);
420 }
421
422 // Converts the given TF_Tensor to a numpy ndarray.
423 // If the returned status is OK, the caller becomes the owner of *out_array.
TF_TensorToPyArray(Safe_TF_TensorPtr tensor,PyObject ** out_ndarray)424 Status TF_TensorToPyArray(Safe_TF_TensorPtr tensor, PyObject** out_ndarray) {
425 // A fetched operation will correspond to a null tensor, and a None
426 // in Python.
427 if (tensor == nullptr) {
428 Py_INCREF(Py_None);
429 *out_ndarray = Py_None;
430 return Status::OK();
431 }
432 int64_t nelems = -1;
433 gtl::InlinedVector<npy_intp, 4> dims;
434 TF_RETURN_IF_ERROR(
435 GetPyArrayDimensionsForTensor(tensor.get(), &dims, &nelems));
436
437 // If the type is neither string nor resource we can reuse the Tensor memory.
438 TF_Tensor* original = tensor.get();
439 TF_Tensor* moved = TF_TensorMaybeMove(tensor.release());
440 if (moved != nullptr) {
441 if (ArrayFromMemory(
442 dims.size(), dims.data(), TF_TensorData(moved),
443 static_cast<DataType>(TF_TensorType(moved)),
444 [moved] { TF_DeleteTensor(moved); }, out_ndarray)
445 .ok()) {
446 return Status::OK();
447 }
448 }
449 tensor.reset(original);
450
451 // Copy the TF_TensorData into a newly-created ndarray and return it.
452 PyArray_Descr* descr = nullptr;
453 TF_RETURN_IF_ERROR(GetPyArrayDescrForTensor(tensor.get(), &descr));
454 Safe_PyObjectPtr safe_out_array =
455 tensorflow::make_safe(PyArray_Empty(dims.size(), dims.data(), descr, 0));
456 if (!safe_out_array) {
457 return errors::Internal("Could not allocate ndarray");
458 }
459 PyArrayObject* py_array =
460 reinterpret_cast<PyArrayObject*>(safe_out_array.get());
461 if (TF_TensorType(tensor.get()) == TF_STRING) {
462 Status s = CopyTF_TensorStringsToPyArray(tensor.get(), nelems, py_array);
463 if (!s.ok()) {
464 return s;
465 }
466 } else if (static_cast<size_t>(PyArray_NBYTES(py_array)) !=
467 TF_TensorByteSize(tensor.get())) {
468 return errors::Internal("ndarray was ", PyArray_NBYTES(py_array),
469 " bytes but TF_Tensor was ",
470 TF_TensorByteSize(tensor.get()), " bytes");
471 } else {
472 FastMemcpy(PyArray_DATA(py_array), TF_TensorData(tensor.get()),
473 PyArray_NBYTES(py_array));
474 }
475
476 *out_ndarray = safe_out_array.release();
477 return Status::OK();
478 }
479
NdarrayToTensor(TFE_Context * ctx,PyObject * ndarray,Safe_TF_TensorPtr * ret)480 Status NdarrayToTensor(TFE_Context* ctx, PyObject* ndarray,
481 Safe_TF_TensorPtr* ret) {
482 DCHECK(ret != nullptr);
483
484 // Make sure we dereference this array object in case of error, etc.
485 Safe_PyObjectPtr array_safe(make_safe(
486 PyArray_FromAny(ndarray, nullptr, 0, 0, NPY_ARRAY_CARRAY_RO, nullptr)));
487 if (!array_safe) return errors::InvalidArgument("Not a ndarray.");
488 PyArrayObject* array = reinterpret_cast<PyArrayObject*>(array_safe.get());
489
490 // Convert numpy dtype to TensorFlow dtype.
491 TF_DataType dtype = TF_FLOAT;
492 TF_RETURN_IF_ERROR(PyArray_TYPE_to_TF_DataType(array, &dtype));
493
494 int64_t nelems = 1;
495 gtl::InlinedVector<int64_t, 4> dims;
496 for (int i = 0; i < PyArray_NDIM(array); ++i) {
497 dims.push_back(PyArray_SHAPE(array)[i]);
498 nelems *= dims[i];
499 }
500
501 // Create a TF_Tensor based on the fed data. In the case of non-string data
502 // type, this steals a reference to array, which will be relinquished when
503 // the underlying buffer is deallocated. For string, a new temporary buffer
504 // is allocated into which the strings are encoded.
505 if (dtype == TF_RESOURCE) {
506 size_t size = PyArray_NBYTES(array);
507 array_safe.release();
508
509 if (ctx) {
510 *ret = make_safe(new TF_Tensor{tensorflow::unwrap(ctx)->CreateTensor(
511 static_cast<tensorflow::DataType>(dtype), {}, 0, PyArray_DATA(array),
512 size, &DelayedNumpyDecref, array)});
513 } else {
514 *ret = make_safe(TF_NewTensor(dtype, {}, 0, PyArray_DATA(array), size,
515 &DelayedNumpyDecref, array));
516 }
517
518 } else if (dtype != TF_STRING) {
519 size_t size = PyArray_NBYTES(array);
520 array_safe.release();
521 if (ctx) {
522 *ret = make_safe(new TF_Tensor{tensorflow::unwrap(ctx)->CreateTensor(
523 static_cast<tensorflow::DataType>(dtype), dims.data(), dims.size(),
524 PyArray_DATA(array), size, &DelayedNumpyDecref, array)});
525 } else {
526 *ret = make_safe(TF_NewTensor(dtype, dims.data(), dims.size(),
527 PyArray_DATA(array), size,
528 &DelayedNumpyDecref, array));
529 }
530
531 } else {
532 size_t size = 0;
533 void* encoded = nullptr;
534 TF_RETURN_IF_ERROR(EncodePyBytesArray(array, nelems, &size, &encoded));
535 if (ctx) {
536 *ret = make_safe(new TF_Tensor{tensorflow::unwrap(ctx)->CreateTensor(
537 static_cast<tensorflow::DataType>(dtype), dims.data(), dims.size(),
538 encoded, size,
539 [](void* data, size_t len, void* arg) {
540 delete[] reinterpret_cast<tensorflow::tstring*>(data);
541 },
542 nullptr)});
543 } else {
544 *ret = make_safe(TF_NewTensor(
545 dtype, dims.data(), dims.size(), encoded, size,
546 [](void* data, size_t len, void* arg) {
547 delete[] reinterpret_cast<tensorflow::tstring*>(data);
548 },
549 nullptr));
550 }
551 }
552
553 return Status::OK();
554 }
555
556 Status TF_TensorToTensor(const TF_Tensor* src, Tensor* dst);
557 TF_Tensor* TF_TensorFromTensor(const tensorflow::Tensor& src, Status* status);
558
NdarrayToTensor(PyObject * obj,Tensor * ret)559 Status NdarrayToTensor(PyObject* obj, Tensor* ret) {
560 Safe_TF_TensorPtr tf_tensor = make_safe(static_cast<TF_Tensor*>(nullptr));
561 Status s = NdarrayToTensor(nullptr /*ctx*/, obj, &tf_tensor);
562 if (!s.ok()) {
563 return s;
564 }
565 return TF_TensorToTensor(tf_tensor.get(), ret);
566 }
567
TensorToNdarray(const Tensor & t,PyObject ** ret)568 Status TensorToNdarray(const Tensor& t, PyObject** ret) {
569 Status status;
570 Safe_TF_TensorPtr tf_tensor = make_safe(TF_TensorFromTensor(t, &status));
571 if (!status.ok()) {
572 return status;
573 }
574 return TF_TensorToMaybeAliasedPyArray(std::move(tf_tensor), ret);
575 }
576
577 } // namespace tensorflow
578