Scripting Ximea machine vision cameras in IronPython

Ximea machine vision cameras are handled via XiAPI. The API is implemented for the .NET platform. In this article I'll present a IronPython scripts that will use the .NET API to control the Ximea xiQ camera.

IronPython is a Python implementation on the .NET platform and as such it can use any .NET libraries and resources.

At start you have to install the software package with the drivers and libraries. Everything will land in "Ximea" folder located on the primary system partition (C:). It will contain a subfolder called "libraries" with xiApi.NET.dll and m3api.dll. Aside of that we need also IronPython. To script my xiQ MQ013MG-E2 camera I copyed those two mentioned DLL files into an empty folder. I've also copyed ipy.exe - IronPython executable to get quick and fast way to launch IronPython scripts from terminal (I should add the original IronPython executable path into system paths).

A basic script that saves one frame looks like so:
#-*- coding: utf-8 -*-
import clr
import System

clr.AddReference("xiApi.NET.dll")
clr.AddReference("System.Drawing")

import System.Drawing
from System.Drawing.Imaging import PixelFormat

from xiApi.NET import *

cam = xiCam()
cam.OpenDevice(0)
cam.SetParam(PRM.BUFFER_POLICY, BUFF_POLICY.SAFE)
cam.SetParam(PRM.EXPOSURE, 9000)
cam.SetParam(PRM.GAIN, 10.0)
cam.SetParam(PRM.IMAGE_DATA_FORMAT, IMG_FORMAT.MONO8)

fileobj = System.Drawing.Bitmap(1280, 1024, PixelFormat.Format8bppIndexed)
cam.StartAcquisition()
cam.GetImage(fileobj, 1000)
fileobj.Save("a.bmp")
cam.StopAcquisition()

cam.CloseDevice()

At start I load the XiAPI and System.Drawing to get BMP (Bitmap) support required for te capture process. After that I initialise the camera using xiCam class - I set the exposure time to 9 ms, gain to 10 dB and I also set the image format to 8-bit mono (it's a mono camera). GetImage expects a Bitmap object with correct sizes and format. All methods are described in the documentation.

GetParam can be used to get some informations from the camera.

#-*- coding: utf-8 -*-

import clr
import System

clr.AddReference("xiApi.NET.dll")

from xiApi.NET import *

strongBox = clr.Reference[str]()

cam = xiCam()
cam.OpenDevice(0)
cam.GetParam(PRM.DEVICE_NAME, strongBox)
print strongBox
cam.GetParam(PRM.DEVICE_TYPE, strongBox)
print strongBox
cam.GetParam(PRM.WIDTH, strongBox)
print strongBox
cam.GetParam(PRM.HEIGHT, strongBox)
print strongBox
cam.GetParam(PRM.AVAILABLE_BANDWIDTH, strongBox)
print strongBox
cam.GetParam(PRM.CHIP_TEMP, strongBox)
print strongBox
cam.GetParam(PRM.API_VERSION, strongBox)
print strongBox
cam.GetParam(PRM.DRV_VERSION, strongBox)
print strongBox

cam.CloseDevice()

GetParam as a second parameter expects a System.Runtime.CompilerServices.StrongBox instance which holds a reference to a value.

Saving one frame isn't much useful, so I wrote a class for imaging that would be easy to use from a command line:

#-*- coding: utf-8 -*-
import clr
import System

clr.AddReference("xiApi.NET.dll")
clr.AddReference("System.Drawing")

import System.Drawing
from System.Drawing.Imaging import PixelFormat
from System.IO import Directory, Path
from xiApi.NET import *

class XimeaImager(object):
    def __init__(self, exposure_time, gain, frames_count, hdr, folder):
        self.exposure_time = exposure_time
        self.gain = gain
        self.hdr = hdr
        self.frames_count = frames_count
        self.folder = folder
        self._perform()

    def _perform(self):
        self._setup_capture_folder()
        self._setup_device()
        if self.hdr:
            self._set_hdr_exposure()
        else:
            self._set_exposure()
        self._capture_frames()
        self._finish_acquisition()

    def _setup_capture_folder(self):
        current_folder = Directory.GetCurrentDirectory()
        capture_folder = Path.Combine(current_folder, self.folder)
        if not Directory.Exists(capture_folder):
            Directory.CreateDirectory(capture_folder)
        self.capture_folder = capture_folder

    def _setup_device(self):
        self.camera = xiCam()
        self.camera.OpenDevice(0)
        self.camera.SetParam(PRM.BUFFER_POLICY, BUFF_POLICY.SAFE)
        self.camera.SetParam(PRM.IMAGE_DATA_FORMAT, IMG_FORMAT.MONO8)
        self.camera.SetParam(PRM.GAIN, self.gain)

    def _set_exposure(self):
        self.camera.SetParam(PRM.EXPOSURE, self.exposure_time)

    def _set_hdr_exposure(self):
        self.camera.SetParam(PRM.HDR, 1)
        self.camera.SetParam(PRM.HDR_T1, self.exposure_time * 10)
        self.camera.SetParam(PRM.HDR_T2, self.exposure_time * 5)
        self.camera.SetParam(PRM.HDR_T3, self.exposure_time)

    def _capture_frames(self):
        self.camera.StartAcquisition()
        for frame in range(0, self.frames_count):
            bmp = self._get_bmp_frame()
            self.camera.GetImage(bmp, 1000)
            self._save_frame(bmp, frame)
        self.camera.StopAcquisition()

    def _get_bmp_frame(self):
        return System.Drawing.Bitmap(1280, 1024, PixelFormat.Format8bppIndexed)

    def _save_frame(self, bmp, frame_index):
        filename = 'frame%04d.bmp' % frame_index
        filepath = Path.Combine(self.capture_folder, filename)
        bmp.Save(filepath)

    def _finish_acquisition(self):
        self.camera.CloseDevice()

To capture a batch of frames into a subfolder only a class instance is needed, like XimeaImager(9000, 10, 100, False, 'test'). I've also added HDR support which merges 3 different exposures into one image with better dynamic range. The exposure time ratio is the same as in default values described in the documentation.

Note that directory and path operations are done by .NET classes. In IronPython you use the .NET API and not what you would use in Python. IronPython scripting allows quick testing and validating of imaging setups and cameras. It's not Python, you need to know a bit of .NET API, but still you can jump on it without being a .NET developer.

As for imaging with this camera - you may want to try my second article about this camera and astrophotography.

Comment article