IC-Imaging-Control-Samples icon indicating copy to clipboard operation
IC-Imaging-Control-Samples copied to clipboard

IC3D SDK example for Python?

Open nexus1203 opened this issue 2 years ago • 9 comments

Is there a python example for IC3DSDK? I am using IC 3D to calibrate a pair of Imaging Source camera. The calibration results are great and I have generated the calibration xml file. Now I want to use these files in python to get XYZ cordinates and disparity maps from left and right images. currently, I am not looking into a live stream solution rather just a offline example where I can load left and right images and pass it the IC3D sdk and return the relevant XYZ coordinates and disparity maps. I see there is a dll file and a ic3d.h file. Any way I could use them to import the functions to python, similar to IC capture sdk?

Any help on this would be much appreciated. Thank you.

nexus1203 avatar Dec 06 '21 06:12 nexus1203

Hello

I am very sorry, but there is no Python sample for the IC3DSK. I think, you can import the libic3d.dll with the Python ctypes module. The download of the IC 3D SDK is at https://www.theimagingsource.com/support/downloads-for-windows/software-development-kits-sdks/ic3dsdk/ You must create the structs passed to and received from the functions of the DLL in Python, similar as I did for the tisgrabber DLL at https://github.com/TheImagingSource/IC-Imaging-Control-Samples/tree/master/Python/tisgrabber.

I hope, this helps a little bit. The IC 3D SDK is available for download, but we do not support or develope it anymore.

Stefan

TIS-Stefan avatar Dec 06 '21 09:12 TIS-Stefan

Thank you for your help. I have tried as you mentioned about the ctypes, however the sdk return error while reading the xml file. I have tried several ways to reformat the way I input the file path the error persists. There's also no source code for the .dll that I can take a look into to debug. If you could help with loading the files and data format part, that would be very helpful.

Thank you again for your help.

import ctypes, os
from enum import Enum

abs_path_to_directory = os.path.abspath(os.path.dirname(__file__))

# add the directory to the os PATH
print(abs_path_to_directory)
os.environ['PATH'] = abs_path_to_directory + os.pathsep + os.environ['PATH']


ic3d = ctypes.cdll.LoadLibrary("libic3d.dll")


class ic3d_status(Enum):
    IC3D_SUCCESS = 0
    IC3D_FILE_TO_ERROR = 1
    IC3D_MEMORY_ALLOCATION_ERROR = 2
    IC3D_INVALID_POINTER_ERROR = 3
    IC3D_MATCHER_INIT_ERROR = 4
    IC3D_INPUT_DIMENSION_ERROR = 5
    IC3D_INPUT_FORMAT_ERROR = 6
    IC3D_MATCHER_NOT_VALID = 7
    IC3D_ERROR = 8

class ic3d_image_format(Enum):
    IC3D_IMG_INVALID_FORMAT = 0
    IC3D_IMG_Y800 = 1   # An single channel (8-bit) grayscale image
    IC3D_IMG_RGB32 = 2   # A four channel RGBA image (8-bit per channel)
    IC3D_IMG_XYZA32 = 3  # A four channel floating point image (32-bit per channel)


class ic3d_matcher_type(Enum):
    IC3D_CPU_MATCHER = 0
    IC3D_CUDA_MATCHER = 1

class ic3d_origin_type(Enum):
    IC3D_ORIGIN_CALIB = 0
    IC3D_ORIGIN_LEFT_CAM = 1
    IC3D_ORIGIN_RIGHT_CAM = 2
    
    
class ic3d_image:
    def __init__(self, img, img_format):
        self.data = ctypes.c_ubyte(img.data)
        self.img_format = ctypes.c_int(img_format)
        self.width = ctypes.c_intimg.shape[0]
        self.height = ctypes.c_intimg.shape[1]
        
img1 =cv2.imread('cam0_3.bmp', 0)
img2 =cv2.imread('cam1_3.bmp', 0)      



c = ctypes.POINTER(ctypes.POINTER(context))
status = ic3d.ic3d_init_context()
print(ic3d_status(status.value))

ic3d.ic3d_load_calibration.argtype = ctypes.c_char_p

# this part doesn't work
status = ic3d.ic3d_load_calibration(b"C:\\Users\\ASUS\Desktop\\3dCalib\\tis3d\\rig_calibration.xml")
print(ic3d_status(status))

the returned error is like this:

ic3d_status.IC3D_SUCCESS
IC3D: Could not load H+詡$ H
ic3d_status.IC3D_FILE_TO_ERROR

nexus1203 avatar Dec 07 '21 02:12 nexus1203

Hello

You may need to to the same, as I do in tisgrabber.py:

