924 lines
33 KiB
Python
924 lines
33 KiB
Python
"""BSD 2-Clause License
|
||
|
||
Copyright (c) 2019, Allied Vision Technologies GmbH
|
||
All rights reserved.
|
||
|
||
Redistribution and use in source and binary forms, with or without
|
||
modification, are permitted provided that the following conditions are met:
|
||
|
||
1. Redistributions of source code must retain the above copyright notice, this
|
||
list of conditions and the following disclaimer.
|
||
|
||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||
this list of conditions and the following disclaimer in the documentation
|
||
and/or other materials provided with the distribution.
|
||
|
||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
"""
|
||
|
||
import enum
|
||
import ctypes
|
||
import copy
|
||
import functools
|
||
|
||
from typing import Optional, Tuple
|
||
from .c_binding import byref, sizeof, decode_flags
|
||
from .c_binding import call_vimba_c, call_vimba_image_transform, VmbFrameStatus, VmbFrameFlags, \
|
||
VmbFrame, VmbHandle, VmbPixelFormat, VmbImage, VmbDebayerMode, \
|
||
VmbTransformInfo, PIXEL_FORMAT_CONVERTIBILITY_MAP, PIXEL_FORMAT_TO_LAYOUT
|
||
from .feature import FeaturesTuple, FeatureTypes, FeatureTypeTypes, discover_features
|
||
from .shared import filter_features_by_name, filter_features_by_type, filter_features_by_category, \
|
||
attach_feature_accessors, remove_feature_accessors
|
||
from .util import TraceEnable, RuntimeTypeCheckEnable, EnterContextOnCall, LeaveContextOnCall, \
|
||
RaiseIfOutsideContext
|
||
from .error import VimbaFrameError, VimbaFeatureError
|
||
|
||
try:
|
||
import numpy # type: ignore
|
||
|
||
except ModuleNotFoundError:
|
||
numpy = None # type: ignore
|
||
|
||
|
||
__all__ = [
|
||
'PixelFormat',
|
||
'MONO_PIXEL_FORMATS',
|
||
'BAYER_PIXEL_FORMATS',
|
||
'RGB_PIXEL_FORMATS',
|
||
'RGBA_PIXEL_FORMATS',
|
||
'BGR_PIXEL_FORMATS',
|
||
'BGRA_PIXEL_FORMATS',
|
||
'YUV_PIXEL_FORMATS',
|
||
'YCBCR_PIXEL_FORMATS',
|
||
'COLOR_PIXEL_FORMATS',
|
||
'OPENCV_PIXEL_FORMATS',
|
||
'FrameStatus',
|
||
'Debayer',
|
||
'Frame',
|
||
'FrameTuple',
|
||
'FormatTuple',
|
||
'intersect_pixel_formats'
|
||
]
|
||
|
||
|
||
# Forward declarations
|
||
FrameTuple = Tuple['Frame', ...]
|
||
FormatTuple = Tuple['PixelFormat', ...]
|
||
|
||
|
||
class PixelFormat(enum.IntEnum):
|
||
"""Enum specifying all PixelFormats. Note: Not all Cameras support all Pixelformats.
|
||
|
||
Mono formats:
|
||
Mono8 - Monochrome, 8 bits (PFNC:Mono8)
|
||
Mono10 - Monochrome, 10 bits in 16 bits (PFNC:Mono10)
|
||
Mono10p - Monochrome, 4x10 bits continuously packed in 40 bits
|
||
(PFNC:Mono10p)
|
||
Mono12 - Monochrome, 12 bits in 16 bits (PFNC:Mono12)
|
||
Mono12Packed - Monochrome, 2x12 bits in 24 bits (GEV:Mono12Packed)
|
||
Mono12p - Monochrome, 2x12 bits continuously packed in 24 bits
|
||
(PFNC:Mono12p)
|
||
Mono14 - Monochrome, 14 bits in 16 bits (PFNC:Mono14)
|
||
Mono16 - Monochrome, 16 bits (PFNC:Mono16)
|
||
|
||
Bayer formats:
|
||
BayerGR8 - Bayer-color, 8 bits, starting with GR line
|
||
(PFNC:BayerGR8)
|
||
BayerRG8 - Bayer-color, 8 bits, starting with RG line
|
||
(PFNC:BayerRG8)
|
||
BayerGB8 - Bayer-color, 8 bits, starting with GB line
|
||
(PFNC:BayerGB8)
|
||
BayerBG8 - Bayer-color, 8 bits, starting with BG line
|
||
(PFNC:BayerBG8)
|
||
BayerGR10 - Bayer-color, 10 bits in 16 bits, starting with GR
|
||
line (PFNC:BayerGR10)
|
||
BayerRG10 - Bayer-color, 10 bits in 16 bits, starting with RG
|
||
line (PFNC:BayerRG10)
|
||
BayerGB10 - Bayer-color, 10 bits in 16 bits, starting with GB
|
||
line (PFNC:BayerGB10)
|
||
BayerBG10 - Bayer-color, 10 bits in 16 bits, starting with BG
|
||
line (PFNC:BayerBG10)
|
||
BayerGR12 - Bayer-color, 12 bits in 16 bits, starting with GR
|
||
line (PFNC:BayerGR12)
|
||
BayerRG12 - Bayer-color, 12 bits in 16 bits, starting with RG
|
||
line (PFNC:BayerRG12)
|
||
BayerGB12 - Bayer-color, 12 bits in 16 bits, starting with GB
|
||
line (PFNC:BayerGB12)
|
||
BayerBG12 - Bayer-color, 12 bits in 16 bits, starting with BG
|
||
line (PFNC:BayerBG12)
|
||
BayerGR12Packed - Bayer-color, 2x12 bits in 24 bits, starting with GR
|
||
line (GEV:BayerGR12Packed)
|
||
BayerRG12Packed - Bayer-color, 2x12 bits in 24 bits, starting with RG
|
||
line (GEV:BayerRG12Packed)
|
||
BayerGB12Packed - Bayer-color, 2x12 bits in 24 bits, starting with GB
|
||
line (GEV:BayerGB12Packed)
|
||
BayerBG12Packed - Bayer-color, 2x12 bits in 24 bits, starting with BG
|
||
line (GEV:BayerBG12Packed)
|
||
BayerGR10p - Bayer-color, 4x10 bits continuously packed in 40
|
||
bits, starting with GR line (PFNC:BayerGR10p)
|
||
BayerRG10p - Bayer-color, 4x10 bits continuously packed in 40
|
||
bits, starting with RG line (PFNC:BayerRG10p)
|
||
BayerGB10p - Bayer-color, 4x10 bits continuously packed in 40
|
||
bits, starting with GB line (PFNC:BayerGB10p)
|
||
BayerBG10p - Bayer-color, 4x10 bits continuously packed in 40
|
||
bits, starting with BG line (PFNC:BayerBG10p)
|
||
BayerGR12p - Bayer-color, 2x12 bits continuously packed in 24
|
||
bits, starting with GR line (PFNC:BayerGR12p)
|
||
BayerRG12p - Bayer-color, 2x12 bits continuously packed in 24
|
||
bits, starting with RG line (PFNC:BayerRG12p)
|
||
BayerGB12p - Bayer-color, 2x12 bits continuously packed in 24
|
||
bits, starting with GB line (PFNC:BayerGB12p)
|
||
BayerBG12p - Bayer-color, 2x12 bits continuously packed in 24
|
||
bits, starting with BG line (PFNC:BayerBG12p)
|
||
BayerGR16 - Bayer-color, 16 bits, starting with GR line
|
||
(PFNC:BayerGR16)
|
||
BayerRG16 - Bayer-color, 16 bits, starting with RG line
|
||
(PFNC:BayerRG16)
|
||
BayerGB16 - Bayer-color, 16 bits, starting with GB line
|
||
(PFNC:BayerGB16)
|
||
BayerBG16 - Bayer-color, 16 bits, starting with BG line
|
||
(PFNC:BayerBG16)
|
||
|
||
RGB formats:
|
||
Rgb8 - RGB, 8 bits x 3 (PFNC:RGB8)
|
||
Bgr8 - BGR, 8 bits x 3 (PFNC:Bgr8)
|
||
Rgb10 - RGB, 10 bits in 16 bits x 3 (PFNC:RGB10)
|
||
Bgr10 - BGR, 10 bits in 16 bits x 3 (PFNC:BGR10)
|
||
Rgb12 - RGB, 12 bits in 16 bits x 3 (PFNC:RGB12)
|
||
Bgr12 - BGR, 12 bits in 16 bits x 3 (PFNC:BGR12)
|
||
Rgb14 - RGB, 14 bits in 16 bits x 3 (PFNC:RGB14)
|
||
Bgr14 - BGR, 14 bits in 16 bits x 3 (PFNC:BGR14)
|
||
Rgb16 - RGB, 16 bits x 3 (PFNC:RGB16)
|
||
Bgr16 - BGR, 16 bits x 3 (PFNC:BGR16)
|
||
|
||
RGBA formats:
|
||
Argb8 - ARGB, 8 bits x 4 (PFNC:RGBa8)
|
||
Rgba8 - RGBA, 8 bits x 4, legacy name
|
||
Bgra8 - BGRA, 8 bits x 4 (PFNC:BGRa8)
|
||
Rgba10 - RGBA, 10 bits in 16 bits x 4
|
||
Bgra10 - BGRA, 10 bits in 16 bits x 4
|
||
Rgba12 - RGBA, 12 bits in 16 bits x 4
|
||
Bgra12 - BGRA, 12 bits in 16 bits x 4
|
||
Rgba14 - RGBA, 14 bits in 16 bits x 4
|
||
Bgra14 - BGRA, 14 bits in 16 bits x 4
|
||
Rgba16 - RGBA, 16 bits x 4
|
||
Bgra16 - BGRA, 16 bits x 4
|
||
|
||
YUV/YCbCr formats:
|
||
Yuv411 - YUV 411 with 8 bits (GEV:YUV411Packed)
|
||
Yuv422 - YUV 422 with 8 bits (GEV:YUV422Packed)
|
||
Yuv444 - YUV 444 with 8 bits (GEV:YUV444Packed)
|
||
YCbCr411_8_CbYYCrYY - Y´CbCr 411 with 8 bits
|
||
(PFNC:YCbCr411_8_CbYYCrYY) - identical to Yuv411
|
||
YCbCr422_8_CbYCrY - Y´CbCr 422 with 8 bits
|
||
(PFNC:YCbCr422_8_CbYCrY) - identical to Yuv422
|
||
YCbCr8_CbYCr - Y´CbCr 444 with 8 bits
|
||
(PFNC:YCbCr8_CbYCr) - identical to Yuv444
|
||
"""
|
||
# Mono Formats
|
||
Mono8 = VmbPixelFormat.Mono8
|
||
Mono10 = VmbPixelFormat.Mono10
|
||
Mono10p = VmbPixelFormat.Mono10p
|
||
Mono12 = VmbPixelFormat.Mono12
|
||
Mono12Packed = VmbPixelFormat.Mono12Packed
|
||
Mono12p = VmbPixelFormat.Mono12p
|
||
Mono14 = VmbPixelFormat.Mono14
|
||
Mono16 = VmbPixelFormat.Mono16
|
||
|
||
# Bayer Formats
|
||
BayerGR8 = VmbPixelFormat.BayerGR8
|
||
BayerRG8 = VmbPixelFormat.BayerRG8
|
||
BayerGB8 = VmbPixelFormat.BayerGB8
|
||
BayerBG8 = VmbPixelFormat.BayerBG8
|
||
BayerGR10 = VmbPixelFormat.BayerGR10
|
||
BayerRG10 = VmbPixelFormat.BayerRG10
|
||
BayerGB10 = VmbPixelFormat.BayerGB10
|
||
BayerBG10 = VmbPixelFormat.BayerBG10
|
||
BayerGR12 = VmbPixelFormat.BayerGR12
|
||
BayerRG12 = VmbPixelFormat.BayerRG12
|
||
BayerGB12 = VmbPixelFormat.BayerGB12
|
||
BayerBG12 = VmbPixelFormat.BayerBG12
|
||
BayerGR12Packed = VmbPixelFormat.BayerGR12Packed
|
||
BayerRG12Packed = VmbPixelFormat.BayerRG12Packed
|
||
BayerGB12Packed = VmbPixelFormat.BayerGB12Packed
|
||
BayerBG12Packed = VmbPixelFormat.BayerBG12Packed
|
||
BayerGR10p = VmbPixelFormat.BayerGR10p
|
||
BayerRG10p = VmbPixelFormat.BayerRG10p
|
||
BayerGB10p = VmbPixelFormat.BayerGB10p
|
||
BayerBG10p = VmbPixelFormat.BayerBG10p
|
||
BayerGR12p = VmbPixelFormat.BayerGR12p
|
||
BayerRG12p = VmbPixelFormat.BayerRG12p
|
||
BayerGB12p = VmbPixelFormat.BayerGB12p
|
||
BayerBG12p = VmbPixelFormat.BayerBG12p
|
||
BayerGR16 = VmbPixelFormat.BayerGR16
|
||
BayerRG16 = VmbPixelFormat.BayerRG16
|
||
BayerGB16 = VmbPixelFormat.BayerGB16
|
||
BayerBG16 = VmbPixelFormat.BayerBG16
|
||
|
||
# RGB Formats
|
||
Rgb8 = VmbPixelFormat.Rgb8
|
||
Bgr8 = VmbPixelFormat.Bgr8
|
||
Rgb10 = VmbPixelFormat.Rgb10
|
||
Bgr10 = VmbPixelFormat.Bgr10
|
||
Rgb12 = VmbPixelFormat.Rgb12
|
||
Bgr12 = VmbPixelFormat.Bgr12
|
||
Rgb14 = VmbPixelFormat.Rgb14
|
||
Bgr14 = VmbPixelFormat.Bgr14
|
||
Rgb16 = VmbPixelFormat.Rgb16
|
||
Bgr16 = VmbPixelFormat.Bgr16
|
||
|
||
# RGBA Formats
|
||
Rgba8 = VmbPixelFormat.Rgba8
|
||
Bgra8 = VmbPixelFormat.Bgra8
|
||
Argb8 = VmbPixelFormat.Argb8
|
||
Rgba10 = VmbPixelFormat.Rgba10
|
||
Bgra10 = VmbPixelFormat.Bgra10
|
||
Rgba12 = VmbPixelFormat.Rgba12
|
||
Bgra12 = VmbPixelFormat.Bgra12
|
||
Rgba14 = VmbPixelFormat.Rgba14
|
||
Bgra14 = VmbPixelFormat.Bgra14
|
||
Rgba16 = VmbPixelFormat.Rgba16
|
||
Bgra16 = VmbPixelFormat.Bgra16
|
||
Yuv411 = VmbPixelFormat.Yuv411
|
||
Yuv422 = VmbPixelFormat.Yuv422
|
||
Yuv444 = VmbPixelFormat.Yuv444
|
||
|
||
# YCbCr Formats
|
||
YCbCr411_8_CbYYCrYY = VmbPixelFormat.YCbCr411_8_CbYYCrYY
|
||
YCbCr422_8_CbYCrY = VmbPixelFormat.YCbCr422_8_CbYCrY
|
||
YCbCr8_CbYCr = VmbPixelFormat.YCbCr8_CbYCr
|
||
|
||
def __str__(self):
|
||
return self._name_
|
||
|
||
def __repr__(self):
|
||
return 'PixelFormat.{}'.format(str(self))
|
||
|
||
def get_convertible_formats(self) -> Tuple['PixelFormat', ...]:
|
||
formats = PIXEL_FORMAT_CONVERTIBILITY_MAP[VmbPixelFormat(self)]
|
||
return tuple([PixelFormat(fmt) for fmt in formats])
|
||
|
||
|
||
MONO_PIXEL_FORMATS = (
|
||
PixelFormat.Mono8,
|
||
PixelFormat.Mono10,
|
||
PixelFormat.Mono10p,
|
||
PixelFormat.Mono12,
|
||
PixelFormat.Mono12Packed,
|
||
PixelFormat.Mono12p,
|
||
PixelFormat.Mono14,
|
||
PixelFormat.Mono16
|
||
)
|
||
|
||
|
||
BAYER_PIXEL_FORMATS = (
|
||
PixelFormat.BayerGR8,
|
||
PixelFormat.BayerRG8,
|
||
PixelFormat.BayerGB8,
|
||
PixelFormat.BayerBG8,
|
||
PixelFormat.BayerGR10,
|
||
PixelFormat.BayerRG10,
|
||
PixelFormat.BayerGB10,
|
||
PixelFormat.BayerBG10,
|
||
PixelFormat.BayerGR12,
|
||
PixelFormat.BayerRG12,
|
||
PixelFormat.BayerGB12,
|
||
PixelFormat.BayerBG12,
|
||
PixelFormat.BayerGR12Packed,
|
||
PixelFormat.BayerRG12Packed,
|
||
PixelFormat.BayerGB12Packed,
|
||
PixelFormat.BayerBG12Packed,
|
||
PixelFormat.BayerGR10p,
|
||
PixelFormat.BayerRG10p,
|
||
PixelFormat.BayerGB10p,
|
||
PixelFormat.BayerBG10p,
|
||
PixelFormat.BayerGR12p,
|
||
PixelFormat.BayerRG12p,
|
||
PixelFormat.BayerGB12p,
|
||
PixelFormat.BayerBG12p,
|
||
PixelFormat.BayerGR16,
|
||
PixelFormat.BayerRG16,
|
||
PixelFormat.BayerGB16,
|
||
PixelFormat.BayerBG16
|
||
)
|
||
|
||
|
||
RGB_PIXEL_FORMATS = (
|
||
PixelFormat.Rgb8,
|
||
PixelFormat.Rgb10,
|
||
PixelFormat.Rgb12,
|
||
PixelFormat.Rgb14,
|
||
PixelFormat.Rgb16
|
||
)
|
||
|
||
|
||
RGBA_PIXEL_FORMATS = (
|
||
PixelFormat.Rgba8,
|
||
PixelFormat.Argb8,
|
||
PixelFormat.Rgba10,
|
||
PixelFormat.Rgba12,
|
||
PixelFormat.Rgba14,
|
||
PixelFormat.Rgba16
|
||
)
|
||
|
||
|
||
BGR_PIXEL_FORMATS = (
|
||
PixelFormat.Bgr8,
|
||
PixelFormat.Bgr10,
|
||
PixelFormat.Bgr12,
|
||
PixelFormat.Bgr14,
|
||
PixelFormat.Bgr16
|
||
)
|
||
|
||
|
||
BGRA_PIXEL_FORMATS = (
|
||
PixelFormat.Bgra8,
|
||
PixelFormat.Bgra10,
|
||
PixelFormat.Bgra12,
|
||
PixelFormat.Bgra14,
|
||
PixelFormat.Bgra16
|
||
)
|
||
|
||
|
||
YUV_PIXEL_FORMATS = (
|
||
PixelFormat.Yuv411,
|
||
PixelFormat.Yuv422,
|
||
PixelFormat.Yuv444
|
||
)
|
||
|
||
|
||
YCBCR_PIXEL_FORMATS = (
|
||
PixelFormat.YCbCr411_8_CbYYCrYY,
|
||
PixelFormat.YCbCr422_8_CbYCrY,
|
||
PixelFormat.YCbCr8_CbYCr
|
||
)
|
||
|
||
|
||
COLOR_PIXEL_FORMATS = BAYER_PIXEL_FORMATS + RGB_PIXEL_FORMATS + RGBA_PIXEL_FORMATS + \
|
||
BGR_PIXEL_FORMATS + BGRA_PIXEL_FORMATS + YUV_PIXEL_FORMATS + \
|
||
YCBCR_PIXEL_FORMATS
|
||
|
||
|
||
OPENCV_PIXEL_FORMATS = (
|
||
PixelFormat.Mono8,
|
||
PixelFormat.Bgr8,
|
||
PixelFormat.Bgra8,
|
||
PixelFormat.Mono16,
|
||
PixelFormat.Bgr16,
|
||
PixelFormat.Bgra16
|
||
)
|
||
|
||
|
||
class Debayer(enum.IntEnum):
|
||
"""Enum specifying debayer modes.
|
||
|
||
Enum values:
|
||
Mode2x2 - 2x2 with green averaging (this is the default if no debayering algorithm
|
||
is added as transformation option).
|
||
Mode3x3 - 3x3 with equal green weighting per line (8-bit images only).
|
||
ModeLCAA - Debayering with horizontal local color anti-aliasing (8-bit images only).
|
||
ModeLCAAV - Debayering with horizontal and vertical local color anti-aliasing
|
||
( 8-bit images only).
|
||
ModeYuv422 - Debayering with YUV422-alike sub-sampling (8-bit images only).
|
||
"""
|
||
Mode2x2 = VmbDebayerMode.Mode_2x2
|
||
Mode3x3 = VmbDebayerMode.Mode_3x3
|
||
ModeLCAA = VmbDebayerMode.Mode_LCAA
|
||
ModeLCAAV = VmbDebayerMode.Mode_LCAAV
|
||
ModeYuv422 = VmbDebayerMode.Mode_YUV422
|
||
|
||
def __str__(self):
|
||
return 'DebayerMode.{}'.format(self._name_)
|
||
|
||
def __repr__(self):
|
||
return str(self)
|
||
|
||
|
||
class FrameStatus(enum.IntEnum):
|
||
"""Enum specifying the current status of internal Frame data.
|
||
|
||
Enum values:
|
||
Complete - Frame data is complete without errors.
|
||
Incomplete - Frame could not be filled to the end.
|
||
TooSmall - Frame buffer was too small.
|
||
Invalid - Frame buffer was invalid.
|
||
"""
|
||
|
||
Complete = VmbFrameStatus.Complete
|
||
Incomplete = VmbFrameStatus.Incomplete
|
||
TooSmall = VmbFrameStatus.TooSmall
|
||
Invalid = VmbFrameStatus.Invalid
|
||
|
||
|
||
class AllocationMode(enum.IntEnum):
|
||
"""Enum specifying the supported frame allocation modes.
|
||
|
||
Enum values:
|
||
AnnounceFrame - The buffer is allocated by VimbaPython
|
||
AllocAndAnnounceFrame - The buffer is allocated by the Transport Layer
|
||
"""
|
||
AnnounceFrame = 0
|
||
AllocAndAnnounceFrame = 1
|
||
|
||
|
||
class AncillaryData:
|
||
"""Ancillary Data are created after enabling a Cameras 'ChunkModeActive' Feature.
|
||
Ancillary Data are Features stored within a Frame.
|
||
"""
|
||
@TraceEnable()
|
||
@LeaveContextOnCall()
|
||
def __init__(self, handle: VmbFrame):
|
||
"""Do not call directly. Get Object via Frame access method"""
|
||
self.__handle: VmbFrame = handle
|
||
self.__data_handle: VmbHandle = VmbHandle()
|
||
self.__feats: FeaturesTuple = ()
|
||
self.__context_cnt: int = 0
|
||
|
||
@TraceEnable()
|
||
def __enter__(self):
|
||
if not self.__context_cnt:
|
||
self._open()
|
||
|
||
self.__context_cnt += 1
|
||
return self
|
||
|
||
@TraceEnable()
|
||
def __exit__(self, exc_type, exc_value, exc_traceback):
|
||
self.__context_cnt -= 1
|
||
|
||
if not self.__context_cnt:
|
||
self._close()
|
||
|
||
@RaiseIfOutsideContext()
|
||
def get_all_features(self) -> FeaturesTuple:
|
||
"""Get all features in ancillary data.
|
||
|
||
Returns:
|
||
A set of all currently features stored in Ancillary Data.
|
||
|
||
Raises:
|
||
RuntimeError then called outside of "with" - statement.
|
||
"""
|
||
return self.__feats
|
||
|
||
@RaiseIfOutsideContext()
|
||
@RuntimeTypeCheckEnable()
|
||
def get_features_by_type(self, feat_type: FeatureTypeTypes) -> FeaturesTuple:
|
||
"""Get all features in ancillary data of a specific type.
|
||
|
||
Valid FeatureTypes are: IntFeature, FloatFeature, StringFeature, BoolFeature,
|
||
EnumFeature, CommandFeature, RawFeature
|
||
|
||
Arguments:
|
||
feat_type - FeatureType used find features of that type.
|
||
|
||
Returns:
|
||
A all features of type 'feat_type'.
|
||
|
||
Raises:
|
||
RuntimeError then called outside of "with" - statement.
|
||
TypeError if parameters do not match their type hint.
|
||
"""
|
||
return filter_features_by_type(self.__feats, feat_type)
|
||
|
||
@RaiseIfOutsideContext()
|
||
@RuntimeTypeCheckEnable()
|
||
def get_features_by_category(self, category: str) -> FeaturesTuple:
|
||
"""Get all features in ancillary data of a specific category.
|
||
|
||
Arguments:
|
||
category - Category that should be used for filtering.
|
||
|
||
Returns:
|
||
A all features of category 'category'.
|
||
|
||
Raises:
|
||
RuntimeError then called outside of "with" - statement.
|
||
TypeError if parameters do not match their type hint.
|
||
"""
|
||
return filter_features_by_category(self.__feats, category)
|
||
|
||
@RaiseIfOutsideContext()
|
||
@RuntimeTypeCheckEnable()
|
||
def get_feature_by_name(self, feat_name: str) -> FeatureTypes:
|
||
"""Get a features in ancillary data by its name.
|
||
|
||
Arguments:
|
||
feat_name - Name used to find a feature.
|
||
|
||
Returns:
|
||
Feature with the associated name.
|
||
|
||
Raises:
|
||
RuntimeError then called outside of "with" - statement.
|
||
TypeError if parameters do not match their type hint.
|
||
VimbaFeatureError if no feature is associated with 'feat_name'.
|
||
"""
|
||
feat = filter_features_by_name(self.__feats, feat_name)
|
||
|
||
if not feat:
|
||
raise VimbaFeatureError('Feature \'{}\' not found.'.format(feat_name))
|
||
|
||
return feat
|
||
|
||
@TraceEnable()
|
||
@EnterContextOnCall()
|
||
def _open(self):
|
||
call_vimba_c('VmbAncillaryDataOpen', byref(self.__handle), byref(self.__data_handle))
|
||
|
||
self.__feats = _replace_invalid_feature_calls(discover_features(self.__data_handle))
|
||
attach_feature_accessors(self, self.__feats)
|
||
|
||
@TraceEnable()
|
||
@LeaveContextOnCall()
|
||
def _close(self):
|
||
remove_feature_accessors(self, self.__feats)
|
||
self.__feats = ()
|
||
|
||
call_vimba_c('VmbAncillaryDataClose', self.__data_handle)
|
||
self.__data_handle = VmbHandle()
|
||
|
||
|
||
def _replace_invalid_feature_calls(feats: FeaturesTuple) -> FeaturesTuple:
|
||
# AncillaryData are basically "lightweight" features. Calling most feature related
|
||
# Functions with a AncillaryData - Handle leads to VimbaC Errors. This method decorates
|
||
# all Methods that are unsafe to call with a decorator raising a RuntimeError.
|
||
to_wrap = [
|
||
'get_access_mode',
|
||
'is_readable',
|
||
'is_writeable',
|
||
'register_change_handler',
|
||
'get_increment',
|
||
'get_range',
|
||
'set'
|
||
]
|
||
|
||
# Decorator raising a RuntimeError instead of delegating call to inner function.
|
||
def invalid_call(func):
|
||
@functools.wraps(func)
|
||
def wrapper(*args, **kwargs):
|
||
msg = 'Calling \'{}\' is invalid for AncillaryData Features.'
|
||
raise RuntimeError(msg.format(func.__name__))
|
||
|
||
return wrapper
|
||
|
||
# Replace original implementation by injecting a surrounding decorator and
|
||
# binding the resulting function as a method to the Feature instance.
|
||
for f, a in [(f, a) for f in feats for a in to_wrap]:
|
||
try:
|
||
fn = invalid_call(getattr(f, a))
|
||
setattr(f, a, fn.__get__(f))
|
||
|
||
except AttributeError:
|
||
pass
|
||
|
||
return feats
|
||
|
||
|
||
class Frame:
|
||
"""This class allows access to Frames acquired by a camera. The Frame is basically
|
||
a buffer that wraps image data and some metadata.
|
||
"""
|
||
def __init__(self, buffer_size: int, allocation_mode: AllocationMode):
|
||
"""Do not call directly. Create Frames via Camera methods instead."""
|
||
self._allocation_mode = allocation_mode
|
||
|
||
# Allocation is not necessary for the AllocAndAnnounce case. In that case the Transport
|
||
# Layer will take care of buffer allocation. The self._buffer variable will be updated after
|
||
# the frame is announced and memory has been allocated.
|
||
if self._allocation_mode == AllocationMode.AnnounceFrame:
|
||
self._buffer = (ctypes.c_ubyte * buffer_size)()
|
||
self._frame: VmbFrame = VmbFrame()
|
||
|
||
# Setup underlaying Frame
|
||
if self._allocation_mode == AllocationMode.AnnounceFrame:
|
||
self._frame.buffer = ctypes.cast(self._buffer, ctypes.c_void_p)
|
||
self._frame.bufferSize = sizeof(self._buffer)
|
||
elif self._allocation_mode == AllocationMode.AllocAndAnnounceFrame:
|
||
# Set buffer pointer to NULL and inform Transport Layer of size it should allocate
|
||
self._frame.buffer = None
|
||
self._frame.bufferSize = buffer_size
|
||
|
||
def __str__(self):
|
||
msg = 'Frame(id={}, status={}, buffer={})'
|
||
return msg.format(self._frame.frameID, str(FrameStatus(self._frame.receiveStatus)),
|
||
hex(self._frame.buffer))
|
||
|
||
def __deepcopy__(self, memo):
|
||
cls = self.__class__
|
||
result = cls.__new__(cls)
|
||
memo[id(self)] = result
|
||
|
||
# VmbFrame contains Pointers and ctypes.Structure with Pointers can't be copied.
|
||
# As a workaround VmbFrame contains a deepcopy-like Method performing deep copy of all
|
||
# Attributes except PointerTypes. Those must be set manually after the copy operation.
|
||
setattr(result, '_buffer', copy.deepcopy(self._buffer, memo))
|
||
setattr(result, '_frame', self._frame.deepcopy_skip_ptr(memo))
|
||
|
||
result._frame.buffer = ctypes.cast(result._buffer, ctypes.c_void_p)
|
||
result._frame.bufferSize = sizeof(result._buffer)
|
||
|
||
return result
|
||
|
||
def _set_buffer(self, buffer: ctypes.c_void_p):
|
||
"""Set self._buffer to memory pointed to by passed buffer pointer
|
||
|
||
Useful if frames were allocated with AllocationMode.AllocAndAnnounce
|
||
"""
|
||
self._buffer = ctypes.cast(buffer,
|
||
ctypes.POINTER(ctypes.c_ubyte * self._frame.bufferSize)).contents
|
||
|
||
def get_buffer(self) -> ctypes.Array:
|
||
"""Get internal buffer object containing image data."""
|
||
return self._buffer
|
||
|
||
def get_buffer_size(self) -> int:
|
||
"""Get byte size of internal buffer."""
|
||
return self._frame.bufferSize
|
||
|
||
def get_image_size(self) -> int:
|
||
"""Get byte size of image data stored in buffer."""
|
||
return self._frame.imageSize
|
||
|
||
def get_ancillary_data(self) -> Optional[AncillaryData]:
|
||
"""Get AncillaryData.
|
||
|
||
Frames acquired with cameras where Feature ChunkModeActive is enabled can contain
|
||
ancillary data within the image data.
|
||
|
||
Returns:
|
||
None if Frame contains no ancillary data.
|
||
AncillaryData if Frame contains ancillary data.
|
||
"""
|
||
if not self._frame.ancillarySize:
|
||
return None
|
||
|
||
return AncillaryData(self._frame)
|
||
|
||
def get_status(self) -> FrameStatus:
|
||
"""Returns current frame status."""
|
||
return FrameStatus(self._frame.receiveStatus)
|
||
|
||
def get_pixel_format(self) -> PixelFormat:
|
||
"""Get format of the acquired image data"""
|
||
return PixelFormat(self._frame.pixelFormat)
|
||
|
||
def get_height(self) -> Optional[int]:
|
||
"""Get image height in pixels.
|
||
|
||
Returns:
|
||
Image height in pixels if dimension data is provided by the camera.
|
||
None if dimension data is not provided by the camera.
|
||
"""
|
||
flags = decode_flags(VmbFrameFlags, self._frame.receiveFlags)
|
||
|
||
if VmbFrameFlags.Dimension not in flags:
|
||
return None
|
||
|
||
return self._frame.height
|
||
|
||
def get_width(self) -> Optional[int]:
|
||
"""Get image width in pixels.
|
||
|
||
Returns:
|
||
Image width in pixels if dimension data is provided by the camera.
|
||
None if dimension data is not provided by the camera.
|
||
"""
|
||
flags = decode_flags(VmbFrameFlags, self._frame.receiveFlags)
|
||
|
||
if VmbFrameFlags.Dimension not in flags:
|
||
return None
|
||
|
||
return self._frame.width
|
||
|
||
def get_offset_x(self) -> Optional[int]:
|
||
"""Get horizontal offset in pixels.
|
||
|
||
Returns:
|
||
Horizontal offset in pixel if offset data is provided by the camera.
|
||
None if offset data is not provided by the camera.
|
||
"""
|
||
flags = decode_flags(VmbFrameFlags, self._frame.receiveFlags)
|
||
|
||
if VmbFrameFlags.Offset not in flags:
|
||
return None
|
||
|
||
return self._frame.offsetX
|
||
|
||
def get_offset_y(self) -> Optional[int]:
|
||
"""Get vertical offset in pixels.
|
||
|
||
Returns:
|
||
Vertical offset in pixels if offset data is provided by the camera.
|
||
None if offset data is not provided by the camera.
|
||
"""
|
||
flags = decode_flags(VmbFrameFlags, self._frame.receiveFlags)
|
||
|
||
if VmbFrameFlags.Offset not in flags:
|
||
return None
|
||
|
||
return self._frame.offsetY
|
||
|
||
def get_id(self) -> Optional[int]:
|
||
"""Get Frame ID.
|
||
|
||
Returns:
|
||
Frame ID if the id is provided by the camera.
|
||
None if frame id is not provided by the camera.
|
||
"""
|
||
flags = decode_flags(VmbFrameFlags, self._frame.receiveFlags)
|
||
|
||
if VmbFrameFlags.FrameID not in flags:
|
||
return None
|
||
|
||
return self._frame.frameID
|
||
|
||
def get_timestamp(self) -> Optional[int]:
|
||
"""Get Frame timestamp.
|
||
|
||
Returns:
|
||
Timestamp if provided by the camera.
|
||
None if timestamp is not provided by the camera.
|
||
"""
|
||
flags = decode_flags(VmbFrameFlags, self._frame.receiveFlags)
|
||
|
||
if VmbFrameFlags.Timestamp not in flags:
|
||
return None
|
||
|
||
return self._frame.timestamp
|
||
|
||
@RuntimeTypeCheckEnable()
|
||
def convert_pixel_format(self, target_fmt: PixelFormat,
|
||
debayer_mode: Optional[Debayer] = None):
|
||
"""Convert internal pixel format to given format.
|
||
|
||
Note: This method allocates a new buffer for internal image data leading to some
|
||
runtime overhead. For performance reasons, it might be better to set the value
|
||
of the camera's 'PixelFormat' feature instead. In addition, a non-default debayer mode
|
||
can be specified.
|
||
|
||
Arguments:
|
||
target_fmt - PixelFormat to convert to.
|
||
debayer_mode - Non-default algorithm used to debayer images in Bayer Formats. If
|
||
no mode is specified, default debayering mode 'Mode2x2' is applied. If
|
||
the current format is no Bayer format, this parameter is silently
|
||
ignored.
|
||
|
||
Raises:
|
||
TypeError if parameters do not match their type hint.
|
||
ValueError if the current format can't be converted into 'target_fmt'. Convertible
|
||
Formats can be queried via get_convertible_formats() of PixelFormat.
|
||
AssertionError if image width or height can't be determined.
|
||
"""
|
||
|
||
global BAYER_PIXEL_FORMATS
|
||
|
||
# 1) Perform sanity checking
|
||
fmt = self.get_pixel_format()
|
||
|
||
if fmt == target_fmt:
|
||
return
|
||
|
||
if target_fmt not in fmt.get_convertible_formats():
|
||
raise ValueError('Current PixelFormat can\'t be converted into given format.')
|
||
|
||
# 2) Specify Transformation Input Image
|
||
height = self._frame.height
|
||
width = self._frame.width
|
||
|
||
c_src_image = VmbImage()
|
||
c_src_image.Size = sizeof(c_src_image)
|
||
c_src_image.Data = ctypes.cast(self._buffer, ctypes.c_void_p)
|
||
|
||
call_vimba_image_transform('VmbSetImageInfoFromPixelFormat', fmt, width, height,
|
||
byref(c_src_image))
|
||
|
||
# 3) Specify Transformation Output Image
|
||
c_dst_image = VmbImage()
|
||
c_dst_image.Size = sizeof(c_dst_image)
|
||
|
||
layout, bits = PIXEL_FORMAT_TO_LAYOUT[VmbPixelFormat(target_fmt)]
|
||
|
||
call_vimba_image_transform('VmbSetImageInfoFromInputImage', byref(c_src_image), layout,
|
||
bits, byref(c_dst_image))
|
||
|
||
# 4) Allocate Buffer and perform transformation
|
||
img_size = int(height * width * c_dst_image.ImageInfo.PixelInfo.BitsPerPixel / 8)
|
||
anc_size = self._frame.ancillarySize
|
||
|
||
buf = (ctypes.c_ubyte * (img_size + anc_size))()
|
||
c_dst_image.Data = ctypes.cast(buf, ctypes.c_void_p)
|
||
|
||
# 5) Setup Debayering mode if given.
|
||
transform_info = VmbTransformInfo()
|
||
if debayer_mode and (fmt in BAYER_PIXEL_FORMATS):
|
||
call_vimba_image_transform('VmbSetDebayerMode', VmbDebayerMode(debayer_mode),
|
||
byref(transform_info))
|
||
|
||
# 6) Perform Transformation
|
||
call_vimba_image_transform('VmbImageTransform', byref(c_src_image), byref(c_dst_image),
|
||
byref(transform_info), 1)
|
||
|
||
# 7) Copy ancillary data if existing
|
||
if anc_size:
|
||
src = ctypes.addressof(self._buffer) + self._frame.imageSize
|
||
dst = ctypes.addressof(buf) + img_size
|
||
|
||
ctypes.memmove(dst, src, anc_size)
|
||
|
||
# 8) Update frame metadata
|
||
self._buffer = buf
|
||
self._frame.buffer = ctypes.cast(self._buffer, ctypes.c_void_p)
|
||
self._frame.bufferSize = sizeof(self._buffer)
|
||
self._frame.imageSize = img_size
|
||
self._frame.pixelFormat = target_fmt
|
||
|
||
def as_numpy_ndarray(self) -> 'numpy.ndarray':
|
||
"""Construct numpy.ndarray view on VimbaFrame.
|
||
|
||
Returns:
|
||
numpy.ndarray on internal image buffer.
|
||
|
||
Raises:
|
||
ImportError if numpy is not installed.
|
||
VimbaFrameError if current PixelFormat can't be converted to a numpy.ndarray.
|
||
"""
|
||
if numpy is None:
|
||
raise ImportError('\'Frame.as_opencv_image()\' requires module \'numpy\'.')
|
||
|
||
# Construct numpy overlay on underlaying image buffer
|
||
height = self._frame.height
|
||
width = self._frame.width
|
||
fmt = self._frame.pixelFormat
|
||
|
||
c_image = VmbImage()
|
||
c_image.Size = sizeof(c_image)
|
||
|
||
call_vimba_image_transform('VmbSetImageInfoFromPixelFormat', fmt, width, height,
|
||
byref(c_image))
|
||
|
||
layout = PIXEL_FORMAT_TO_LAYOUT.get(fmt)
|
||
|
||
if not layout:
|
||
msg = 'Can\'t construct numpy.ndarray for Pixelformat {}. ' \
|
||
'Use \'frame.convert_pixel_format()\' to convert to a different Pixelformat.'
|
||
raise VimbaFrameError(msg.format(str(self.get_pixel_format())))
|
||
|
||
bits_per_channel = layout[1]
|
||
channels_per_pixel = c_image.ImageInfo.PixelInfo.BitsPerPixel // bits_per_channel
|
||
|
||
return numpy.ndarray(shape=(height, width, channels_per_pixel),
|
||
buffer=self._buffer, # type: ignore
|
||
dtype=numpy.uint8 if bits_per_channel == 8 else numpy.uint16)
|
||
|
||
def as_opencv_image(self) -> 'numpy.ndarray':
|
||
"""Construct OpenCV compatible view on VimbaFrame.
|
||
|
||
Returns:
|
||
OpenCV compatible numpy.ndarray
|
||
|
||
Raises:
|
||
ImportError if numpy is not installed.
|
||
ValueError if current pixel format is not compatible with opencv. Compatible
|
||
formats are in OPENCV_PIXEL_FORMATS.
|
||
"""
|
||
global OPENCV_PIXEL_FORMATS
|
||
|
||
if numpy is None:
|
||
raise ImportError('\'Frame.as_opencv_image()\' requires module \'numpy\'.')
|
||
|
||
fmt = self._frame.pixelFormat
|
||
|
||
if fmt not in OPENCV_PIXEL_FORMATS:
|
||
raise ValueError('Current Format \'{}\' is not in OPENCV_PIXEL_FORMATS'.format(
|
||
str(PixelFormat(self._frame.pixelFormat))))
|
||
|
||
return self.as_numpy_ndarray()
|
||
|
||
|
||
@TraceEnable()
|
||
@RuntimeTypeCheckEnable()
|
||
def intersect_pixel_formats(fmts1: FormatTuple, fmts2: FormatTuple) -> FormatTuple:
|
||
"""Build intersection of two sets containing PixelFormat.
|
||
|
||
Arguments:
|
||
fmts1 - PixelFormats to intersect with fmts2
|
||
fmts2 - PixelFormats to intersect with fmts1
|
||
|
||
Returns:
|
||
Set of PixelFormats that occur in fmts1 and fmts2
|
||
|
||
Raises:
|
||
TypeError if parameters do not match their type hint.
|
||
"""
|
||
return tuple(set(fmts1).intersection(set(fmts2)))
|