在Cython中返回一个结构数组

我试图在Cython中返回一个结构数组。

// .pyx from libc.stdint cimport uint8_t cdef extern from "": cdef struct apriltag_detection: int id double c[2] double p[4][2] ctypedef apriltag_detection apriltag_detection_t cdef extern from "tag36h11_detector/tag36h11_detector.h": apriltag_detection_t* scan_frame(int width, int height, uint8_t* data); cdef class Detection: # how do I "link" this to the struct defined above? def __cinit__(self): pass def __dealloc__(self): pass def detect(width, height, frame): return scan_frame(width, height, frame) 

理想情况下,我想在Python代码中调用detect函数,并获取一个Detection对象列表,其中Detection是C struct apriltag_detection的包装类,它是apriltag_detection_t

我收到以下编译错误:

tag36h11_detector.pyx:22:21:无法将’apriltag_detection_t *’转换为Python对象

我找不到在文档中的任何地方返回指向结构或结构数组的指针的引用。

更新3

 // .h typedef struct detection_payload { int size; apriltag_detection_t** detections; } detection_payload_t; 

我正在尝试将上面的结构转换为包含size的Python对象和包含apriltag_detection_t对象的Python列表。

 // .pyx cdef extern from "": cdef struct apriltag_detection: int id double c[2] double p[4][2] ctypedef apriltag_detection apriltag_detection_t cdef extern from "tag36h11_detector/tag36h11_detector.h": cdef struct detection_payload: int size apriltag_detection_t** detections ctypedef detection_payload detection_payload_t detection_payload* scan_frame(int width, int height, uint8_t* data) ... cdef class Detection: cdef apriltag_detection* _d def __cinit__(self): self._d = NULL cdef _setup(self, apriltag_detection* d): self._d = d def __dealloc__(self): self._d = NULL property id: def __get__(self): return self._d.id property c: def __get__(self): return self._d.c property p: def __get__(self): return self._d.p cdef Detection_create(apriltag_detection_t* d): return Detection()._setup(d) cdef class DetectionPayload: cdef detection_payload* _p def __cinit__(self): self._p = NULL cdef _setup(self, detection_payload* p): self._p = p self.size = p.size self.detections = [] for i in range(0, self.size): apriltag_detection_t* detection = self._p.detections[i] d = Detection_create(detection) self.detections+=[d] def __dealloc__(self): _p = NULL property size: def __get__(self): return self.size property detections: def __get__(self): return self.detections 

我在线上遇到了几个语法错误:

 apriltag_detection_t* detection = self._p.detections[I] 

具体来说,在指针apriltag_detection_t*

更新2

这个编译和导入现在很好。 arrays上仍然没有进展

 from libc.stdint cimport uint8_t cdef extern from "": cdef struct apriltag_detection: int id double c[2] double p[4][2] ctypedef apriltag_detection apriltag_detection_t cdef extern from "tag36h11_detector/tag36h11_detector.h": apriltag_detection_t* scan_frame(int width, int height, uint8_t* data); cdef class Detection: cdef apriltag_detection* _d def __cinit__(self): self._d = NULL cdef _setup(self, apriltag_detection* d): self._d = d def __dealloc__(self): self._d = NULL property id: def __get__(self): return self._d.id property c: def __get__(self): return self._d.c property p: def __get__(self): return self._d.p cdef Detection_create(apriltag_detection_t* d): return Detection()._setup(d) def detect(width, height, frame): cdef apriltag_detection_t* detection = scan_frame(width, height, frame) return Detection_create(detection) 

更新1

我尝试按照下面链接的post,这是我到目前为止。

 from libc.stdint cimport uint8_t cdef extern from "": cdef struct apriltag_detection: int id double c[2] double p[4][2] ctypedef apriltag_detection apriltag_detection_t cdef extern from "tag36h11_detector/tag36h11_detector.h": apriltag_detection_t* scan_frame(int width, int height, uint8_t* data); cdef class Detection: cdef apriltag_detection* _d; def __cinit__(self): self._d = NULL def _setup(self, apriltag_detection* d): self._d = d def __dealloc__(self): self._d = NULL property id: def __get__(self): return self._t.id property c: def __get__(self): return self._t.c property p: def __get__(self): return self._t.p cdef Detection_create(apriltag_detection_t* d): return Detection()._setup(d) def detect(width, height, frame): return scan_frame(width, height, frame) 

虽然这比我以前更接近,但我仍然得到错误:

tag36h11_detector.pyx:33:30:无法将’apriltag_detection_t *’转换为Python对象

在线上

 cdef Detection_create(apriltag_detection_t* d): return Detection()._setup(d) 

而且,我不知道如何返回Python列表……

你似乎已经解决了你的问题,但我想回答这个问题。 首先是因为我想尝试一下,其次是因为我认为你在内存管理方面存在一些问题,我想指出它。

我们将包装以下简单的C接口:

 //creator.h typedef struct { int mult; int add; } Result; typedef struct { int size; Result *arr; } ResultArray; ResultArray create(int size, int *input){ //whole file at the end of the answer } 

它处理输入数组并返回结构的C数组以及该数组中的元素数。

