Last active
January 10, 2020 13:16
-
-
Save danielballan/963d5e3078e67235addb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Daniel B. Allan | |
# August 2014 | |
# Robert L. Leheny Lab, JHU Dept. of Physics & Astronomy | |
""" | |
Capture an image from a NI frame grabber. | |
Adapted from code posted by Oliver W. at | |
http://stackoverflow.com/questions/17095359 | |
This assumes a monochromatic camera. See the source link | |
for discussion on capturing color images (some code for | |
which remains, unused, in this implementation). | |
This is tested on an NI PCIe-1433 running LabView 2014 | |
on Windows 7 32-bit with an IO Industries Flare 4M camera. | |
""" | |
from __future__ import division, print_function | |
import ctypes as C | |
import ctypes.util as Cutil | |
import time | |
imgbuffer = None | |
imgbuffer_vpp = None | |
# useful references: | |
# location of the niimaq.h: C:\Program Files (x86)\National Instruments\NI-IMAQ\Include | |
# location of the camera files: C:\Users\Public\Documents\National Instruments\NI-IMAQ\Data | |
# check it C:\Users\Public\Documents\National Instruments\NI-IMAQ\Examples\MSVC\Color\BayerDecode | |
class IMAQError(Exception): | |
"""A class for errors produced during the calling of National Intrument's IMAQ functions. | |
It will also produce the textual error message that corresponds to a specific code.""" | |
def __init__(self, code): | |
self.code = code | |
text = C.c_char_p('') | |
imaq.imgShowError(code, text) | |
self.message = "{}: {}".format(self.code, text.value) | |
# Call the base class constructor with the parameters it needs | |
Exception.__init__(self, self.message) | |
def imaq_error_handler(code): | |
"""Print the textual error message that is associated with the error code.""" | |
if code < 0: | |
raise IMAQError(code) | |
free_associated_resources = 1 | |
imaq.imgSessionStopAcquisition(sid) | |
imaq.imgClose(sid, free_associated_resources) | |
imaq.imgClose(iid, free_associated_resources) | |
else: | |
return code | |
imaqlib_path = Cutil.find_library('imaq') | |
imaq = C.windll.LoadLibrary(imaqlib_path) | |
imaq_function_list = [ # this is not an exhaustive list, merely the ones used in this program | |
imaq.imgGetAttribute, | |
imaq.imgInterfaceOpen, | |
imaq.imgSessionOpen, | |
imaq.niimaquDisable32bitPhysMemLimitEnforcement, # because we're running on a 64-bit machine with over 3GB of RAM | |
imaq.imgCreateBufList, | |
imaq.imgCreateBuffer, | |
imaq.imgSetBufferElement, | |
imaq.imgSnap, | |
imaq.imgSessionSaveBufferEx, | |
imaq.imgSessionStopAcquisition, | |
imaq.imgClose, | |
imaq.imgCalculateBayerColorLUT, | |
imaq.imgBayerColorDecode ] | |
# for all imaq functions we're going to call, we should specify that if they | |
# produce an error (a number), we want to see the error message (textually) | |
for func in imaq_function_list: | |
func.restype = imaq_error_handler | |
def initialize(interface_name='img0'): | |
global imgbuffer | |
global imgbuffer_vpp | |
INTERFACE_ID = C.c_uint32 | |
SESSION_ID = C.c_uint32 | |
BUFLIST_ID = C.c_uint32 | |
iid = INTERFACE_ID(0) | |
sid = SESSION_ID(0) | |
bid = BUFLIST_ID(0) | |
array_16bit = 2**16 * C.c_uint32 | |
redLUT, greenLUT, blueLUT = [ array_16bit() for _ in range(3) ] | |
red_gain, blue_gain, green_gain = [ C.c_double(val) for val in (1., 1., 1.) ] | |
# OPEN A COMMUNICATION CHANNEL WITH THE CAMERA | |
# our camera has been given its proper name in Measurement & Automation Explorer (MAX) | |
imaq.imgInterfaceOpen(interface_name, C.byref(iid)) | |
imaq.imgSessionOpen(iid, C.byref(sid)); | |
# START C MACROS DEFINITIONS | |
# define some C preprocessor macros (these are all defined in the niimaq.h file) | |
_IMG_BASE = 0x3FF60000 | |
IMG_BUFF_ADDRESS = _IMG_BASE + 0x007E # void * | |
IMG_BUFF_COMMAND = _IMG_BASE + 0x007F # uInt32 | |
IMG_BUFF_SIZE = _IMG_BASE + 0x0082 #uInt32 | |
IMG_CMD_STOP = 0x08 # single shot acquisition | |
IMG_ATTR_ROI_WIDTH = _IMG_BASE + 0x01A6 | |
IMG_ATTR_ROI_HEIGHT = _IMG_BASE + 0x01A7 | |
IMG_ATTR_BYTESPERPIXEL = _IMG_BASE + 0x0067 | |
IMG_ATTR_COLOR = _IMG_BASE + 0x0003 # true = supports color | |
IMG_ATTR_PIXDEPTH = _IMG_BASE + 0x0002 # pix depth in bits | |
IMG_ATTR_BITSPERPIXEL = _IMG_BASE + 0x0066 # aka the bit depth | |
IMG_BAYER_PATTERN_GBGB_RGRG = 0 | |
IMG_BAYER_PATTERN_GRGR_BGBG = 1 | |
IMG_BAYER_PATTERN_BGBG_GRGR = 2 | |
IMG_BAYER_PATTERN_RGRG_GBGB = 3 | |
# END C MACROS DEFINITIONS | |
width, height = C.c_uint32(), C.c_uint32() | |
has_color, pixdepth, bitsperpixel, bytes_per_pixel = [ C.c_uint8() for _ in range(4) ] | |
# poll the camera (or is it the camera file (icd)?) for these attributes and store them in the variables | |
for var, macro in [ (width, IMG_ATTR_ROI_WIDTH), | |
(height, IMG_ATTR_ROI_HEIGHT), | |
(bytes_per_pixel, IMG_ATTR_BYTESPERPIXEL), | |
(pixdepth, IMG_ATTR_PIXDEPTH), | |
(has_color, IMG_ATTR_COLOR), | |
(bitsperpixel, IMG_ATTR_BITSPERPIXEL) ]: | |
imaq.imgGetAttribute(sid, macro, C.byref(var)) | |
print("Image ROI size: {} x {}".format(width.value, height.value)) | |
print("Pixel depth: {}\nBits per pixel: {} -> {} bytes per pixel".format( | |
pixdepth.value, | |
bitsperpixel.value, | |
bytes_per_pixel.value)) | |
bufsize = width.value*height.value*bytes_per_pixel.value | |
imaq.niimaquDisable32bitPhysMemLimitEnforcement(sid) | |
# create the buffer (in a list) | |
imaq.imgCreateBufList(1, C.byref(bid)) # Creates a buffer list with one buffer | |
# CONFIGURE THE PROPERTIES OF THE BUFFER | |
imgbuffer = C.POINTER(C.c_uint16)() # create a null pointer | |
RGBbuffer = C.POINTER(C.c_uint32)() # placeholder for the Bayer decoded imgbuffer (i.e. demosaiced imgbuffer) | |
imaq.imgCreateBuffer(sid, 0, bufsize, C.byref(imgbuffer)) # allocate memory (the buffer) on the host machine (param2==0) | |
imaq.imgCreateBuffer(sid, 0, width.value*height.value * 4, C.byref(RGBbuffer)) | |
imaq.imgSetBufferElement(bid, 0, IMG_BUFF_ADDRESS, C.cast(imgbuffer, C.POINTER(C.c_uint32))) # my guess is that the cast to an uint32 is necessary to prevent 64-bit callable memory addresses | |
imaq.imgSetBufferElement(bid, 0, IMG_BUFF_SIZE, bufsize) | |
imaq.imgSetBufferElement(bid, 0, IMG_BUFF_COMMAND, IMG_CMD_STOP) | |
# CALCULATE THE LOOKUP TABLES TO CONVERT THE BAYER ENCODED IMAGE TO RGB (=DEMOSAICING) | |
imaq.imgCalculateBayerColorLUT(red_gain, green_gain, blue_gain, redLUT, greenLUT, blueLUT, bitsperpixel) | |
imgbuffer_vpp = C.cast(C.byref(imgbuffer), C.POINTER(C.c_void_p)) | |
def capture(filename): | |
imaq.imgSnap(sid, imgbuffer_vpp) | |
#imaq.imgSnap(sid, imgbuffer) # <- doesn't work (img produced is entirely black). The above 2 lines are required | |
imaq.imgSessionSaveBufferEx(sid, imgbuffer, filename) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment