6

Using Python 3.6 and Python for dotNET/pythonnet I have manged to get hold of an image array. This is of type System.Single[,]

I'd like to convert that to a numpy array so that I can actually do something with it in Python. I've set up a function to step through that array and convert it elementwise - but is there something more sensible (and faster) that I could use?

def MeasurementArrayToNumpy(TwoDArray):
    hBound = TwoDArray.GetUpperBound(0)
    vBound = TwoDArray.GetUpperBound(1)

    resultArray = np.zeros([hBound, vBound])

    for c in range(TwoDArray.GetUpperBound(0)):            
            for r in range(TwoDArray.GetUpperBound(1)):
                resultArray[c,r] = TwoDArray[c,r]
    return resultArray
6
  • Have you tried numpy.array(TwoDArray)? Commented May 11, 2017 at 8:58
  • Yes, I did give that a go. It returns: array(<System.Single[,] object at 0x0000000011501438>, dtype=object) Commented May 11, 2017 at 9:00
  • Then it apparently doesn't implement iteration in a way NumPy understands it. I guess what you're doing is already the best it can do. Commented May 11, 2017 at 9:03
  • you can start by reading this mailing list thread: mail.python.org/pipermail/pythondotnet/2014-May/001526.html Commented May 13, 2017 at 5:20
  • 1
    you may find few extremely efficient methods here: github.com/pythonnet/pythonnet/issues/514 Commented Feb 2, 2018 at 15:19

3 Answers 3

7

@denfromufa - that is a very useful link.

The suggestion there is to do a direct memory copy, either using Marshal.Copy or np.frombuffer. I couldn't manage to get the Marshal.Copy version working - some shenanigans are required to use a 2D array with Marshal and that changed the contents of of the array somehow - but the np.frombuffer version seems to work for me and reduced the time to complete by a factor of ~16000 for a 3296*2471 array (~25s -> ~1.50ms). This is good enough for my purposes

The method requires a couple more imports, so I've included those in the code snippet below

import ctypes
from System.Runtime.InteropServices import GCHandle, GCHandleType

def SingleToNumpyFromBuffer(TwoDArray):
    src_hndl = GCHandle.Alloc(TwoDArray, GCHandleType.Pinned)

    try:
        src_ptr = src_hndl.AddrOfPinnedObject().ToInt32()
        bufType = ctypes.c_float*len(TwoDArray)
        cbuf = bufType.from_address(src_ptr)
        resultArray = np.frombuffer(cbuf, dtype=cbuf._type_)
    finally:
        if src_hndl.IsAllocated: src_hndl.Free()
    return resultArray
Sign up to request clarification or add additional context in comments.

3 Comments

I'm glad that this worked for you! Feel free to contribute this as wiki to the project.
I am looking for a way of checking the type of the c# array, but found no way to safely achiev this. Any hints?
I found using TwoDArray.GetType().ToString() usefull for dynamic type checking.
1

Following denfromufa's link, I think Robert McLeod offers the best solution. He also points out a draw back of using np.frombuffer:

one can do a zero-copy with np.frombuffer but then you have a mess of memory manged both by Python's garbage collector and C#'s garbage collector.

Robert McLeod's snippet from the github issue:

import numpy as np
import ctypes
import clr, System
from System import Array, Int32
from System.Runtime.InteropServices import GCHandle, GCHandleType

_MAP_NP_NET = {
    np.dtype('float32'): System.Single,
    np.dtype('float64'): System.Double,
    np.dtype('int8')   : System.SByte,
    np.dtype('int16')  : System.Int16,
    np.dtype('int32')  : System.Int32,
    np.dtype('int64')  : System.Int64,
    np.dtype('uint8')  : System.Byte,
    np.dtype('uint16') : System.UInt16,
    np.dtype('uint32') : System.UInt32,
    np.dtype('uint64') : System.UInt64,
    np.dtype('bool')   : System.Boolean,
}
_MAP_NET_NP = {
    'Single' : np.dtype('float32'),
    'Double' : np.dtype('float64'),
    'SByte'  : np.dtype('int8'),
    'Int16'  : np.dtype('int16'), 
    'Int32'  : np.dtype('int32'),
    'Int64'  : np.dtype('int64'),
    'Byte'   : np.dtype('uint8'),
    'UInt16' : np.dtype('uint16'),
    'UInt32' : np.dtype('uint32'),
    'UInt64' : np.dtype('uint64'),
    'Boolean': np.dtype('bool'),
}

def asNumpyArray(netArray):
    '''
    Given a CLR `System.Array` returns a `numpy.ndarray`.  See _MAP_NET_NP for 
    the mapping of CLR types to Numpy dtypes.
    '''
    dims = np.empty(netArray.Rank, dtype=int)
    for I in range(netArray.Rank):
        dims[I] = netArray.GetLength(I)
    netType = netArray.GetType().GetElementType().Name

    try:
        npArray = np.empty(dims, order='C', dtype=_MAP_NET_NP[netType])
    except KeyError:
        raise NotImplementedError("asNumpyArray does not yet support System type {}".format(netType) )

    try: # Memmove 
        sourceHandle = GCHandle.Alloc(netArray, GCHandleType.Pinned)
        sourcePtr = sourceHandle.AddrOfPinnedObject().ToInt64()
        destPtr = npArray.__array_interface__['data'][0]
        ctypes.memmove(destPtr, sourcePtr, npArray.nbytes)
    finally:
        if sourceHandle.IsAllocated: sourceHandle.Free()
    return npArray

