• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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