簡體   English   中英

傳遞/返回Cython Memoryviews與NumPy數組

[英]Passing/Returning Cython Memoryviews vs NumPy Arrays

我正在編寫Python代碼來加速二進制圖像中帶標簽對象的區域屬性功能。 以下代碼將在給定對象索引的情況下計算二進制圖像中帶標簽對象的邊界像素數。 main()函數將循環遍歷二進制圖像“遮罩”中的所有標記對象,並計算每個像素的邊框像素數。

我想知道最好的方法是在此Cython代碼中傳遞或返回變量。 變量位於NumPy數組中或鍵入的Memoryviews中。 我搞砸了以不同格式傳遞/返回變量的方法,但是無法推斷出最佳/最有效的方法是什么。 我是Cython的新手,因此Memoryviews對我來說仍然相當抽象,並且兩種方法之間是否存在差異仍然是一個謎。 我正在使用的圖像包含100,000+個帶有標簽的對象,因此諸如此類的操作需要相當高效。

總結一下:

什么時候/應該將變量作為類型的Memoryview而不是NumPy數組傳遞/返回,以便進行非常重復的計算? 有沒有最好的方法,或者它們是完全一樣的?

%%cython --annotate

import numpy as np
import cython
cimport numpy as np

DTYPE = np.intp
ctypedef np.intp_t DTYPE_t

@cython.boundscheck(False)
@cython.wraparound(False)
def erode(DTYPE_t [:,:] img):

    # Image dimensions
    cdef int height, width, local_min
    height = img.shape[0]
    width = img.shape[1]

    # Padded Array
    padded_np = np.zeros((height+2, width+2), dtype = DTYPE)
    cdef DTYPE_t[:,:] padded = padded_np
    padded[1:height+1,1:width+1] = img

    # Eroded image
    eroded_np = np.zeros((height,width),dtype=DTYPE)
    cdef DTYPE_t[:,:] eroded = eroded_np

    cdef DTYPE_t i,j
    for i in range(height):
        for j in range(width):
            local_min = min(padded[i+1,j+1], padded[i,j+1], padded[i+1,j],padded[i+1,j+2],padded[i+2,j+1])
            eroded[i,j] = local_min
    return eroded_np


@cython.boundscheck(False)
@cython.wraparound(False)
def border_image(slice_np):

    # Memoryview of slice_np
    cdef DTYPE_t [:,:] slice = slice_np

    # Image dimensions
    cdef Py_ssize_t ymax, xmax, y, x
    ymax = slice.shape[0]
    xmax = slice.shape[1]

    # Erode image
    eroded_image_np = erode(slice_np)
    cdef DTYPE_t[:,:] eroded_image = eroded_image_np

    # Border image
    border_image_np = np.zeros((ymax,xmax),dtype=DTYPE)
    cdef DTYPE_t[:,:] border_image = border_image_np
    for y in range(ymax):
        for x in range(xmax):
            border_image[y,x] = slice[y,x]-eroded_image[y,x]
    return border_image_np.sum()


@cython.boundscheck(False)
@cython.wraparound(False)
def main(DTYPE_t[:,:] mask, int numobjects, Py_ssize_t[:,:] indices):

    # Memoryview of boundary pixels
    boundary_pixels_np = np.zeros(numobjects,dtype=DTYPE)
    cdef DTYPE_t[:] boundary_pixels = boundary_pixels_np

    # Loop through each object
    cdef Py_ssize_t y_from, y_to, x_from, x_to, i
    cdef DTYPE_t[:,:] slice
    for i in range(numobjects):
        y_from = indices[i,0]
        y_to = indices[i,1]
        x_from = indices[i,2]
        x_to = indices[i,3]
        slice = mask[y_from:y_to, x_from:x_to]
        boundary_pixels[i] = border_image(slice)

    return boundary_pixels_np

Memoryview是Cython的更新版本,旨在與原始np.ndarray語法相比進行改進。 因此,它們是首選。 但是,使用它通常不會產生太大差異。 以下是一些注意事項:

速度

對於速度這讓很少的差別-我的經驗是,作為memoryviews功能參數都或多或少地更慢,但它幾乎不值得擔心。

概論

Memoryview設計為可與具有Python緩沖區接口的任何類型一起使用(例如,標准庫array模塊)。 鍵入np.ndarray僅適用於numpy數組。 原則上,memorviews可以支持更大范圍的內存布局 ,這可以使與C代碼的接口更加容易(在實踐中,我從未真正看到這很有用)。

作為返回值

當從Cython返回數組以編碼Python時,用戶可能會更喜歡使用numpy數組而不是使用memoryview。 如果您正在使用memoryviews,則可以執行以下任一操作:

return np.asarray(mview)
return mview.base

易於編譯

如果您使用的是np.ndarray ,則必須在setup.py文件中使用np.get_include()設置包含目錄。 您不必使用memoryviews來執行此操作,這通常意味着您可以跳過setup.py而僅使用cythonize命令行命令或pyximport進行更簡單的項目。

並行化

與numpy數組相比,這是memoryviews的一優勢(如果要使用它)。 它不需要全局解釋器鎖來獲取memoryview的切片,但它需要一個numpy數組。 這意味着以下代碼大綱可以與memoryview並行工作:

cdef void somefunc(double[:] x) nogil:
     # implementation goes here

cdef double[:,:] 2d_array = np.array(...)
for i in prange(2d_array.shape[0]):
    somefunc(2d_array[i,:])

如果您不使用Cython的並行功能,則此方法不適用。

cdef

您可以將memoryviews用作cdef類的屬性,但不能np.ndarray 您可以(當然)改為使用numpy數組作為未類型化的object屬性。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM