diff --git a/pyCameras/cameraAVT.py b/pyCameras/cameraAVTPymba.py similarity index 88% rename from pyCameras/cameraAVT.py rename to pyCameras/cameraAVTPymba.py index 237dcabdac480462ca177c3969696d3c6bda606d..e7b9d0acc7d116d24a425ca403bb82e0e9752eff 100644 --- a/pyCameras/cameraAVT.py +++ b/pyCameras/cameraAVTPymba.py @@ -177,6 +177,20 @@ class Camera(CameraTemplate): self.registerFeature('numberCams', self._setNumberCams) self.registerFeature('numberOfCameras', self._setNumberCams) + self.ReverseX = False + self.ReverseY = False + + # Function to set the Camera Images as flipped according to X and Y + self.registerFeature('reverseImageX', self._setReverseX) + self.registerFeature('reverseX', self._setReverseX) + self.registerFeature('flipX', self._setReverseX) + self.registerFeature('flippedImageX', self._setReverseX) + + self.registerFeature('reverseImageY', self._setReverseY) + self.registerFeature('reverseY', self._setReverseY) + self.registerFeature('flipY', self._setReverseY) + self.registerFeature('flippedImageY', self._setReverseY) + self.framelist = [] self.imgData = [] self._clearQueueAndFrames() @@ -445,7 +459,14 @@ class Camera(CameraTemplate): self._cleanUp() self.logger.debug('Image acquisition finished') - return imgData.copy() + if self.ReverseX and self.ReverseY: + return np.flipud(np.fliplr(imgData.copy())) + elif self.ReverseX and not self.ReverseY: + return np.flipud(imgData.copy()) + elif not self.ReverseX and self.ReverseY: + return np.fliplr(imgData.copy()) + else: + return imgData.copy() def prepareRecording(self, num): """ Sets the camera to MultiFrame mode and prepares frames. Use with @@ -797,10 +818,67 @@ class Camera(CameraTemplate): raise TypeError('Trigger Mode should be None, "in", "out", or ' '"off". Got {mode}'.format(mode=mode)) + def _setReverseX(self, reversed=None): + """ + Flips the image sent by the camera horizontally. The Region of interest is applied after flipping. If the avt + camera used has the hardware support with ReverseX the hardwware Interface is used. If not the device + the self property called ReverseY to differentiate this. + + Parameters + ---------- + reversed : bool + If a horizontally flipped image is desired then reversed has to be set as true. Default value for + devices ReverseX value is false. The Region of interest is applied after flipping. + + Returns + ------- + ReverseX : bool + Returns the current state of the ReverseX value. Default is false. + """ + if reversed is not None: + if hasattr(self.device, 'ReverseX'): + self.device.ReverseX = reversed + else: + self.ReverseX = reversed + self.logger.debug('Setting <ReverseX> to {reversed}'.format(reversed=reversed)) + + if hasattr(self.device, 'ReverseX'): + return self.device.ReverseX + else: + return self.ReverseX + + def _setReverseY(self, reversed=None): + """ + Flips the image sent by the camera verticall. The Region of interest is applied after flipping. If the avt + camera used has the hardware support with ReverseY the hardwware Interface is used. If not the device the self + property called ReverseY to differentiate this. + + Parameters + ---------- + reversed : bool + If a vertically flipped image is desired then reversed has to be set as true. Default value for + devices ReverseY value is false. + + Returns + ------- + ReverseY : bool + Returns the current state of the ReverseY value. Default is false. + """ + if reversed is not None: + if hasattr(self.device, 'ReverseY'): + self.device.ReverseY = reversed + else: + self.ReverseY = reversed + self.logger.debug('Setting <ReverseY> to {reversed}'.format(reversed=reversed)) + + if hasattr(self.device, 'ReverseY'): + return self.device.ReverseY + else: + return self.ReverseY + def __repr__(self): return repr(self.device) - if __name__ == '__main__': import logging import cv2 as cv @@ -823,28 +901,29 @@ if __name__ == '__main__': # cam_device = contr.getDevice('DEV_000F314D941E') # Test auto exposure - cam_device = Camera('DEV_000F314E2C01') + cam_device = Camera('DEV_000F314E5F03') - cam_device.exposure = 400000 + cam_device.exposure = 600000 print("Before: ", cam_device.exposure) - exposure = cam_device.autoExposure() - print("After: ", cam_device.exposure) + # exposure = cam_device.autoExposure() + # print("After: ", cam_device.exposure) # Listing features of device if bListFeatures: cam_device.listFeatures() - # Get an image - image = cam_device.getImage() - cv.namedWindow('Captured image', cv.WINDOW_NORMAL) - cv.resizeWindow('Captured image', 1000, 1000) - cv.imshow('Captured image', image) - cv.waitKey() + # image = cam_device.getImage() + # cv.namedWindow('Captured image', cv.WINDOW_NORMAL) + # cv.resizeWindow('Captured image', 1000, 1000) + # cv.imshow('Captured image', image) + # cv.waitKey() if bLiveView: cam_device._liveView() - images = cam_device.getImages(10) + number_of_images = 10 + print("Capturing {0} images".format(number_of_images)) + images = cam_device.getImages(number_of_images) print(len(images)) for _, img in enumerate(images): print('Showing image {i}'.format(i=_)) diff --git a/pyCameras/cameraAVTVimba.py b/pyCameras/cameraAVTVimba.py new file mode 100644 index 0000000000000000000000000000000000000000..f0d1c334dcbaf7c00eac68b72d4dada1c3c1ab22 --- /dev/null +++ b/pyCameras/cameraAVTVimba.py @@ -0,0 +1,960 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +__author__ = "Tim Betker" +__copyright__ = "Copyright 2017, LUH: IMR" +__credits__ = ["Rüdiger Beermann"] +# __license__ = "" +__version__ = "0.3" +__maintainer__ = "Tim Betker" +__email__ = "tim.betker@imr.uni-hannover.de" +__status__ = "alpha" +__package_name__ = "AVTcamera" +__developer__ = __author__ + +import copy +import re +import time +import logging +import threading +import cv2 + +import numpy as np + +from vimba.vimba import Vimba +from vimba.frame import FrameStatus +from vimba.error import VimbaFeatureError + +from pyCameras.cameraTemplate import ControllerTemplate, CameraTemplate + +LOGGING_LEVEL = None + + +def vimba_context(func): + """ + Decorator which enables a function to be executed + inside the vimba context when not needing the vimba variable itself. + + Parameters + ---------- + func : Decorated function + """ + + def open_vimba_context(*args, **kwargs): + with Vimba.get_instance(): + return func(*args, **kwargs) + + return open_vimba_context + + +class FrameHandler: + def __init__(self, max_imgs=1000): + self.img_data = list() + self.max_imgs = max_imgs + + def __call__(self, cam, frame): + if frame.get_status() == FrameStatus.Complete: + if len(self.img_data) < self.max_imgs: + # After max_imgs images all frames will be trashed + self.img_data.append(frame.as_numpy_ndarray()) + + cam.queue_frame(frame) + + def get_images(self): + return self.img_data.copy() + + +class FrameHandlerBlocking(FrameHandler): + def __init__(self, max_imgs, thread_lock): + super(FrameHandlerBlocking, self).__init__(max_imgs=max_imgs) + self.thread_lock = thread_lock + # self.counter = 0 + + def __call__(self, cam, frame): + # print(f"{cam} Frame {self.counter}") + # print(f"{cam} Frame Status {frame.get_status()}") + if frame.get_status() == FrameStatus.Complete: + # self.counter += 1 + if len(self.img_data) < self.max_imgs: + # After max_imgs images all frames will be trashed + self.img_data.append(frame.as_numpy_ndarray()) + if len(self.img_data) >= self.max_imgs: + self.thread_lock.release() + + cam.queue_frame(frame) + + +class CVLiveViewHandler: + def __init__(self, width, height): + self.shutdown_event = threading.Event() + self.window_width = width + self.window_height = height + + def __call__(self, cam, frame): + key = cv2.waitKey(1) + if key in (ord('q'), 13): + self.shutdown_event.set() + return + + elif frame.get_status() == FrameStatus.Complete: + print('{} acquired {}'.format(cam, frame), flush=True) + + msg = 'Stream from \'{}\'. Press <Enter> or <q> to stop stream.' + window_title = msg.format(cam.get_name()) + cv2.namedWindow(window_title, cv2.WINDOW_NORMAL) + # TODO: Currently resizes the window to 900 x 900 for every frame (moving to init does not work) + cv2.resizeWindow(window_title, self.window_width, self.window_height) + cv2.imshow(window_title, frame.as_opencv_image()) + + cam.queue_frame(frame) + + +class Grabber(threading.Thread): + def __init__(self, camera, frame_handler, number_of_images): + super(Grabber, self).__init__() + self._stop_event = threading.Event() + self._cam = camera + self._frame_handler = frame_handler + self._buffer_count = number_of_images + + def run(self): + # Start vimba and camera contexts + with self._cam as cam: + cam.start_streaming(handler=self._frame_handler, buffer_count=self._buffer_count) + # Keep context alive until grabber is stopped + while not self._stop_event.is_set(): + pass + + def stop(self): + with Vimba.get_instance(): + self._cam.stop_streaming() + self._stop_event.set() + + +class Controller(ControllerTemplate): + """ + Camera controller for AVT cameras based on pymba + """ + + def __init__(self): + """ + Camera controller for AVT camera devices. This implementation uses + pymba as backend. + """ + super(Controller, self).__init__() + self.logger = logging.getLogger(__name__) + if LOGGING_LEVEL is not None: + self.logger.setLevel(LOGGING_LEVEL) + self.logger.debug('Starting AVT Camera Controller') + + def updateDeviceHandles(self): + """ + Refresh the list of available devices + """ + self.logger.debug('Searching for AVT camera devices') + self.device_handles = [] + with Vimba.get_instance() as vimba: + cams = vimba.get_all_cameras() + + for cam in cams: + self.device_handles.append('<AVT {model} (MAC: {mac})>' + ''.format(model=cam.get_name(), + mac=cam.get_id())) + + def getDevice(self, device_handle): + """ + Return the corresponding camera object for given device handle + + Parameters + ---------- + device_handle : can be IP address, mac address or + camera ID (DEV_...) as reported by Camera.get_id() + + Returns + ------- + cam : Camera object + A camera object for AVT devices corresponding to the given + device handle + """ + + # Check if device handle is list or tuple, if so: use first entry + if isinstance(device_handle, (list, tuple)): + device_handle = device_handle[0] + + self.logger.debug('Opening device {device_handle}' + ''.format(device_handle=device_handle)) + # Search for mac addresses in form 'DEV_XXXXXXXXXXXX' + candidates = re.findall(r'(DEV_[0-9A-Z]{11,13})', device_handle) + if len(candidates) == 0: + # no mac address found: search for IP + candidates = re.findall(r'[0-9]+(?:\.[0-9]+){3}', device_handle) + + try: + return Camera(device_handle=candidates[0]) + except Exception as e: + self.logger.exception('Failed to open the camera device: {e}' + ''.format(e=e)) + msg = '<Was not able to open camera with given device handle!!\n' \ + 'Handle must be IP or MAC address (DEV_XXXXXXXXXXXXX)>' + e.message = msg + print(e.message) + raise + + def closeController(self): + self.logger.info("Vimba Camera Controller shutdown") + + def __repr__(self): + return "<AVT Camera Controller>" + + +class Camera(CameraTemplate): + """ + AVT Camera implementation based on pymba + + Creating this Object automatically opens the camera. It is NOT necessary to + call openDevice() !!! This is done to set some settings to put the camera + into freerun mode. + """ + + def __init__(self, device_handle): + """ + Implementation of the AVT camera device + + Parameters + ---------- + device_handle : object + Unique camera device handle to identify the camera + """ + super(Camera, self).__init__(device_handle) + self.logger = logging.getLogger(__name__) + if LOGGING_LEVEL is not None: + self.logger.setLevel(LOGGING_LEVEL) + + self.thread_lock = threading.Lock() + + self.vmb = Vimba.get_instance() + self.vmb.__enter__() + + # with Vimba.get_instance() as vimba: + self.device = self.vmb.get_camera_by_id(device_handle) + + # Sets package sizes and transfer rates for GigE cameras + # TODO: How to set transfer rates for already running cameras? + self._setup_transfer_sizes() + + self.device_handle = device_handle + + self.triggerModeSetting = 'off' + + # Register AVT specific functions. + # Function to set pixel format + self.registerFeature('pixelFormat', self.setPixelFormat) + self.registerFeature('pixelType', self.setPixelFormat) + self.registerFeature('format', self.setPixelFormat) + + # Influences framerate, necessary if network bandwidth is not big enough + # NOTE: Functions self._setMaxTransferRate, self._setTransferRate and self._setNumberCams may change this value + # self.device.StreamBytesPerSecond = 10000000 # 10 Mb/sec (without GigE) + self.device.StreamBytesPerSecond = 115000000 # 100 Mb/sec (with GigE) + + self.maxTransferRate = 115000000 + self.numCams = 1 + self.isSet = {'rate': False, 'numCams': False} + + # Register AVT specific functions. + # Function to set maximum transfer rate depending on used network specifications + self.registerFeature('maxRate', self._setMaxTransferRate) + self.registerFeature('bandwidth', self._setMaxTransferRate) + self.registerFeature('maximumTransferRate', self._setMaxTransferRate) + self.registerFeature('transferRate', self._setTransferRate) + # Function to set number of cameras, may affect the available transfer rate per camera + self.registerFeature('numCams', self._setNumberCams) + self.registerFeature('numberCams', self._setNumberCams) + self.registerFeature('numberOfCameras', self._setNumberCams) + + self.ReverseX = False + self.ReverseY = False + # Function to set the Camera Images as flipped according to X and Y + self.registerFeature('reverseImageX', self._setReverseX) + self.registerFeature('reverseX', self._setReverseX) + self.registerFeature('flipX', self._setReverseX) + self.registerFeature('flippedImageX', self._setReverseX) + + self.registerFeature('reverseImageY', self._setReverseY) + self.registerFeature('reverseY', self._setReverseY) + self.registerFeature('flipY', self._setReverseY) + self.registerFeature('flippedImageY', self._setReverseY) + + self._frame_handler = None + self._number_of_images = None + self._grabber = None + self.imgData = [] + + # Init data type LUT for each PixelFormat + self.imageFormatLUT = {'Mono8': np.uint8, 'Mono12': np.uint16} + + def __del__(self): + self.vmb.__exit__(None, None, None) + + #@vimba_context + def _setup_transfer_sizes(self): + with self.device as cam: + # Try to adjust GeV packet size. This Feature is only available for GigE - Cameras. + try: + cam.GVSPAdjustPacketSize.run() + + while not cam.GVSPAdjustPacketSize.is_done(): + pass + + except (AttributeError, VimbaFeatureError): + pass + + @staticmethod + def openController(): + """ + Open the camera controller and prepare everything to make cameras + accessible + """ + pass + + @staticmethod + def closeController(): + """ + Close the camera controller + """ + pass + + @staticmethod + def listDevices(): + """ + List available AVT cameras + + Returns + ------- + cams : list + list of available AVT devices + """ + return Controller().listDevices() + + def openDevice(self): + """ + Opens a camera device with the stored self.device object + """ + # Cameras are handled within a context. + # There is a "open" method, but using the with statement seems more safe. + pass + + def closeDevice(self): + """ + Closes camera device + """ + del self.device + self.device = None + + def isOpen(self): + """ + Check if the device for this instance is currently open and ready to + communicate + + Returns + ------- + bool + True if the camera connection is open, False if it is not + """ + # Cameras are handled within a context. No available isOpen function. + if self.device is not None: + return True + else: + return False + + def getImage(self, *args, **kwargs): + """ + Get an image from the camera device + + *args and **kwargs are ignored parameters! + + !!! Warning: Check transfer rate of your network connection !!! + Low transfer-rates may cause incomplete image transfer with missing + data + + Returns + ------- + img : np.ndarray + Current camera image + """ + self.logger.debug('Creating frame and starting acquisition') + # Create new frame for camera + with self.device as cam: + frame = cam.get_frame(timeout_ms=2000) + img = frame.as_numpy_ndarray() + + self.logger.debug('Image acquisition finished') + + if self.ReverseX and self.ReverseY: + return np.flipud(np.fliplr(img.copy())) + elif self.ReverseX and not self.ReverseY: + return np.flipud(img.copy()) + elif not self.ReverseX and self.ReverseY: + return np.fliplr(img.copy()) + else: + return img.copy() + + def prepareRecording(self, num): + """ Sets the camera to MultiFrame mode and prepares frames. Use with + "record()"-function. + + Parameters + ---------- + num : int + number of frames to be captured during acquisition + """ + self.thread_lock.acquire() + + with self.device as cam: + cam.AcquisitionMode.set('MultiFrame') + cam.AcquisitionFrameCount.set(num) + + self._frame_handler = FrameHandlerBlocking(max_imgs=num, thread_lock=self.thread_lock) + self._grabber = Grabber(self.device, self._frame_handler, num) + self._grabber.start() + + while not self.device.is_streaming(): + pass + + time.sleep(1) + + def record(self): + """ Blocking image acquisition, ends acquisition when num frames are + captured, where num is set by "prepareRecording(num)". Only use with + "prepareRecording(num)". + + Returns + ------- + imgData : list + List of images + """ + print("Waiting for lock") + self.thread_lock.acquire() + print("Acquired lock") + # Reset thread + self._grabber.stop() + self._grabber.join() + self._grabber = None + + # Get data and clear frame handler + self.imgData = self._frame_handler.get_images() + self._frame_handler = None + + with self.device as cam: + cam.AcquisitionMode.set('Continuous') + + self.thread_lock.release() + + return self.imgData + + def grabStart(self, number_of_images): + """ + Prepares num images to be grabbed. This function is not blocking. + Calling "grabStop()" will end acquisition. + + Parameters + ---------- + num : int + Number of images that should be recorded + """ + # TODO: Prevent starting multiple threads? - Camera is occupied anyway... + self._frame_handler = FrameHandler(max_imgs=1000) + self._grabber = Grabber(self.device, self._frame_handler, number_of_images=number_of_images) + self._grabber.start() + + def grabStop(self): + """ + Stop grabbing images and return camera to continuous mode. + """ + # Reset thread + while self._grabber._buffer_count != len(self._frame_handler.img_data): + continue + + self._grabber.stop() + self._grabber.join() + self._grabber = None + + # Get data and clear frame handler + self.imgData = self._frame_handler.get_images() + self._frame_handler = None + + return self.imgData + + #@vimba_context + def _liveView(self): + """ + Live image stream an visualization through OpenCV window + + Leave _liveView by pressing "q" + """ + handler = CVLiveViewHandler(400, 400) + self.setTriggerMode("off") + with self.device as cam: + cam.AcquisitionMode.set('Continuous') + try: + cam.start_streaming(handler=handler, buffer_count=10) + handler.shutdown_event.wait() + finally: + cam.stop_streaming() + + #@vimba_context + def listFeatures(self): + """ + Lists camera features + """ + try: + self.logger.debug('Listing camera features') + with self.device as cam: + for feature in cam.get_all_features(): + print("-------------------") + print("Feature name: {}".format(feature.get_name())) + except Exception as e: + self.logger.exception('Failed to get feature names: ' + '{e}'.format(e=e)) + + #@vimba_context + def setExposureMicrons(self, microns=None): + """ + Set the exposure time to the given value in microseconds or read the + current value by passing None + + Parameters + ---------- + microns : int + Desired exposure time in microseconds that should be set, or None + to read the current exposure time + + Returns + ------- + microns : int + The exposure time in microseconds after applying the passed value + """ + with self.device as cam: + exposure_time_feature = cam.ExposureTimeAbs + if microns is not None: + self.logger.debug('Setting <ExposureTime> to {microns}' + ''.format(microns=microns)) + exposure_time_feature.set(microns) + + return exposure_time_feature.get() + + #@vimba_context + def _setReverseX(self, reversed=None): + """ + Flips the image sent by the camera horizontally. The Region of interest is applied after flipping. If the avt + camera used has the hardware support with ReverseX the hardwware Interface is used. If not the device + the self property called ReverseY to differentiate this. + + Parameters + ---------- + reversed : bool + If a horizontally flipped image is desired then reversed has to be set as true. Default value for + devices ReverseX value is false. The Region of interest is applied after flipping. + + Returns + ------- + ReverseX : bool + Returns the current state of the ReverseX value. Default is false. + """ + with self.device as cam: + if reversed is not None: + if cam.get_feature_by_name("ReverseX"): + cam.ReverseX.set(reversed) + else: + self.ReverseX = reversed + self.logger.debug('Setting <ReverseX> to {reversed}'.format(reversed=reversed)) + + if cam.get_feature_by_name("ReverseX"): + return cam.ReverseX.get() + else: + return self.ReverseX + + #@vimba_context + def _setReverseY(self, reversed=None): + """ + Flips the image sent by the camera verticall. The Region of interest is applied after flipping. If the avt + camera used has the hardware support with ReverseY the hardwware Interface is used. If not the device the self + property called ReverseY to differentiate this. + + Parameters + ---------- + reversed : bool + If a vertically flipped image is desired then reversed has to be set as true. Default value for + devices ReverseY value is false. + + Returns + ------- + ReverseY : bool + Returns the current state of the ReverseY value. Default is false. + """ + with self.device as cam: + if reversed is not None: + if cam.get_feature_by_name("ReverseY"): + cam.ReverseY.set(reversed) + else: + self.ReverseY = reversed + self.logger.debug('Setting <ReverseY> to {reversed}'.format(reversed=reversed)) + + if cam.get_feature_by_name("ReverseY"): + return cam.ReverseY.get() + else: + return self.ReverseY + + def _setMaxTransferRate(self, rate=None): + """ + Sets the transfer rate by changing 'StreamBytesPerSecond'. + If passed None, will return actual rate set. + + Parameters + ---------- + rate: int + Maximum bandwidth available. Typical values: + - with GigE : 115000000 + - without GigE : 10000000 + + Returns + ------- + self.max_bandwidth: int + If passed None: returns set bandwidth + """ + self.logger.debug("Setting max transfer rate for device {handle} to {rate}" + "".format(handle=self.device_handle, rate=rate)) + if rate is None: + return self.maxTransferRate + self.maxTransferRate = rate + self.isSet['rate'] = True + + # Call function if number of cams was set + if self.isSet['numCams']: + self._setTransferRate() + else: + self.device.StreamBytesPerSecond = rate + + return self.maxTransferRate + + def _setTransferRate(self): + """ + Takes maxTransferRate and numCams to compute a viable transfer rate for the device. + Formula: StreamBytesPerSecond = Height x Width x FrameRate x BytesPerPixel + """ + + + transfer_rate = int(self.maxTransferRate / self.numCams) + self.device.StreamBytesPerSecond = transfer_rate + self.logger.debug("Setting transfer rate for {device} to {rate}" + "".format(device=self.device_handle, rate=transfer_rate)) + + def _setNumberCams(self, num=None): + """ + Sets the number of AVT cameras used (this will affect the maximum transfer rate for each camera). + If passed None, will return actual number of cameras set. + + Parameters + ---------- + num: int + Number of AVT cameras + + Returns + ------- + self.numCams: int + Number of AVT cameras set for this object + """ + self.logger.debug("Setting number of cameras for device {handle} to {num}" + "".format(handle=self.device_handle, num=num)) + if num is None: + return self.numCams + self.numCams = num + self.isSet['numCams'] = True + + if self.isSet['rate']: + self._setTransferRate() + + return self.numCams + + #@vimba_context + def autoExposure(self): + """ + Automatically sets the exposure time of the camera ONCE. + Old exposure setting is lost during the process! + + Returns + ------- + exposure : int + The exposure time in microseconds after auto exposure + """ + self.logger.debug("Starting automatic exposure control") + with self.device as cam: + exposure_auto_feature = cam.ExposureAuto + exposure_auto_feature.set("Once") + + exposure_time_feature = cam.ExposureTimeAbs + # Save trigger settings and activate acquisition until + # auto exposure has settled + trigger_mode_buffer = self.triggerMode + + self.triggerMode = "off" + max_iter = 100 + iter = 0 + # Auto exposure gets stuck if the border values are reached, + # but further adjustments are necessary + limits = (cam.ExposureAutoMin.get(), cam.ExposureAutoMax.get()) + limit_cnt = 0 + last_exposure = -1 + + while str(exposure_auto_feature.get()).lower() != "off": + frame = cam.get_frame(timeout_ms=2000) + if last_exposure in limits: + limit_cnt += 1 + else: + limit_cnt = 0 + + self.logger.debug("Limit count: {}".format(limit_cnt)) + self.logger.debug("Auto Exposure feature: {}".format(exposure_auto_feature.get())) + last_exposure = exposure_time_feature.get() + self.logger.debug("Current exposure: {}".format(last_exposure)) + + if limit_cnt > 5: + self.logger.info( + "Auto exposure has run into limits. Continuing with exposure of: {exposure} ".format( + exposure=last_exposure)) + exposure_auto_feature.set("Off") + if iter >= max_iter: + try: + raise TimeoutError("Timeout while setting auto exposure!") + except NameError: + # Python 2 compatible Error + raise Exception("Timeout while setting auto exposure!") + + iter += 1 + + self.triggerMode = trigger_mode_buffer + self.logger.debug("Set exposure time to {exposure}" + "".format(exposure=last_exposure)) + + return exposure_time_feature.get() + + #@vimba_context + def setResolution(self, resolution=None): + """ + Set the resolution of the camera to the given values in pixels or read + the current resolution by passing None + + Parameters + ---------- + resolution : tuple + Desired camera resolution in the form (width, height), or None to + read the current resolution + + Returns + ------- + resolution : tuple + The set camera resolution after applying the passed value + """ + with self.device as cam: + height_feature = cam.Height + width_feature = cam.Width + if resolution is not None: + self.logger.debug('Setting <Width> to {width}' + ''.format(width=resolution[0])) + width_feature.set(resolution[0]) + self.logger.debug('Setting <Height> to {height}' + ''.format(height=resolution[1])) + height_feature.set(resolution[1]) + return width_feature.get(), height_feature.get() + + #@vimba_context + def setGain(self, gain=None): + """ + Set the gain of the camera to the given value or read the current value + by passing None + + Parameters + ---------- + gain : float + Desired gain value in dB to be set, or None to read the current + gain value + + Returns + ------- + gain : int + The gain value after applying the passed value + """ + with self.device as cam: + gain_feature = cam.Gain + if gain is not None: + self.logger.debug('Setting <Gain> to {gain}' + ''.format(gain=gain)) + gain_feature.set(gain) + return gain_feature.get() + + #@vimba_context + def setPixelFormat(self, fmt=None): + """ + Set the image format to the passed setting or read the current format + by passing None + + Parameters + ---------- + fmt : str + String describing the desired image format (e.g. "mono8"), or None + to read the current image format. Check camera technical manual for available formats, + may differ from model to model. + + Returns + ------- + fmt : str + The image format after applying the passed value + """ + with self.device as cam: + fmt_feature = cam.get_feature_by_name("PixelFormat") + if fmt is not None: + self.logger.debug('Setting <PixelFormat> to {fmt}' + ''.format(fmt=fmt)) + fmt_feature.set(fmt) + + # Try to adjust GeV packet size. This Feature is only available for GigE - Cameras. + try: + cam.GVSPAdjustPacketSize.run() + + while not cam.GVSPAdjustPacketSize.is_done(): + pass + + except (AttributeError, VimbaFeatureError): + pass + + return str(fmt_feature.get()) + + #@vimba_context + def setTriggerMode(self, mode=None): + """ + Set the trigger mode of the camera to either "in", "out" or "off", or + read the current trigger setting ba passing None + + Parameters + ---------- + mode : str + The desired trigger mode. "in" means the camera receives a trigger + signal, "out" means the camera sends a trigger signal, "off"" means + the camera does not react to triggers. To read the current trigger + setting pass None + + Returns + ------- + mode : str + The trigger mode after applying the passed value + """ + # TODO: Not perfectly clean since the settings may differ from self.triggerModeSetting + if mode is None: + return self.triggerModeSetting + + with self.device as cam: + trigger_mode_feature = cam.TriggerMode + trigger_selector_feature = cam.TriggerSelector + trigger_source_feature = cam.TriggerSource + trigger_activation_feature = cam.TriggerActivation + self.logger.debug("Setting trigger mode to: {mode}".format(mode=mode)) + + if isinstance(mode, str): + if mode.lower() == 'in': + trigger_mode_feature.set('On') + trigger_source_feature.set('Line1') + trigger_selector_feature.set('FrameStart') + trigger_activation_feature.set("RisingEdge") + + self.triggerModeSetting = 'in' + elif mode.lower() == 'out': + # TODO: Implement out trigger for AVT cameras + self.triggerModeSetting = 'out' + raise NotImplementedError('Sending triggers is not' + 'implemented yet!') + elif mode.lower() == 'off': + trigger_mode_feature.set('Off') + trigger_source_feature.set('Freerun') + trigger_selector_feature.set('FrameStart') + + self.triggerModeSetting = 'off' + else: + raise ValueError('Unexpected value in setTriggerMode. ' + 'Expected "in", "out", or "off". Got {mode}' + ''.format(mode=mode)) + return self.triggerModeSetting + else: + raise TypeError('Trigger Mode should be None, "in", "out", or ' + '"off". Got {mode}'.format(mode=mode)) + + def __repr__(self): + return repr(self.device) + + +if __name__ == '__main__': + import logging + + logging.basicConfig(level=logging.DEBUG) + bListFeatures = False + bLiveView = False + + contr = Controller() + handle = contr.listDevices() + print(handle) + + # Dictionary to test different connection types/inputs + source = {'IP': '130.75.27.144', + 'Handle_list': handle, + 'Handle': handle[0], + 'Bad_input': 'Yo Mama is fat'} + # Use one of source entries here: + cam_device = contr.getDevice(source['Handle_list']) + # cam_device = contr.getDevice('DEV_000F314D941E') + + # Test auto exposure + cam_device = Camera('000F314E5F04') + + cam_device.exposure = 1500000 + # print("Before: ", cam_device.exposure) + # exposure = cam_device.autoExposure() + # print("After: ", cam_device.exposure) + + # Listing features of device + if bListFeatures: + cam_device.listFeatures() + + # Get an image + # image = cam_device.getImage() + # cv2.namedWindow('Captured image', cv2.WINDOW_NORMAL) + # cv2.resizeWindow('Captured image', 1000, 1000) + # cv2.imshow('Captured image', image) + # cv2.waitKey() + + if bLiveView: + cam_device._liveView() + + print(cam_device.pixelFormat) + + cam_device.setTriggerMode("off") + number_of_images = 10 + print("Capturing {0} images".format(number_of_images)) + + # images = cam_device.getImages(number_of_images) + # print(f"Number of Images {len(images)}") + # for i, img in enumerate(images): + # cv2.imwrite(rf"F:\Messwerte\Telezentrie\image{i}.png") + + # print('Showing image {i}'.format(i=i)) + # cv2.imshow('Captured image', img) + # cv2.waitKey() + + cam_device.grabStart(10) + grabbedImgs = cam_device.grabStop() + + for i, img in enumerate(grabbedImgs): + print(f"Writing Image {i}") + cv2.imwrite(rf"F:\Messwerte\Telezentrie\image{i}.png", img) + + cam_device.closeDevice() + + # contr.closeController() diff --git a/pyCameras/cameraTemplate.py b/pyCameras/cameraTemplate.py index 3cceb1365907c6650949c2b1dc6a5ed1b73be8f5..c54bcb17d39ecf56f30211b80ba797ef9ab9428e 100644 --- a/pyCameras/cameraTemplate.py +++ b/pyCameras/cameraTemplate.py @@ -80,8 +80,7 @@ class ControllerTemplate(object): raise NotImplementedError def __del__(self): - self.logger.debug('Deleting cameracontroller {self}' - ''.format(self=self)) + self.logger.debug('Deleting cameracontroller {self}'.format(self=self)) self.closeController() def __repr__(self):