Передача С++ std::Vector в массив numpy в Python

Я пытаюсь передать вектор двойников, который я сгенерировал в моем C++ коде, в массив python numpy. Я хочу выполнить некоторую обработку в потоке в python и хочу использовать некоторые объекты python, как только я заполню массив numpy. Одна из самых больших вещей, которые я хочу сделать, это уметь строить сюжеты, а С++ немного неуклюжие, когда дело доходит до этого. Также я хочу иметь возможность использовать статистическую мощность Python.

Хотя я не очень понимаю, как это сделать. Я потратил много времени на изучение документации API Python C. Я столкнулся с функцией PyArray_SimpleNewFromData, которая, видимо, может сделать трюк. Я все еще очень неясен в отношении общей настройки кода. Я создаю некоторые очень простые тестовые примеры, чтобы помочь мне понять этот процесс. Я сгенерировал следующий код как отдельный пустой проект в Visual Studio express 2012. Я вызываю этот файл Project1

#include <Python.h>
#include "C:/Python27/Lib/site-packages/numpy/core/include/numpy/arrayobject.h"

PyObject * testCreatArray()
{
    float fArray[5] = {0,1,2,3,4};
    npy_intp m = 5;
    PyObject * c = PyArray_SimpleNewFromData(1,&m,PyArray_FLOAT,fArray);
    return c; 
}

Моя цель - прочитать PyObject в Python. Я застрял, потому что не знаю, как ссылаться на этот модуль на Python. В частности, как мне импортировать этот проект из Python, я попытался выполнить импорт Project1 из пути к проекту в python, но не смог. Как только я понимаю этот базовый случай, моя цель - выяснить способ передачи векторного контейнера, который я вычисляю в своей основной функции на Python. Я также не знаю, как это сделать.

Любые эксперты, которые могут помочь мне в этом, или, может быть, опубликуют простой, хорошо содержащий пример код, который читает и заполняет массив numpy из простого вектора С++, я буду благодарен. Большое спасибо заранее.

Ответ 1

Поскольку нет ответа на этот вопрос, который действительно полезен для людей, которые могли бы искать такие вещи, я решил, что поставил бы легкое решение.

Сначала вам нужно создать модуль расширения python на С++, этого достаточно просто сделать и все в документации python c-api, поэтому я не буду вдаваться в это.

Теперь преобразовать С++ std::vector в массив numpy очень просто. Сначала вам нужно импортировать заголовок массива numpy

#include <numpy/arrayobject.h>

и в вашей функции intialising вам нужно import_array()

PyModINIT_FUNC
inittestFunction(void){
   (void) Py_InitModule("testFunction". testFunctionMethods);
   import_array();
}

теперь вы можете использовать функции массива numpy, которые предоставляются. Тот, который вам нужно для этого, - это то, что OP сказал несколько лет назад PyArray_SimpleNewFromData, это глупо прост в использовании. Все, что вам нужно, это массив типа npy_intp, это форма создаваемого массива. убедитесь, что он совпадает с вашим вектором, используя testVector.size(), (и для нескольких измерений выполняйте testVector [0].size(), testVector [0] [0].size() векторы гарантированно непрерывны в С++ 11, если это не bool).

