使用 C API 访问 NumPy 数组的视图

2024-02-27

在我用 C++ 编写的 Python 扩展模块中,我使用以下代码片段将 NumPy 数组转换为犰狳 http://arma.sourceforge.net/用于代码的 C++ 部分的数组:

static arma::mat convertPyArrayToArma(PyArrayObject* pyarr, int nrows, int ncols)
{
    // Check if the dimensions are what I expect.
    if (!checkPyArrayDimensions(pyarr, nrows, ncols)) throw WrongDimensions();
    
    const std::vector<int> dims = getPyArrayDimensions(pyarr);  // Gets the dimensions using the API
    
    PyArray_Descr* reqDescr = PyArray_DescrFromType(NPY_DOUBLE);
    if (reqDescr == NULL) throw std::bad_alloc();
    
    // Convert the array to Fortran-ordering as required by Armadillo
    PyArrayObject* cleanArr = (PyArrayObject*)PyArray_FromArray(pyarr, reqDescr, 
                                                                NPY_ARRAY_FARRAY);
    if (cleanArr == NULL) throw std::bad_alloc();
    reqDescr = NULL;  // The new reference from DescrFromType was stolen by FromArray

    double* dataPtr = static_cast<double*>(PyArray_DATA(cleanArr));
    arma::mat result (dataPtr, dims[0], dims[1], true);  // this copies the data from cleanArr
    Py_DECREF(cleanArr);
    return result;
}

问题是当我通过这个viewNumPy 数组的(即my_array[:, 3]),它似乎没有正确处理底层 C 数组的步长。根据输出,它看起来像数组pyarr该函数接收到的实际上是完整的基本数组,而不是视图(或者至少当我使用PyArray_DATA,我似乎得到了一个指向完整基本数组的指针)。如果我改为将此函数传递给视图的副本(即my_array[:, 3].copy()),它按预期工作,但我不想每次都记得这样做。

那么,有没有办法让PyArray_FromArray只复制我想要的矩阵切片?我尝试使用标志NPY_ARRAY_ENSURECOPY,但这没有帮助。

Edit 1

正如评论中所建议的,这是一个完整的工作示例:

In file example.cpp:

#define NPY_NO_DEPRECATED_API NPY_1_9_API_VERSION

extern "C" {
    #include <Python.h>
    #include <numpy/arrayobject.h>
}
#include <exception>
#include <cassert>
#include <string>
#include <type_traits>
#include <map>
#include <vector>
#include <armadillo>

class WrongDimensions : public std::exception
{
public:
    WrongDimensions() {}
    const char* what() const noexcept { return msg.c_str(); }

private:
    std::string msg = "The dimensions were incorrect";
};

class NotImplemented : public std::exception
{
public:
    NotImplemented() {}
    const char* what() const noexcept { return msg.c_str(); }

private:
    std::string msg = "Not implemented";
};

class BadArrayLayout : public std::exception
{
public:
    BadArrayLayout() {}
    const char* what() const noexcept { return msg.c_str(); }

private:
    std::string msg = "The matrix was not contiguous";
};

static const std::vector<npy_intp> getPyArrayDimensions(PyArrayObject* pyarr)
{
    npy_intp ndims = PyArray_NDIM(pyarr);
    npy_intp* dims = PyArray_SHAPE(pyarr);
    std::vector<npy_intp> result;
    for (int i = 0; i < ndims; i++) {
        result.push_back(dims[i]);
    }
    return result;
}

/* Checks the dimensions of the given array. Pass -1 for either dimension to say you don't
 * care what the size is in that dimension. Pass dimensions (X, 1) for a vector.
 */
static bool checkPyArrayDimensions(PyArrayObject* pyarr, const npy_intp dim0, const npy_intp dim1)
{
    const auto dims = getPyArrayDimensions(pyarr);
    assert(dims.size() <= 2 && dims.size() > 0);
    if (dims.size() == 1) {
        return (dims[0] == dim0 || dim0 == -1) && (dim1 == 1 || dim1 == -1);
    }
    else {
        return (dims[0] == dim0 || dim0 == -1) && (dims[1] == dim1 || dim1 == -1);
    }
}