status = ic3d.ic3d_load_calibration("C:\\Users\\ASUS\Desktop\\3dCalib\\tis3d\\rig_calibration.xml".encode("utf-8"))

Because the "H+詡$ H" looks for a wrongly interpreted unicode string.

Stefan

TIS-Stefan avatar Dec 07 '21 14:12 TIS-Stefan

Thank you again, after studying the header file and your tisgrabber.py, I fix that issue this way. Now the string values are interpreted correctly however the code runs into a OSError: access violation reading 0xFFFFFFFFFFFFFFFF

The Code is as follow:

import numpy as np
import cv2
import ctypes, os
from enum import Enum

abs_path_to_directory = os.path.abspath(os.path.dirname(__file__))

# add the directory to the os PATH
print(abs_path_to_directory)
os.environ['PATH'] = abs_path_to_directory + os.pathsep + os.environ['PATH']
ic3d = ctypes.cdll.LoadLibrary("libic3d.dll")



class ic3d_status(Enum):
    IC3D_SUCCESS = 0
    IC3D_FILE_TO_ERROR = 1
    IC3D_MEMORY_ALLOCATION_ERROR = 2
    IC3D_INVALID_POINTER_ERROR = 3
    IC3D_MATCHER_INIT_ERROR = 4
    IC3D_INPUT_DIMENSION_ERROR = 5
    IC3D_INPUT_FORMAT_ERROR = 6
    IC3D_MATCHER_NOT_VALID = 7
    IC3D_ERROR = 8

class ic3d_image_format(Enum):
    IC3D_IMG_INVALID_FORMAT = 0
    IC3D_IMG_Y800 = 1   # An single channel (8-bit) grayscale image
    IC3D_IMG_RGB32 = 2   # A four channel RGBA image (8-bit per channel)
    IC3D_IMG_XYZA32 = 3  # A four channel floating point image (32-bit per channel)


class ic3d_matcher_type(Enum):
    IC3D_CPU_MATCHER = 0
    IC3D_CUDA_MATCHER = 1

class ic3d_origin_type(Enum):
    IC3D_ORIGIN_CALIB = 0
    IC3D_ORIGIN_LEFT_CAM = 1
    IC3D_ORIGIN_RIGHT_CAM = 2
    
    
class ic3d_image:
    def __init__(self, img, img_format):
        self.data = ctypes.c_ubyte(img.data)
        self.img_format = ctypes.c_int(img_format)
        self.width = ctypes.c_intimg.shape[0]
        self.height = ctypes.c_intimg.shape[1]

# assign function to python objects
start = ic3d.ic3d_init_context
load_file = ic3d.ic3d_load_calibration

# define the argtypes and restypes
start.argtypes = [ctypes.POINTER(ctypes.c_void_p)]
start.restype = ctypes.c_long
load_file.argtypes = [ctypes.POINTER(ctypes.c_void_p), ctypes.c_char_p]
load_file.restype = ctypes.c_long

Handle = ctypes.c_void_p() # create a Handle to the context

# prepare the file path
fpath = ctypes.c_char_p("rig_calibration_rect.xml".encode('utf-8'))
filepath = ctypes.create_string_buffer(1000)
ctypes.memmove(filepath, fpath, len("rig_calibration_rect.xml"))

"""This method of filename works too but returns the same error code IC3D_FILE_TO_ERROR"""
# filepath = ctypes.c_char_p("rig_calibration_rect.xml".encode('utf-8'))

status = start(ctypes.byref(Handle)) 
print(ic3d_status(status))

status2 =load_file(Handle,filepath)  # this is where error happens
print(ic3d_status(status2))

The error

Traceback (most recent call last):
  File "c:\Users\ASUS\Desktop\3dCalib\tis3d\tis_stereo.py", line 85, in <module>
    status2 =load_file(Handle,filepath)
OSError: exception: access violation reading 0xFFFFFFFFFFFFFFFF

The example code runs in C++ with visual studio 2019 compiler without any errors. To my surprise, the unicode is parsed correctly by the DLL since when I change the filepath to an incorrect destination like,

# prepare the file path
fpath = ctypes.c_char_p("abc.xml".encode('utf-8'))

it returns following error:

<ctypes.c_char_Array_1000 object at 0x00000226C6612BC0>
ic3d_status.IC3D_SUCCESS
IC3D: Could not load abc.xml
ic3d_status.IC3D_FILE_TO_ERROR

#Edit: seems like the code is working when context is passed this way. I will keep working on this method and try to implement other functions.


class Handle(ctypes.Structure):
    '''
    This class is used to handle the pointer to the internal 
    context class, which contains the context. 
    A pointer to this class is used by ic3d DLL.
    '''
    _fields_ = [('unused', ctypes.c_void_p)]