//create testVector with data initialised to 0
std::vector<std::vector<uint16_t>> testVector;
testVector.resize(width, std::vector<uint16_t>(height, 0);
//create shape for numpy array
npy_intp dims[2] = {width, height}
//convert testVector to a numpy array
PyArrayObject* numpyArray = (PyArrayObject*)PyArray_SimpleNewFromData(2, dims, NPY_UINT16, (uint16_t*)testVector.data());

Пройти через параматера. Сначала вам нужно передать его в PyArrayObject, иначе это будет PyObject, и при возврате на python не будет массива numpy. 2, - количество измерений в массиве. dims, является формой массива. Это должно быть типа npy_intp NPY_UINT16 - это тип данных, который будет находиться в python. вы затем используете testVector.data() для получения данных массива, применяете это к void * или указателю того же типа данных, что и ваш вектор.

Надеюсь, это поможет любому, кому это может понадобиться.

(Также, если вам не нужна чистая скорость, я бы посоветовал избежать использования C-API, это вызывает немало проблем, а cython или swig все равно, вероятно, ваш лучший выбор. Также есть c-типы, которые могут быть весьма полезными.

Ответ 2

Я не cpp-герой, но хотел предоставить мое решение с двумя функциями шаблона для 1D и 2D векторов. Это один лайнер для использования l8ter и шаблонов 1D и 2D векторов, компилятор может взять правильную версию для вашей формы векторов. Выбрасывает строку в случае нерегулярной формы в случае 2D. Обычная копия данных здесь, но ее можно легко изменить, чтобы получить адрес первого элемента входного вектора, чтобы сделать его просто "представлением".

Использование выглядит следующим образом:

// Random data
vector<float> some_vector_1D(3,1.f); // 3 entries set to 1
vector< vector<float> > some_vector_2D(3,vector<float>(3,1.f)); // 3 subvectors with 1

// Convert vectors to numpy arrays
PyObject* np_vec_1D = (PyObject*) vector_to_nparray(some_vector_1D);
PyObject* np_vec_2D = (PyObject*) vector_to_nparray(some_vector_2D);

Вы также можете изменить тип массива numpy дополнительными аргументами. Функции шаблона:

/** Convert a c++ 2D vector into a numpy array
 *
 * @param const vector< vector<T> >& vec : 2D vector data
 * @return PyArrayObject* array : converted numpy array
 *
 * Transforms an arbitrary 2D C++ vector into a numpy array. Throws in case of
 * unregular shape. The array may contain empty columns or something else, as
 * long as it shape is square.
 *
 * Warning this routine makes a copy of the memory!
 */
template<typename T>
static PyArrayObject* vector_to_nparray(const vector< vector<T> >& vec, int type_num = PyArray_FLOAT){

   // rows not empty
   if( !vec.empty() ){

      // column not empty
      if( !vec[0].empty() ){

        size_t nRows = vec.size();
        size_t nCols = vec[0].size();
        npy_intp dims[2] = {nRows, nCols};
        PyArrayObject* vec_array = (PyArrayObject *) PyArray_SimpleNew(2, dims, type_num);

        T *vec_array_pointer = (T*) PyArray_DATA(vec_array);

        // copy vector line by line ... maybe could be done at one
        for (size_t iRow=0; iRow < vec.size(); ++iRow){

          if( vec[iRow].size() != nCols){
             Py_DECREF(vec_array); // delete
             throw(string("Can not convert vector<vector<T>> to np.array, since c++ matrix shape is not uniform."));
          }

          copy(vec[iRow].begin(),vec[iRow].end(),vec_array_pointer+iRow*nCols);
        }

        return vec_array;

     // Empty columns
     } else {
        npy_intp dims[2] = {vec.size(), 0};
        return (PyArrayObject*) PyArray_ZEROS(2, dims, PyArray_FLOAT, 0);
     }


   // no data at all
   } else {
      npy_intp dims[2] = {0, 0};
      return (PyArrayObject*) PyArray_ZEROS(2, dims, PyArray_FLOAT, 0);
   }

}


/** Convert a c++ vector into a numpy array
 *
 * @param const vector<T>& vec : 1D vector data
 * @return PyArrayObject* array : converted numpy array
 *
 * Transforms an arbitrary C++ vector into a numpy array. Throws in case of
 * unregular shape. The array may contain empty columns or something else, as
 * long as it shape is square.
 *
 * Warning this routine makes a copy of the memory!
 */
template<typename T>
static PyArrayObject* vector_to_nparray(const vector<T>& vec, int type_num = PyArray_FLOAT){

   // rows not empty
   if( !vec.empty() ){

       size_t nRows = vec.size();
       npy_intp dims[1] = {nRows};

       PyArrayObject* vec_array = (PyArrayObject *) PyArray_SimpleNew(1, dims, type_num);
       T *vec_array_pointer = (T*) PyArray_DATA(vec_array);

       copy(vec.begin(),vec.end(),vec_array_pointer);
       return vec_array;

   // no data at all
   } else {
      npy_intp dims[1] = {0};
      return (PyArrayObject*) PyArray_ZEROS(1, dims, PyArray_FLOAT, 0);
   }

}

Ответ 3

Я наткнулся на ваш пост, пытаясь сделать что-то очень похожее. Я смог собрать решение, полное из которого на моем Github. Он создает два вектора С++, преобразует их в кортежи Python, передает их на Python, преобразует их в массивы NumPy, а затем разбивает их на использование Matplotlib.

Большая часть этого кода взята из документации Python.

Вот некоторые из важных бит из файла .cpp:

 //Make some vectors containing the data
 static const double xarr[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14};
 std::vector<double> xvec (xarr, xarr + sizeof(xarr) / sizeof(xarr[0]) );
 static const double yarr[] = {0,0,1,1,0,0,2,2,0,0,1,1,0,0};
 std::vector<double> yvec (yarr, yarr + sizeof(yarr) / sizeof(yarr[0]) );

 //Transfer the C++ vector to a python tuple
 pXVec = PyTuple_New(xvec.size()); 
 for (i = 0; i < xvec.size(); ++i) {
      pValue = PyFloat_FromDouble(xvec[i]);
      if (!pValue) {
           Py_DECREF(pXVec);
           Py_DECREF(pModule);
           fprintf(stderr, "Cannot convert array value\n");
           return 1;
      }
      PyTuple_SetItem(pXVec, i, pValue);
 }

 //Transfer the other C++ vector to a python tuple
 pYVec = PyTuple_New(yvec.size()); 
 for (i = 0; i < yvec.size(); ++i) {
      pValue = PyFloat_FromDouble(yvec[i]);
      if (!pValue) {
           Py_DECREF(pYVec);
           Py_DECREF(pModule);
           fprintf(stderr, "Cannot convert array value\n");
           return 1;
      }
      PyTuple_SetItem(pYVec, i, pValue); //
 }

 //Set the argument tuple to contain the two input tuples
 PyTuple_SetItem(pArgTuple, 0, pXVec);
 PyTuple_SetItem(pArgTuple, 1, pYVec);

 //Call the python function
 pValue = PyObject_CallObject(pFunc, pArgTuple);

И код Python:

def plotStdVectors(x, y):
    import numpy as np
    import matplotlib.pyplot as plt
    print "Printing from Python in plotStdVectors()"
    print x
    print y
    x = np.fromiter(x, dtype = np.float)
    y = np.fromiter(y, dtype = np.float)
    print x
    print y
    plt.plot(x, y)
    plt.show()
    return 0

Что приводит к сюжету, который я не могу опубликовать здесь из-за моей репутации, но размещен в моем сообщении в блоге здесь.

Ответ 4

  _import_array(); //this is required for numpy to create an array correctly

Примечание. В руководстве по расширению Numpy они используют import_array() для достижения той же цели, что и для _import_array(). Когда я попытался использовать import_array(), на mac я получил ошибку. Поэтому вам может понадобиться попробовать обе команды и посмотреть, какой из них работает.

Кстати, вы можете использовать С++ std::vector в вызове PyArray_SimpleNewFromData. Если ваш std::vector my_vector, замените fArray на &my_vector[0]. &my_vector[0] позволяет получить доступ к указателю, хранящему данные в my_vector.