我们的包装pyx文件看起来如下:

 #result_import.pyx (verion 0) cdef extern from "creator.h": ctypedef struct Result: int mult int add ctypedef struct ResultArray: int size Result *arr ResultArray create(int size, int *input) def create_structs(int[::1] input_vals): pass 

最值得注意的部分:我使用memoryview( int[::1] )来传递我的输入数组,这有两个好处:

  1. 在python端,可以使用支持内存视图的任何东西( numpy ,自Python3以来的数组),这样可以更灵活地使用numpy数组
  2. 确保(通过[::1] )输入是连续的

在测试脚本中我使用numpy,但也可以使用内置数组:

 #test.py import result_import import numpy as np a=np.array([1,2,3],dtype='int32') result=result_import.create_structs(a) for i,el in enumerate(result): print i, ": mult:", el.mult, " add:", el.add 

现在什么都没有用,但一切都准备好了。

第一种情况:我们只想让普通的python-objects没什么特别的! 一种可能性是:

 #result_import.pyx (verion 1) #from cpython cimport array needed for array.array in Python2 from libc.stdlib cimport free .... class PyResult: def __init__(self, mult, add): self.mult=mult self.add=add def create_structs(int[::1] input_vals): cdef ResultArray res=create(len(input_vals), &input_vals[0]) try: lst=[] for i in range(res.size): lst.append(PyResult(res.arr[i].mult, res.arr[i].add)) finally: free(res.arr) return lst 

我将整个数据转换为python-objects并使用一个简单的列表。 很简单,但有两件事值得注意:

  1. 内存管理:我负责释放res.arr中分配的内存。 所以我使用try...finally确保即使抛出exception也会发生。
  2. 仅将此指针设置为NULL是不够的,我必须调用free -function。

现在我们的test.py工作 – 很好!

第二种情况:但是,如果我只需要一些元素并将它们全部转换 – 它只是效率低下。 此外,我将所有元素保留在内存中两次(至少在一段时间内) – 这是天真方法的缺点。 所以我想在稍后的程序中按需创建PyResult -objects。

让我们写一个包装器列表:

 #result_import.pyx (verion 2) ... cdef class WrappingList: cdef int size cdef Result *arr def __cinit__(self): self.size=0 self.arr=NULL def __dealloc__(self): free(self.arr) print "deallocated"#just a check def __getitem__(self, index): if index<0 or index>=self.size: raise IndexError("list index out of range") return PyResult(self.arr[index].mult, self.arr[index].add) def create_structs(int[::1] input_vals): cdef ResultArray res=create(len(input_vals), &input_vals[0]) lst=WrappingList() lst.size, lst.arr=res.size, res.arr return lst 

所以类WrappingList行为很像列表,保留整个C数组而不复制,只在需要时创建PyResult -objects。 值得一提的事:

  1. WrapperingList被销毁时调用WrapperingList – 这是我们释放由C代码提供给我们的内存的地方。 在test.py结束时,我们应该看到“deallocated”,否则出现问题……
  2. __getitem__用于迭代。

第三种情况: Python代码不仅应该读取结果而且还要更改它们,因此更改的数据可以传递回C代码。 为此,让PyResult成为代理:

 #result_import.pyx (verion 3, last) ... cdef class PyResult: cdef Result *ptr #ptr to my element def __init__(self): self.ptr=NULL @property def mult(self): return self.ptr.mult @mult.setter def mult(self, value): self.ptr.mult = value @property def add(self): return self.ptr.add @add.setter def add(self, value): self.ptr.add = value cdef class WrappingList: ... def __getitem__(self, index): if index>=self.size: raise IndexError("list index out of range") res=PyResult() res.ptr=&self.arr[index] return res 

现在, PyResult包含一个指向相应元素的指针,可以直接在C数组中更改它。 但是,有一些陷阱,我应该提一下:

  1. 它有点不安全: PyResult不应超过父级WrappingList -object。 您可以通过在PyResult -class中添加对父级的引用来修复它。
  2. 访问元素( addmult )非常昂贵,因为每次必须创建,注册然后删除新的Python对象。

让我们改变测试脚本,看看代理在行动:

 #test.py(second version) import result_import import numpy as np a=np.array([1,2,3],dtype='int32') result=result_import.create_structs(a) for i,el in enumerate(result): print i, ": mult:", el.mult, " add:", el.add el.mult, el.add=42*i,21*i # now print changed values: for i,el in enumerate(result): print i, ": mult:", el.mult, " add:", el.add 

还有很多需要改进的地方,但我想一个答案就够了:)


附件:

Sloppy creator.h – 需要检查malloc的结果:

 //creator.h typedef struct { int mult; int add; } Result; typedef struct { int size; Result *arr; } ResultArray; ResultArray create(int size, int *input){ ResultArray res; res.size=size; res.arr=(Result *)malloc(size*sizeof(Result));//todo: check !=0 for(int i=0;i 

setup.py:

 from distutils.core import setup, Extension from Cython.Build import cythonize setup(ext_modules=cythonize(Extension( name='result_import', sources = ["result_import.pyx"] )))