def asNetArray(npArray):
    '''
    Given a `numpy.ndarray` returns a CLR `System.Array`.  See _MAP_NP_NET for 
    the mapping of Numpy dtypes to CLR types.

    Note: `complex64` and `complex128` arrays are converted to `float32` 
    and `float64` arrays respectively with shape [m,n,...] -> [m,n,...,2]
    '''
    dims = npArray.shape
    dtype = npArray.dtype
    # For complex arrays, we must make a view of the array as its corresponding 
    # float type.
    if dtype == np.complex64:
        dtype = np.dtype('float32')
        dims.append(2)
        npArray = npArray.view(np.float32).reshape(dims)
    elif dtype == np.complex128:
        dtype = np.dtype('float64')
        dims.append(2)
        npArray = npArray.view(np.float64).reshape(dims)

    netDims = Array.CreateInstance(Int32, npArray.ndim)
    for I in range(npArray.ndim):
        netDims[I] = Int32(dims[I])
    
    if not npArray.flags.c_contiguous:
        npArray = npArray.copy(order='C')
    assert npArray.flags.c_contiguous

    try:
        netArray = Array.CreateInstance(_MAP_NP_NET[dtype], netDims)
    except KeyError:
        raise NotImplementedError("asNetArray does not yet support dtype {}".format(dtype))

    try: # Memmove 
        destHandle = GCHandle.Alloc(netArray, GCHandleType.Pinned)
        sourcePtr = npArray.__array_interface__['data'][0]
        destPtr = destHandle.AddrOfPinnedObject().ToInt64()
        ctypes.memmove(destPtr, sourcePtr, npArray.nbytes)
    finally:
        if destHandle.IsAllocated: destHandle.Free()
    return netArray

if __name__ == '__main__':
    from time import perf_counter
    import matplotlib.pyplot as plt
    import psutil

    tries = 1000
    foo = np.full([1024,1024], 2.5, dtype='float32')


    netMem = np.zeros(tries)
    t_asNet = np.zeros(tries)
    netFoo = asNetArray( foo ) # Lazy loading makes the first iteration very slow
    for I in range(tries):
        t0 = perf_counter()
        netFoo = asNetArray( foo )
        t_asNet[I] = perf_counter() - t0
        netMem[I] = psutil.virtual_memory().free / 2.0**20

    t_asNumpy = np.zeros(tries)
    numpyMem = np.zeros(tries)
    unNetFoo = asNumpyArray( netFoo ) # Lazy loading makes the first iteration very slow
    for I in range(tries):
        t0 = perf_counter()
        unNetFoo = asNumpyArray( netFoo )
        t_asNumpy[I] = perf_counter() - t0
        numpyMem[I] = psutil.virtual_memory().free / 2.0**20

    # Convert times to milliseconds
    t_asNet *= 1000
    t_asNumpy *= 1000
    np.testing.assert_array_almost_equal( unNetFoo, foo )
    print( "Numpy to .NET converted {} bytes in {:.3f} +/- {:.3f} ms (mean: {:.1f} ns/ele)".format( \
        foo.nbytes, t_asNet.mean(), t_asNet.std(), t_asNet.mean()/foo.size*1e6 ) )
    print( ".NET to Numpy converted {} bytes in {:.3f} +/- {:.3f} ms (mean: {:.1f} ns/ele)".format( \
        foo.nbytes, t_asNumpy.mean(), t_asNumpy.std(), t_asNumpy.mean()/foo.size*1e6 ) )

    plt.figure()
    plt.plot(np.arange(tries), netMem, '-', label='asNetArray')
    plt.plot(np.arange(tries), numpyMem, '-', label='asNumpyArray')
    plt.legend(loc='best')
    plt.ylabel('Free memory (MB)')
    plt.xlabel('Iteration')
    plt.show(block=True)

It is also worth noting that pythonnet has a new experimental feature, which seems promising: Codecs. Only relevant if you build from source and manage to figure out the documentation:

Comments

0

I modified rbp109 function so that it can be used with RGB images of the type System.Int32[,,]. The resulting numpy array is then re-shaped so that the image can be displayed in a opencv window

def net2Numpy(net_img,width,height):  

    src_hndl = GCHandle.Alloc(net_img, GCHandleType.Pinned)
    try:
        src_ptr = src_hndl.AddrOfPinnedObject().ToInt32()
        bufType = ctypes.c_int*len(net_img)
        cbuf = bufType.from_address(src_ptr)
        resultArray = np.frombuffer(cbuf, dtype=cbuf._type_)
    finally:
        if src_hndl.IsAllocated: src_hndl.Free()

    resultArray = resultArray.astype(dtype=np.uint8)
    resultArray = resultArray.reshape((height,width,3),order='C')

    return resultArray

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.