# -*- coding: utf-8 -*-
"""
Define camera driver classes for FLIR cameras
"""
import numpy as np
from .CameraClassDef import CameraClass
# PySpin driver for FLIR cameras
try :
import PySpin
PySpinDriverInstalled = True
except :
print('ERROR ! : Tried to load PySpin driver, but it is not installed')
PySpinDriverInstalled = False
[docs]
class FLIRPySpinClass(CameraClass) :
"""Parent class for FLIR cameras using PySpin python driver.
Used as generic driver class if no model-specific child class is defined in same file below"""
[docs]
def __init__(self, cameraNumber, triggerMode=0, exposurems=1,
gaindB=1, camROI=None, loadDefault = False) :
"""Initialize the Camera object
Args:
cameraNumber (int) : index of camera in camerasConfigs list
Keyword Args:
triggerMode=0 (int) : 0 for hardware/external, 1 for software/internal
exposurems=1. (float) : Exposition duration (exposure) in ms.
gaindB=0. (float) : hardware gain of the camera in dB.
camROI=None (None or [int]*4) : Camera region of interest to read from sensor
[x offset , y offset , x size , y size ] (binning is not implemented)
loadDefault = True (bool): Decide if default values from cameraConfigs should be set at creation
Return:
FLIRPySpinClass camera object
"""
super().__init__(cameraNumber, triggerMode=triggerMode, exposurems=exposurems,
gaindB=gaindB, camROI=camROI, loadDefault=loadDefault)
#check if driver installed, if no return
self.cameraDriverInstalled = PySpinDriverInstalled
if not(self.cameraDriverInstalled) :
print('ERROR ! : PySpin driver is not installed')
return
#init PySpin.System object and get camera list
self.camSystem = PySpin.System.GetInstance()
self.camList = self.camSystem.GetCameras()
# here is the main camera object that will be used to access camera via driver
# get serial number of camera to use from cameraConfig and pick the right camera in list
self.pySpinCamera = self.camList.GetBySerial(str(self.cameraConfig['serial']))
if not(self.pySpinCamera.IsValid()) :
print('ERROR ! : Could not connect camera '+str(cameraNumber)+' (maybe check serial number)')
return
#connect camera
self.pySpinCamera.Init()
# # get camera and transport interfaces node maps to access to advanced features
# self.pySpinNodeMap = self.pySpinCamera.GetNodeMap()
# self.pySpinStreamNodeMap = self.pySpinCamera.GetTLStreamNodeMap()
# set mode bit depth for image mono 8 or mono 16 depending on cameraCOnfig imageBitDepth
self.imageBitDepth = int(self.cameraConfig['imageBitDepth'])
if self.imageBitDepth <= 8 :
self.pySpinCamera.PixelFormat.SetValue(PySpin.PixelFormat_Mono8)
self.imageDtype = np.uint8
self.imageBitsToShift = int(8 - self.imageBitDepth) # number n of bits to right shift (/2^n) = difference bits(imageDtype) - imageBitDepth
else :
self.pySpinCamera.PixelFormat.SetValue(PySpin.PixelFormat_Mono16)
self.imageDtype = np.uint16
self.imageBitsToShift = int(16 - self.imageBitDepth) # number n of bits to right shift (/2^n) = difference bits(imageDtype) - imageBitDepth
# set other properties that are standard for proper acquisition
self.pySpinCamera.BlackLevel.SetValue(0) # offset, former brightness
self.pySpinCamera.ExposureAuto.SetValue(PySpin.ExposureAuto_Off)
# self.pySpinCamera.Gamma.SetValue(1.) # not writable with some cameras like Chameleon 3 camera
# set trigger mode
self.pySpinCamera.TriggerActivation.SetValue(PySpin.TriggerActivation_RisingEdge)
self.pySpinCamera.TriggerDelay.SetValue(self.pySpinCamera.TriggerDelay.GetMin())
self.setTriggerMode(self.triggerMode)
# get exposure min and max, set auto off and set exposure
self.exposuremsMin = self.pySpinCamera.ExposureTime.GetMin()*1.e-3
self.exposuremsMax = self.pySpinCamera.ExposureTime.GetMax()*1.e-3
self.pySpinCamera.ExposureAuto.SetValue(PySpin.ExposureAuto_Off)
self.setExposurems(self.exposurems)
# get gain properties and set gain
self.gaindBMin = self.pySpinCamera.Gain.GetMin()
self.gaindBMax = self.pySpinCamera.Gain.GetMax()
self.pySpinCamera.GainAuto.SetValue(PySpin.GainAuto_Off)
self.setGaindB(self.gaindB)
# #set camera ROI,
self.setCamROI(ROI=camROI)
# numpy image array
self.imageSize = (self.hpx,self.wpx)
self.image = np.array(self.imageSize, dtype=self.imageDtype)
# image scaling and properties
self.pixelCalXumperpx = self.cameraConfig['pixelCalXumperpx']
self.pixelCalYumperpx = self.cameraConfig['pixelCalYumperpx']
self.reversedAxes = self.cameraConfig['reversedAxes']
self.maxLevel = 2**self.imageBitDepth - 1
self.imageLimits = [-self.wpx/2*self.pixelCalXumperpx,
self.wpx/2*self.pixelCalXumperpx,
-self.hpx/2*self.pixelCalYumperpx,
self.hpx/2*self.pixelCalYumperpx]
# set camera buffer count to 20 images
self.pySpinCamera.TLStream.StreamBufferCountManual.SetValue(20)
#
#start acquisition
self.startAcquisition()
self.startAcquisition() # to solve bug appearring on 25/05/2021
self.cameraConnected = True
[docs]
def __del__(self) :
"""Delete the Camera object by calling close function
Close the Camera and free memory"""
if self.cameraConnected :
self.pySpinCamera.EndAcquisition()
self.pySpinCamera.DeInit()
del(self.pySpinCamera)
self.camList.Clear()# Clear camera list before releasing system
self.camSystem.ReleaseInstance()# Release PySpin system instance
super().__del__() #ALWAYS call at end of any child class del
[docs]
def setTriggerMode(self, triggerMode):
"""Set the trigger mode
Args:
triggerMode (int) : 0 for hardware/external, 1 for software/internal
"""
acqStarted = self.pySpinCamera.IsStreaming()
if acqStarted : #stop acquisition if already started
self.pySpinCamera.EndAcquisition()
self.triggerMode = triggerMode
#stop trigger before change
self.pySpinCamera.TriggerMode.SetValue(PySpin.TriggerMode_Off)
if triggerMode==0:#'external'
self.pySpinCamera.TriggerSource.SetValue(PySpin.TriggerSource_Line0)
elif triggerMode==1:#'software'
self.pySpinCamera.TriggerSource.SetValue(PySpin.TriggerSource_Software)
else : raise NameError('Trigger mode number not defined ')
#restart trigger
self.pySpinCamera.TriggerMode.SetValue(PySpin.TriggerMode_On)
if acqStarted : #restart acquisition if started initially
self.pySpinCamera.BeginAcquisition()
[docs]
def sendSoftwareTrigger(self):
"""Send a software trigger to the camera to start an exposure"""
self.pySpinCamera.TriggerSoftware.Execute()
[docs]
def setExposurems(self, exposurems) :
"""Set the duration of the exposition
Args:
exposurems (float) : Exposition duration (exposure) in ms.
"""
acqStarted = self.pySpinCamera.IsStreaming()
if acqStarted : #stop acquisition if already started
self.pySpinCamera.EndAcquisition()
if exposurems >= self.exposuremsMin and exposurems <= self.exposuremsMax :
self.pySpinCamera.ExposureTime.SetValue(exposurems*1.e3)
self.exposurems = self.pySpinCamera.ExposureTime.GetValue()*1.e-3
elif exposurems < self.exposuremsMin :
self.pySpinCamera.ExposureTime.SetValue(self.exposuremsMin*1.e3)
self.exposurems = self.pySpinCamera.ExposureTime.GetValue()*1.e-3
else :
self.pySpinCamera.ExposureTime.SetValue(self.exposuremsMax*1.e3)
self.exposurems = self.pySpinCamera.ExposureTime.GetValue()*1.e-3
if acqStarted : #restart acquisition if started initially
self.pySpinCamera.BeginAcquisition()
[docs]
def setGaindB(self, gaindB) :
"""Set the hardware gain of camera readout
Args:
gaindB (float) : hardware gain of the camera in dB.
"""
acqStarted = self.pySpinCamera.IsStreaming()
if acqStarted : #stop acquisition if already started
self.pySpinCamera.EndAcquisition()
gaindBset = gaindB
if gaindBset > self.gaindBMax : gaindBset = self.gaindBMax
if gaindBset < self.gaindBMin : gaindBset = self.gaindBMin
self.pySpinCamera.Gain.SetValue(gaindBset)
self.gaindB = self.pySpinCamera.Gain.GetValue()
if acqStarted : #restart acquisition if started initially
self.pySpinCamera.BeginAcquisition()
[docs]
def roundCamROI(self, ROI=None) :
"""Rounding of ROI values to closest possible one according to camera rules
Keyword Args:
ROI = None ( None or [int]*4) : [# x offset, y offset, x size, y size]
if None, set to [0, 0, max Width, max height]
Return:
Camera ROI ([int]*4): [ x offset, y offset, x size, y size]
"""
rules = [self.pySpinCamera.OffsetX.GetInc(),
self.pySpinCamera.OffsetY.GetInc(),
self.pySpinCamera.Width.GetInc(),
self.pySpinCamera.Height.GetInc()]
self.wpxmax = self.pySpinCamera.Width.GetMax()
self.hpxmax = self.pySpinCamera.Height.GetMax()
if ROI is not None:
def rounding(a) :
val, step = a
return int(val - (val % step))
[x,y,w,h] = ROI
x = int(max(min(x,self.wpxmax-w), 0))
y = int(max(min(y,self.hpxmax-h), 0))
return map(rounding, zip([x,y,w,h], rules))
else:
return [0, 0, self.wpxmax , self.hpxmax]
[docs]
def setCamROI(self, ROI=None) :
"""Set the ROI of camera (without binning)
Keyword Args:
ROI = None ( None or [int]*4) : [ x offset, y offset, x size, y size]
if None, set to [0, 0, max Width, max height]
Return:
Camera ROI ([int]*4) : [ x offset, y offset, x size, y size]
"""
acqStarted = self.pySpinCamera.IsStreaming()
if acqStarted : #stop acquisition if already started
self.pySpinCamera.EndAcquisition()
self.camROI = self.roundCamROI(ROI)
[x,y,w,h] = self.camROI
self.pySpinCamera.OffsetX.SetValue(x)
self.pySpinCamera.OffsetX.SetValue(y)
self.wpx = w #width
self.pySpinCamera.Width.SetValue(w)
self.hpx = h #height
self.pySpinCamera.Height.SetValue(h)
if acqStarted : #restart acquisition if started initially
self.pySpinCamera.BeginAcquisition()
return self.camROI
[docs]
def startAcquisition(self):
"""Start acquisition or restart if already running """
if self.pySpinCamera.IsStreaming() :
self.pySpinCamera.EndAcquisition()
self.pySpinCamera.BeginAcquisition()
[docs]
def clearBuffer(self) :
""" Clear camera buffer from images.
Use before imaging scans to make sure no previously acquired image is present in buffer"""
while self.pySpinCamera.TLStream.StreamOutputBufferCount() > 0 :
self._image = self.pySpinCamera.GetNextImage(int(100))
[docs]
def grabArray(self) :
"""Get an image from the camera.
Wait for trigger if external or generate one if internal.
Return:
Camera image (numpy 2D array)
"""
self.imageAcqLastFailed = False
try:
if not self.pySpinCamera.IsStreaming() : # normally should not happen but if acquisition stopped, restart it
self.startAcquisition()
if self.triggerMode==1 :
self.sendSoftwareTrigger()
self._image = self.pySpinCamera.GetNextImage(int(1000*self.timeout))
if self._image.IsIncomplete():
print('ERROR ! : Image incomplete with image status %d ...' % self._image.GetImageStatus())
self.imageAcqLastFailed = True
return False
else :
if self.reversedAxes == [False, False] :
self.image = np.array(self._image.GetNDArray()) >> self.imageBitsToShift
elif self.reversedAxes == [True, False] :
self.image = np.flip(np.array(self._image.GetNDArray()) >> self.imageBitsToShift, 1) #X axis is second dimension in image array
elif self.reversedAxes == [False, True] :
self.image = np.flip(np.array(self._image.GetNDArray()) >> self.imageBitsToShift, 0) #Y axis is first dimension in image array
elif self.reversedAxes == [True, True] :
self.image = np.flip(np.array(self._image.GetNDArray()) >> self.imageBitsToShift, (0,1))
else :
self.image = np.array(self._image.GetNDArray()) >> self.imageBitsToShift
print('WARNING ! : \n reversedAxes in camera config is not properly defined as [True/False , True/False] \n Axes unchanged from default [False, False]')
except PySpin.SpinnakerException as ex:
print('ERROR : %s' % ex)
print('Failed to grab array from camera : probably Timeout')
self.imageAcqLastFailed = True
return False
return self.image.copy()
class ExampleModelClass(FLIRPySpinClass) :
"""Model-specific class for ... FLIR camera using PySpin python driver.
Child Class of FLIRPySpinClass.
Use to implement specific initialization for this camera model"""
def __init__(self, cameraNumber, triggerMode=0, exposurems=1,
gaindB=1, camROI=None, loadDefault = False) :
"""Initialize the Camera object
Args:
cameraNumber (int) : index of camera in camerasConfigs list
Keyword Args:
triggerMode=0 (int) : 0 for hardware/external, 1 for software/internal
exposurems=1. (float) : Exposition duration (exposure) in ms.
gaindB=0. (float) : hardware gain of the camera in dB.
camROI=None (None or [int]*4) : Camera region of interest to read from sensor
[x offset , y offset , x size , y size ] (binning is not implemented)
loadDefault = True (bool): Decide if default values from cameraConfigs should be set at creation
Return:
ExampleModelClass camera object
"""
super().__init__(cameraNumber, triggerMode=triggerMode, exposurems=exposurems,
gaindB=gaindB, camROI=camROI, loadDefault=loadDefault)
def __del__(self) :
"""Delete the Camera object by calling close function
Close the Camera and free memory"""
super().__del__() #ALWAYS call at end of any child class del
[docs]
class Chameleon3Class(FLIRPySpinClass) :
"""Model-specific class for Chameleon3 FLIR cameras using PySpin python driver.
Child Class of FLIRPySpinClass.
Use to implement specific initialization for this camera model"""
[docs]
def __init__(self, cameraNumber, triggerMode=0, exposurems=1,
gaindB=1, camROI=None, loadDefault = False) :
"""Initialize the Camera object
Args:
cameraNumber (int) : index of camera in camerasConfigs list
Keyword Args:
triggerMode=0 (int) : 0 for hardware/external, 1 for software/internal
exposurems=1. (float) : Exposition duration (exposure) in ms.
gaindB=0. (float) : hardware gain of the camera in dB.
camROI=None (None or [int]*4) : Camera region of interest to read from sensor
[x offset , y offset , x size , y size ] (binning is not implemented)
loadDefault = True (bool): Decide if default values from cameraConfigs should be set at creation
Return:
Chameleon3Class camera object
"""
super().__init__(cameraNumber, triggerMode=triggerMode, exposurems=exposurems,
gaindB=gaindB, camROI=camROI, loadDefault=loadDefault)
[docs]
def __del__(self) :
"""Delete the Camera object by calling close function
Close the Camera and free memory"""
super().__del__() #ALWAYS call at end of any child class del