start.argtypes = [ctypes.POINTER(Handle)]
start.restype = ctypes.c_long
# load_file.argtypes = [ctypes.POINTER(Handle), ctypes.c_char_p]  #--> by not defining this part the code below works
load_file.restype = ctypes.c_long


"""This method of filename works IC3D_FILE_TO_ERROR"""
filepath = ctypes.c_char_p("rig_calivbration_rect.xml".encode('utf-8'))

Handler = Handle()
print(filepath)
status = start(Handler)
print('1',ic3d_status(status))

status2 = load_file(Handler,"rig_calibration_rect.xml".encode('utf-8'))
print('2',ic3d_status(status2))

and returns:

1 ic3d_status.IC3D_SUCCESS
2 ic3d_status.IC3D_SUCCESS

Thanks a lot again for your guidance.

nexus1203 avatar Dec 07 '21 15:12 nexus1203

Good to read, you got it working now. The fields = [('unused', ctypes.c_void_p)] is similar to what I have in tisgrabber.py.

Stefan

TIS-Stefan avatar Dec 07 '21 16:12 TIS-Stefan

I forgot to mention, we no longer support and develope the IC 3D software.

Stefan

TIS-Stefan avatar Dec 07 '21 16:12 TIS-Stefan

Thank you for all the help you provided. I have one last question if you may be able to help me.

in c++ there is a method for converting cv2 :: MAT to ic3d image and from ic3d to cv2.

void cv_as_ic3d(const cv::Mat &src,
                ic3d_image *dst)
{
    ic3d_image_format ic3d_format;

    auto src_type = src.type();

    switch(src_type) {

    case CV_8UC1:
        ic3d_format = IC3D_IMG_Y800;
        break;

    case CV_8UC4:
        ic3d_format = IC3D_IMG_RGB32;
        break;

    case CV_32FC4:
        ic3d_format = IC3D_IMG_XYZA32;
        break;

    default:
        ic3d_format = IC3D_IMG_INVALID_FORMAT;
        break;

    }

    if(ic3d_format == IC3D_IMG_INVALID_FORMAT) {
        return;
    }


    dst->data = (unsigned char *)src.data;
    dst->width = src.cols;
    dst->height = src.rows;
    dst->format = ic3d_format;

    return;
}

void ic3d_as_cv(const ic3d_image *src,
                cv::Mat &dst)
{
    int cv_format;

    if(src->format == IC3D_IMG_Y800) {
        cv_format = CV_8UC1;
    }
    else if(src->format == IC3D_IMG_RGB32) {
        cv_format = CV_8UC4;
    }
    else if(src->format == IC3D_IMG_XYZA32) {
        cv_format = CV_32FC4;
    }
    else {
        return;
    }

    dst = cv::Mat(src->height, src->width, cv_format, src->data);

    return;
}

I am trying to convert the python numpy array representation to the IC3D format as follow. but I am getting IC3D_INPUT_FORMAT_ERROR

import numpy as np

# image formats for to and from i3d functions
class ic3d_image(ctypes.Structure):
    ''' The structure of ic3D image'''
    _fields_ = [('data', ctypes.POINTER(ctypes.c_ubyte)),
                ('width', ctypes.c_int),
                ('height', ctypes.c_int),
                ('format', ctypes.c_int)]

def cv2_to_ic3d(cv2_img:np.ndarray):
    '''
    Convert a cv2 image to ic3d image.
    '''
    # convert the image to ic3d image
    ic3d_img = ic3d_image()
    ic3d_img.width = cv2_img.shape[1]
    ic3d_img.height = cv2_img.shape[0]
    ic3d_img.format = IC3D_IMG_RGB32
    ic3d_img.data = ctypes.cast(cv2_img.ctypes.data, ctypes.POINTER(ctypes.c_ubyte))
   
    return ic3d_img


def ic3d_to_cv2(ic3d_img:ic3d_image):
    '''
    Convert a ic3d image to cv2 image.
    '''
    # convert the image to cv2 image
    cv2_img = np.ndarray(shape=(ic3d_img.height, ic3d_img.width, 4), dtype=np.uint8, buffer=ic3d_img.data)
    return cv2_img

nexus1203 avatar Dec 08 '21 10:12 nexus1203

I am very sorry, but here I am not able to help at all. I can convert a GstBuffer to numpy and numpy to QPixmap. But nothing else. I simply do not know, how to do that with Python. You are sure, you have a 32 bit image? Stefan

TIS-Stefan avatar Dec 08 '21 10:12 TIS-Stefan

I am sure I have a 32bit image (RGBA) as shown in functions. Actually never mind. I would just gonna leave this to be. Again thanks a lot for your help.

nexus1203 avatar Dec 08 '21 11:12 nexus1203