05-19-2024 04:11 AM - edited 05-19-2024 04:16 AM
Unfortunately DeAllocate still returns 108 for me.
@Greg
IMHO: The approach of using a Cluster doesn't bring you any advantage, since your 1D array also has it's length allocated just in front of it.
See 'Uint16Array1Base' in your header:
typedef struct {
int32_t dimSize;
uint16_t Numeric[1];
} Uint16Array1Base;
typedef Uint16Array1Base **Uint16Array1;
typedef struct {
Uint16Array1 Array;
int32_t NumRows;
int32_t NumCols;
} U162DArrayCluster;
So the Cluster with a 1D array is likely worse than using a 2D array directly, because of the additionals in your VI. My statement earlier about the possibility on zero copy was probably wrong. I forgot that LV 2D Array is not defined like:
typedef struct {
int32_t dimSizes[2];
uint16_t elt*;
} Uint16ArrayBase;
So if copying is not a showstopper, use LV Allocate and DeAllocate, defined in your genereted headers. Maybe rolfk knows why DeAllocation returns 108?
05-19-2024 08:31 AM
From initial question I didn't catch at all — if this DLL will be called from LabVIEW or from C?
In case if this DLL intendent for third-party environment, then probably I will not mix LabVIEW "managed" memory and external and then pass native pointers from C to LabVIEW code and call MoveBlock to copy to LabVIEW's array for manipulation.
For example, function to compute average of the U16 image could be like this:
And we don't need to deallocate LabVIEW's Array, because LabVIEW will take care automatically.
If we have source and destination, then it looks like this, for example, increment:
Then C code will be very simple and straight forward:
#include <ansi_c.h>
#include "SharedLib.h"
int main (int argc, char *argv[])
{
uint16_t *src, *dst;
int width = 240, height = 320;
src=(uint16_t *)malloc(width * height * sizeof(uint16_t));
dst = (uint16_t *)malloc(width * height * sizeof(uint16_t));
int sum = 0;
for (uint16_t row = 0; row < height; row++){
for (uint16_t col = 0; col < width; col++){
src[col + row * width ] = row + col;
sum += row + col;
}
}
float testAverage = (double)sum/(double)(width * height);
int ret = IncImage((int64_t)src, (int64_t)dst, width, height); //call from LabVIEW's DLL
float srcAverage = ImageAverage((int64_t)src, width, height);
float dstAverage = ImageAverage((int64_t)dst, width, height);
printf("\nDLL ret = %d; src_mean = %f, dst_mean = %f\n", ret, srcAverage, dstAverage);
if (testAverage == srcAverage) printf("Test passed"); else printf("Test failed");
free(src);
free(dst);
return 0;
}
Something like this. Just an idea...
05-19-2024 09:51 AM
Hi Andrey,
To clarify, I'd like the DLL to be called with their language of choice. (C, C++, Python, etc.) I will look into the MoveBlock functions which you have referred to, thank you!
05-19-2024 10:15 AM
@Gregory wrote:
Hi Andrey,
To clarify, I'd like the DLL to be called with their language of choice. (C, C++, Python, etc.) I will look into the MoveBlock functions which you have referred to, thank you!
OK, then I really recommend do not put LabVIEW Types into API Interface and keep everything as simple as possible.
This is for example, how ImageAverage can be called from Python Script:
import os
import ctypes
# Get the directory of the current script
script_dir = os.path.dirname(os.path.abspath(__file__))
# Construct the full path to the DLL
dll_path = os.path.join(script_dir, 'SharedLib.dll')
# Load the DLL
dll = ctypes.CDLL(dll_path)
# Define the function prototype
dll.ImageAverage.argtypes = [ctypes.POINTER(ctypes.c_ushort), ctypes.c_int, ctypes.c_int]
dll.ImageAverage.restype = ctypes.c_float
# Define the image dimensions
width = 320
height = 240
# Create an array of unsigned short values filled with a constant value (e.g., 1000)
image_data = (ctypes.c_ushort * (width * height))(*[1000] * (width * height))
# Call the ImageAverage function from the LabVIEW DLL
src_average = dll.ImageAverage(image_data, width, height)
print(f"The average value of the image is: {src_average}")
Demo project in the attachment.
05-19-2024 02:43 PM
Hi Andrey, thank you so much for the help. I have a few questions:
1. On the LabVIEW side, you have a call library function node which calls a library named "LabVIEW" and the function is "MoveBlock". Is there somewhere I can find more documentation about this library and function?
2. In the Python code it looks like you're passing in the full image data. But, I guess since you've defined the argtypes as a POINTER, Python knows to pass in a pointer rather than the actual data, is that correct?
05-19-2024 03:11 PM
@Gregory wrote:
Hi Andrey, thank you so much for the help. I have a few questions:
1. On the LabVIEW side, you have a call library function node which calls a library named "LabVIEW" and the function is "MoveBlock". Is there somewhere I can find more documentation about this library and function?
2. In the Python code it looks like you're passing in the full image data. But, I guess since you've defined the argtypes as a POINTER, Python knows to pass in a pointer rather than the actual data, is that correct?
NI Doc: the MoveBlock (LabVIEW Manager Function).
Python: How can I pass an array to shared library(.dll) written in c using python.Don't remember, where the docs, probably somewhere here: ctypes Arrays and Pointers.
05-19-2024 07:25 PM
@Quiztus2 wrote:
@Greg
IMHO: The approach of using a Cluster doesn't bring you any advantage, since your 1D array also has it's length allocated just in front of it.See 'Uint16Array1Base' in your header:
Good point, I think when the 1D array was outside the cluster / structure it was a native c-type. I'll have to test again when I'm at my development computer tomorrow.
05-19-2024 07:30 PM
Thanks Andrey. So when a user is using C or Python ctypes to create a 2D array, is the array guaranteed to be in a continuous space of memory?
05-19-2024 11:10 PM - edited 05-19-2024 11:13 PM
@Gregory wrote:
Thanks Andrey. So when a user is using C or Python ctypes to create a 2D array, is the array guaranteed to be in a continuous space of memory?
Yes, they are fundamentally contiguous. In theory, the layout of the 2D array in memory depends on the ordering used, which can be either row-major or column-major, but column-major ordering is rarely used (in Fortran, as far as I remember). Also, for 16-bit images, they could be stored as big-endian or little-endian, but by default on Windows at least, they matched perfectly between C, Python, and LabVIEW.For high-performance image processing, alignment gaps can be introduced (meaning each row will start at an aligned address, by default nowadays aligned to a 64-byte boundary). Then, together with the ImageWidth, you will have a "LineWidth", but this is a special case. IMAQ Vision, as well as Intel Performance Primitives, will allocate image data in this manner, but OpenCV will allocate continuous memory by default. This is also not your case.
05-20-2024 10:02 AM
Unfortunately I was totally unaware of the LABVIEW MANAGER FUNCTIONs. Thank you for sharing.