template<typename outT>
static arma::Mat<outT> convertPyArrayToArma(PyArrayObject* pyarr, int nrows, int ncols)
{
    if (!checkPyArrayDimensions(pyarr, nrows, ncols)) throw WrongDimensions();

    int arrTypeCode;
    if (std::is_same<outT, uint16_t>::value) {
        arrTypeCode = NPY_UINT16;
    }
    else if (std::is_same<outT, double>::value) {
        arrTypeCode = NPY_DOUBLE;
    }
    else {
        throw NotImplemented();
    }

    const auto dims = getPyArrayDimensions(pyarr);
    if (dims.size() == 1) {
        outT* dataPtr = static_cast<outT*>(PyArray_DATA(pyarr));
        return arma::Col<outT>(dataPtr, dims[0], true);
    }
    else {
        PyArray_Descr* reqDescr = PyArray_DescrFromType(arrTypeCode);
        if (reqDescr == NULL) throw std::bad_alloc();
        PyArrayObject* cleanArr = (PyArrayObject*)PyArray_FromArray(pyarr, reqDescr, NPY_ARRAY_FARRAY);
        if (cleanArr == NULL) throw std::bad_alloc();
        reqDescr = NULL;  // The new reference from DescrFromType was stolen by FromArray

        outT* dataPtr = static_cast<outT*>(PyArray_DATA(cleanArr));
        arma::Mat<outT> result (dataPtr, dims[0], dims[1], true);  // this copies the data from cleanArr
        Py_DECREF(cleanArr);
        return result;
    }
}

static PyObject* convertArmaToPyArray(const arma::mat& matrix)
{
    npy_intp ndim = matrix.is_colvec() ? 1 : 2;
    npy_intp nRows = static_cast<npy_intp>(matrix.n_rows);  // NOTE: This narrows the integer
    npy_intp nCols = static_cast<npy_intp>(matrix.n_cols);
    npy_intp dims[2] = {nRows, nCols};

    PyObject* result = PyArray_SimpleNew(ndim, dims, NPY_DOUBLE);
    if (result == NULL) throw std::bad_alloc();

    double* resultDataPtr = static_cast<double*>(PyArray_DATA((PyArrayObject*)result));
    for (int i = 0; i < nRows; i++) {
        for (int j = 0; j < nCols; j++) {
            resultDataPtr[i * nCols + j] = matrix(i, j);
        }
    }

    return result;
}

extern "C" {
    // An example function that takes a NumPy array and converts it to
    // an arma::mat and back. This should return the array unchanged.
    static PyObject* example_testFunction(PyObject* self, PyObject* args)
    {
        PyArrayObject* myArray = NULL;

        if (!PyArg_ParseTuple(args, "O!", &PyArray_Type, &myArray)) {
            return NULL;
        }

        PyObject* output = NULL;
        try {
            arma::mat myMat = convertPyArrayToArma<double>(myArray, -1, -1);
            output = convertArmaToPyArray(myMat);
        }
        catch (const std::bad_alloc&) {
            PyErr_NoMemory();
            Py_XDECREF(output);
            return NULL;
        }
        catch (const std::exception& err) {
            PyErr_SetString(PyExc_RuntimeError, err.what());
            Py_XDECREF(output);
            return NULL;
        }

        return output;
    }

    static PyMethodDef example_methods[] =
    {
        {"test_function", example_testFunction, METH_VARARGS, "A test function"},
        {NULL, NULL, 0, NULL}
    };

    static struct PyModuleDef example_module = {
       PyModuleDef_HEAD_INIT,
       "example",   /* name of module */
       NULL, /* module documentation, may be NULL */
       -1,       /* size of per-interpreter state of the module,
                    or -1 if the module keeps state in global variables. */
       example_methods
    };

    PyMODINIT_FUNC
    PyInit_example(void)
    {
        import_array();

        PyObject* m = PyModule_Create(&example_module);
        if (m == NULL) return NULL;
        return m;
    }
}

And setup.py用于编译:

from setuptools import setup, Extension
import numpy as np

example_module = Extension(
    'example',
    include_dirs=[np.get_include(), '/usr/local/include'],
    libraries=['armadillo'],
    library_dirs=['/usr/local/lib'],
    sources=['example.cpp'],
    language='c++',
    extra_compile_args=['-std=c++11', '-mmacosx-version-min=10.10'],
    )

