Efficiently convert System.Single[,] to numpy array

前端 未结 3 1690
清酒与你
清酒与你 2020-12-17 01:29

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 a

相关标签:
3条回答
  • 2020-12-17 02:24

    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
    
    0 讨论(0)
  • 2020-12-17 02:24

    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:

    • customizing interop
    • Codecs: customizing object marshalling between .NET and Python
    0 讨论(0)
  • 2020-12-17 02:30

    @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
    
    0 讨论(0)
提交回复
热议问题