setup(name='example',
      ext_modules=[example_module],
      )

现在假设我们有示例数组

a = np.array([[ 1, 2, 3, 4, 5, 6], 
              [ 7, 8, 9,10,11,12], 
              [13,14,15,16,17,18]], dtype='float64')

该函数似乎适用于多维切片,例如a[:, :3],并且它返回的矩阵未改变,正如我所期望的那样。但是如果我给它一个单维切片,除非我制作一个副本,否则我会得到错误的组件:

>>> example.test_function(a[:, 3])
array([ 4.,  5.,  6.])

>>> example.test_function(a[:, 3].copy())
array([  4.,  10.,  16.])

数组的视图只是同一数据数组的另一个信息包装器。Numpy此处不复制任何数据。仅调整用于解释数据的信息,并移动指向数据的指针(如果有用)。

在您的代码中,您假设向量的数据a[:, 3]被表示为内存中的向量,对于NPY_ARRAY_CARRAY and NPY_ARRAY_FARRAY。但是只有在创建数组本身的(fortran 有序)副本后才能获得这种表示形式。

为了使它工作,我修改了你的convertPyArrayToArma()即使它是一个向量,也可以使用一点函数来创建副本:

template<typename outT>
static arma::Mat<outT> convertPyArrayToArma(PyArrayObject* pyarr, int nrows, int ncols)
{
    if (!checkPyArrayDimensions(pyarr, nrows, ncols)) throw WrongDimensions();

    int arrTypeCode;
    if (std::is_same<outT, uint16_t>::value) {
        arrTypeCode = NPY_UINT16;
    }
    else if (std::is_same<outT, double>::value) {
        arrTypeCode = NPY_DOUBLE;
    }
    else {
        throw NotImplemented();
    }

    PyArray_Descr* reqDescr = PyArray_DescrFromType(arrTypeCode);
    if (reqDescr == NULL) throw std::bad_alloc();
    PyArrayObject* cleanArr = (PyArrayObject*)PyArray_FromArray(pyarr, reqDescr, NPY_ARRAY_FARRAY);
    if (cleanArr == NULL) throw std::bad_alloc();
    reqDescr = NULL;  // The new reference from DescrFromType was stolen by FromArray

    const auto dims = getPyArrayDimensions(pyarr);
    outT* dataPtr = static_cast<outT*>(PyArray_DATA(cleanArr));

    // this copies the data from cleanArr
    arma::Mat<outT> result;
    if (dims.size() == 1) {
        result = arma::Col<outT>(dataPtr, dims[0], true);
    }
    else {
        result = arma::Mat<outT>(dataPtr, dims[0], dims[1], true);
    }

    Py_DECREF(cleanArr);
    return result;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 C API 访问 NumPy 数组的视图 的相关文章

随机推荐

  • Textview - 最大长度不适用于椭圆

    我正在尝试创建一个文本视图 以便如果字符数超过 22 则显示省略号 会出现 然而 这不适用于 maxLength
  • 避免在未像素对齐的平铺 SVG 矩形之间出现线条

    我附近有几个rect就像在这个问题 https stackoverflow com questions 13049336 avoid line between tiled svg shapes 但未与像素对齐 我无法更改元素位置 例如 ht
  • 使用带有 Locust 的多 CPU 平台

    I am running htop on the same machine where locust is running during the tests I have been running this morning I see on
  • Xamarin.Forms.WebView 初始加载指示器

    对我来说 采用 Xamarin 表单 WebView 并为第一次加载添加加载进度条的最简单方法是什么 我的应用程序只是包装了一个网站 但在带宽较差的情况下 当启动屏幕消失并且 WebView 正在加载其内容时 我当然会看到白屏 我想看到一个
  • 如何首先在 Entity Framework 5 代码中使用两个外键创建主键?

    我有一个实体 其中主键由另外两个表的两个外键组成 我的配置使用以下内容 但该表是使用两个 FK 引用生成的 桌子 domain Entity1 MorePK PK FK int not null Entity2 Id PK FK int n
  • 禁用 AngularJS 中不需要的验证(条件验证)

    我有一份需要验证的表格 该表单包含许多部分 其中一些部分默认情况下处于禁用状态 每个字段中的值都是正确的 但它违反了我的验证指令 例如 当它被禁用时 它应该包含 0 但是当它可编辑时 它应该包含其他内容 不管怎样 我给它们附加了一个禁用指令
  • C/C++ 中的鲁棒人脸检测?

    我正在寻找一个强大的人脸检测算法 库 最好是 C 语言 C 也可以 如果需要 我可以移植其他语言 我用过OpenCV http opencv willowgarage com 过去的实现 但我不认为它对于轮换是不变的 不需要是实时的 但也不
  • 将 c# 正则表达式转换为 javascript 正则表达式

    使用 C 正则表达式可以使用以下正则表达式
  • 无法从 Cherrypy 将日期时间序列化为 JSON

    我正在尝试发送记录列表以响应 Ajax 查询 这很有效 除非当我的进程因错误而失败时结果包含日期时间字段datetime date 2011 11 1 is not JSON serializable 我试图将我找到的答案结合起来类似的问题
  • 关闭 Google 地图本地点

    我目前有一个网络应用程序 它使用地图 API 绘制兴趣点 但是我注意到一个小烦恼 如果可能的话我想将其关闭 现在 当谷歌地图加载时 它将显示兴趣点和当地商业 市政厅 必胜客等 我不介意标记它们的措辞 但我不希望这些点可点击 就好像使用触摸屏
  • CSS 动画延迟不起作用

    尝试淡入一个 div 7 秒后 淡入另一个 div 我一生都无法弄清楚为什么它不起作用 动画本身可以工作 淡入 淡出动画 但我需要 正在进行的 div 在设定的秒数后淡入 有人知道如何正确执行此操作吗 coming width 320px
  • Android:使用 onClick 更改 ListView 行中的按钮背景

    我的行包含一个按钮 该按钮在我的适配器的 getView 中设置了自己的单击侦听器 我可以使用行父级中的 android descendantFocusability blocksDescendants 来区分按钮点击和实际行项目点击 当我
  • 如何从 pandas 的第一个元素开始重新采样?

    我正在对下表 数据进行重新采样 Timestamp L x L y L a R x R y R a 2403950 621 3 461 3 313 623 3 461 8 260 2403954 622 5 461 3 312 623 3
  • Python 文档字符串中的字符串操作

    我一直在尝试做以下事情 def history dependent simulate self node iterations 1 args kwargs For history dependent simulations only sel
  • Windows 版 Git 不执行我的 .bashrc 文件

    我刚刚在 Windows 7 上安装了 Git for Windows 2 5 0 看来我的 bashrc当我运行 Git Bash 时 文件没有被执行 我像这样创建了文件 Administrator HintTech Dev MINGW6
  • 如何在 Visual Studio 2010 中查看二维数组的所有元素?

    我正在 Visual Studio 2010 中调试我的 C 代码 并希望查看数组的内容 例如 Q 它的大小为 17x17 当我插入断点并尝试调试时 我只看到变量 Q 当我将其带到 观看 屏幕并将其重命名为 Q 17 时 我看到下一级 但我
  • 从解析中删除类/列时出现问题

    我试图从解析中删除一些不需要的列 我不断收到以下错误 错误 类名 Session 必须以字母解析开头 不确定为什么会发生这种情况 或者这是否是一个错误 当我删除一个类时 我也会收到此错误 有没有解决的办法 UPDATE 我刚刚尝试过 我能够
  • 如何使用 FFMPEG 驱动程序使 opencv 工作

    我的 linuxbox 上有一个摄像头 它运行良好 ls al dev video crw rw 1 root video 81 0 janv 8 16 13 dev video0 crw rw 1 root video 81 1 janv
  • Android获取JSON键值

    我对解析特定的问题有疑问json细绳 我没有找到任何对我的情况有帮助的东西 我有这个 json AM country name Armenia data 180854 time published 2012 03 30 13 31 39 t
  • 使用 C API 访问 NumPy 数组的视图

    在我用 C 编写的 Python 扩展模块中 我使用以下代码片段将 NumPy 数组转换为犰狳 http arma sourceforge net 用于代码的 C 部分的数组 static arma mat convertPyArrayTo