Skip to content

Commit

Permalink
Merge pull request #7 from elerac/next
Browse files Browse the repository at this point in the history
Next ver2.0.0
  • Loading branch information
elerac authored Aug 8, 2021
2 parents 76192a9 + ee2e1cc commit f1c97b1
Show file tree
Hide file tree
Showing 14 changed files with 1,201 additions and 644 deletions.
4 changes: 3 additions & 1 deletion EasyPySpin/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from .videocapture import VideoCapture
from .synchronizedvideocapture import SynchronizedVideoCapture
from .videocaptureex import VideoCaptureEX
from .multiplevideocapture import MultipleVideoCapture
from .synchronizedvideocapture import SynchronizedVideoCapture
from .utils import EasyPySpinWarning
44 changes: 25 additions & 19 deletions EasyPySpin/command_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,25 @@
import cv2
import argparse


def print_xy(event, x, y, flags, param):
"""
Export xy coordinates in csv format by clicking
"""
if event==cv2.EVENT_LBUTTONDOWN:
if event == cv2.EVENT_LBUTTONDOWN:
scale = param
print(f"{int(x/scale)}, {int(y/scale)}")


def main():
parser = argparse.ArgumentParser()
parser.add_argument("-i", "--index", type=int, default=0, help="Camera index (Default: 0)")
parser.add_argument("-e", "--exposure",type=float, default=-1, help="Exposure time [us] (Default: Auto)")
parser.add_argument("-g", "--gain", type=float, default=-1, help="Gain [dB] (Default: Auto)")
parser.add_argument("-G", "--gamma", type=float, help="Gamma value")
parser.add_argument("-b", "--brightness", type=float, help="Brightness [EV]")
parser.add_argument("-f", "--fps", type=float, help="FrameRate [fps]")
parser.add_argument("-s", "--scale", type=float, default=0.25, help="Image scale to show (>0) (Default: 0.25)")
parser.add_argument("-i", "--index", type=int, default=0, help="Camera index (Default: 0)")
parser.add_argument("-e", "--exposure", type=float, default=-1, help="Exposure time [us] (Default: Auto)")
parser.add_argument("-g", "--gain", type=float, default=-1, help="Gain [dB] (Default: Auto)")
parser.add_argument("-G", "--gamma", type=float, help="Gamma value")
parser.add_argument("-b", "--brightness", type=float, help="Brightness [EV]")
parser.add_argument("-f", "--fps", type=float, help="FrameRate [fps]")
parser.add_argument("-s", "--scale", type=float, default=.25, help="Image scale to show (>0) (Default: 0.25)")
args = parser.parse_args()

# Instance creation
Expand All @@ -31,14 +33,17 @@ def main():
if not cap.isOpened():
print("Camera can't open\nexit")
return -1

# Set the camera parameters
cap.set(cv2.CAP_PROP_EXPOSURE, args.exposure) #-1 sets exposure_time to auto
cap.set(cv2.CAP_PROP_GAIN, args.gain) #-1 sets gain to auto
if args.gamma is not None: cap.set(cv2.CAP_PROP_GAMMA, args.gamma)
if args.fps is not None: cap.set(cv2.CAP_PROP_FPS, args.fps)
if args.brightness is not None: cap.set(cv2.CAP_PROP_BRIGHTNESS, args.brightness)

cap.set(cv2.CAP_PROP_EXPOSURE, args.exposure) # -1 sets exposure_time to auto
cap.set(cv2.CAP_PROP_GAIN, args.gain) # -1 sets gain to auto
if args.gamma is not None:
cap.set(cv2.CAP_PROP_GAMMA, args.gamma)
if args.fps is not None:
cap.set(cv2.CAP_PROP_FPS, args.fps)
if args.brightness is not None:
cap.set(cv2.CAP_PROP_BRIGHTNESS, args.brightness)

# Window setting
winname = "press q to quit"
cv2.namedWindow(winname)
Expand All @@ -48,16 +53,17 @@ def main():
# Start capturing
while True:
ret, frame = cap.read()
#frame = cv2.cvtColor(frame, cv2.COLOR_BayerBG2BGR) #for RGB camera demosaicing
# frame = cv2.cvtColor(frame, cv2.COLOR_BayerBG2BGR) #for RGB camera demosaicing

img_show = cv2.resize(frame, None, fx=args.scale, fy=args.scale)
cv2.imshow(winname, img_show)
key = cv2.waitKey(30)
if key==ord("q"):
if key == ord("q"):
break

cv2.destroyAllWindows()
cap.release()

if __name__=="__main__":

if __name__ == "__main__":
main()
91 changes: 91 additions & 0 deletions EasyPySpin/multiplevideocapture.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from typing import List, Tuple, Union, Any
from concurrent.futures import ThreadPoolExecutor

import numpy as np

from .videocapture import VideoCapture as EasyPySpinVideoCapture


class MultipleVideoCapture:
"""VideoCapture for multiple cameras.
Examples
--------
>>> cap = MultipleVideoCapture(0, 1)
>>> cap.isOpened()
[True, True]
>>> cap.set(cv2.CAP_PROP_EXPOSURE, 1000)
[True, True]
>>> cap.get(cv2.CAP_PROP_EXPOSURE)
[1000.0, 1000.0]
>>> cap[0].set(cv2.CAP_PROP_EXPOSURE, 2000)
True
>>> cap.get(cv2.CAP_PROP_EXPOSURE)
[2000.0, 1000.0]
>>> (ret0, frame0), (ret1, frame1) = cap.read()
>>> cap.release()
Add camera after initialization
>>> cap = MultipleVideoCapture(0, 1) # open two cameras
>>> cap.isOpened()
[True, True]
>>> cap.open(2) # add a camera
>>> cap.isOpened()
[True, True, True]
Open camera as arbitrary VideoCapture
>>> cap = MultipleVideoCapture()
>>> cap.open(0, 1, VideoCapture=EasyPySpin.VideoCaptureEX)
>>> cap.isOpened()
[True, True]
>>> cap.average_num = 5 # Set attribute of VideoCaptureEX
>>> cap.open(0, VideoCapture=cv2.VideoCapture)
>>> cap.isOpened()
[True, True, True]
"""

__caps = list()
__executor = ThreadPoolExecutor()

def __init__(self, *indexes: Tuple[Union[int, str], ...]):
self.open(*indexes)

def __len__(self):
return self.__caps.__len__()

def __getitem__(self, item):
return self.__caps.__getitem__(item)

def __iter__(self):
return self.__caps.__iter__()

def __next__(self):
return self.__caps.__next__()

def __setattr__(self, key, value):
for cap in self:
if hasattr(cap, key):
setattr(cap, key, value)

return object.__setattr__(self, key, value)

def __getattr__(self, name):
def method(*args, **kwargs) -> List[Any]:
futures = [
self.__executor.submit(getattr(cap, name), *args, **kwargs)
for cap in self
]
return [future.result() for future in futures]

return method

def open(
self, *indexs: Tuple[Union[int, str], ...], VideoCapture=EasyPySpinVideoCapture
) -> List[bool]:
for index in indexs:
cap = VideoCapture(index)
self.__caps.append(cap)

return self.isOpened()
174 changes: 62 additions & 112 deletions EasyPySpin/synchronizedvideocapture.py
Original file line number Diff line number Diff line change
@@ -1,118 +1,68 @@
import PySpin
from typing import Tuple, Union

class SynchronizedVideoCapture:
"""
Hardware synchronized video capturing.
It can be handled in the same way as the "VideoCapture" class and the return value is stored in the list.
You can find instructions on how to connect the camera in FLIR official page.
[https://www.flir.com/support-center/iis/machine-vision/application-note/configuring-synchronized-capture-with-multiple-cameras]
NOTE : Currently, only two cameras (primary and secondary) are supported, but I would like to support multiple secondary cameras in the future.
NOTE : I only have the "BFS" camera, so I haven't tested it with any other camera ("BFLY", "CM3", etc...). So, if you have a problem, please send me an issue or PR.
"""
def __init__(self, cap_primary, cap_secondary):
self.cap_primary = cap_primary
self.cap_secondary = cap_secondary

self.cap_primary = self._configure_as_primary(self.cap_primary)
self.cap_secondary = self._configure_as_secondary(self.cap_secondary)

self.cap_primary.auto_software_trigger_execute = True

def __del__(self):
self.cap_primary.release()
self.cap_secondary.release()

def release(self):
self.__del__()

def isOpened(self):
return [self.cap_primary.isOpened(), self.cap_secondary.isOpened()]

def read(self):
if not self.cap_primary.cam.IsStreaming():
self.cap_primary.cam.BeginAcquisition()

if not self.cap_secondary.cam.IsStreaming():
self.cap_secondary.cam.BeginAcquisition()

if (self.cap_primary.cam.TriggerMode.GetValue()==PySpin.TriggerMode_On and
self.cap_primary.cam.TriggerSource.GetValue()==PySpin.TriggerSource_Software and
self.cap_primary.auto_software_trigger_execute==True):
self.cap_primary.cam.TriggerSoftware.Execute()
from .multiplevideocapture import MultipleVideoCapture

ret_p, frame_p = self.cap_primary.read()
ret_s, frame_s = self.cap_secondary.read()
ret = [ret_p, ret_s]
frame = [frame_p, frame_s]
return ret, frame

def set(self, propId, value):
ret_p = self.cap_primary.set(propId, value)
ret_s = self.cap_secondary.set(propId, value)
return [ret_p, ret_s]
class SynchronizedVideoCapture(MultipleVideoCapture):
"""VideoCapture for hardware synchronized cameras.
def get(self, propId):
value_p = self.cap_primary.get(propId)
value_s = self.cap_secondary.get(propId)
return [value_p, value_s]
I only have the "BFS" camera, so I haven't tested it with any other camera ("BFLY", "CM3", etc...). So, if you have a problem, please send me an issue or PR.
def _configure_as_primary(self, cap):
series_name = self._which_camera_series(cap)

# Set the output line
if series_name in ["CM3", "FL3", "GS3", "FFY-DL", "ORX"]:
# For CM3, FL3, GS3, FFY-DL, and ORX cameras,
# select Line2 from the Line Selection dropdown and set Line Mode to Output.
cap.cam.LineSelector.SetValue(PySpin.LineSelector_Line2)
cap.cam.LineMode.SetValue(PySpin.LineMode_Output)
elif series_name in ["BFS"]:
# For BFS cameras, select Line1 from the Line Selection dropdown
# and set Line Mode to Output.
cap.cam.LineSelector.SetValue(PySpin.LineSelector_Line1)
cap.cam.LineMode.SetValue(PySpin.LineMode_Output)

# For BFS and BFLY cameras enable the 3.3V line
if series_name in ["BFS"]:
# For BFS cameras from the line selection drop-down select Line2
# and check the checkbox for 3.3V Enable.
cap.cam.LineSelector.SetValue(PySpin.LineSelector_Line2)
cap.cam.V3_3Enable.SetValue(True)
elif series_name in ["BFLY"]:
# For BFLY cameras, set 3.3V Enable to true
cap.cam.V3_3Enable.SetValue(True)

return cap

def _configure_as_secondary(self, cap):
series_name = self._which_camera_series(cap)

cap.cam.TriggerMode.SetValue(PySpin.TriggerMode_Off)
cap.cam.TriggerSelector.SetValue(PySpin.TriggerSelector_FrameStart)

# Set the trigger source
if series_name in ["BFS", "CM3", "FL3", "FFY-DL", "GS3"]:
# For BFS, CM3, FL3, FFY-DL, and GS3 cameras,
# from the Trigger Source drop-down, select Line 3.
cap.cam.TriggerSource.SetValue(PySpin.TriggerSource_Line3)
elif series_name in ["ORX"]:
# For ORX cameras, from the Trigger Source drop-down, select Line 5.
cap.cam.TriggerSource.SetValue(PySpin.TriggerSource_Line5)

# From the Trigger Overlap drop-down, select Read Out.
cap.cam.TriggerOverlap.SetValue(PySpin.TriggerOverlap_ReadOut)

# From the Trigger Mode drop-down, select On.
cap.cam.TriggerMode.SetValue(PySpin.TriggerMode_On)

return cap

def _which_camera_series(self, cap):
model_name = cap.cam.DeviceModelName.GetValue()
Notes
-----
You can find instructions on how to connect the camera in FLIR official page.
https://www.flir.com/support-center/iis/machine-vision/application-note/configuring-synchronized-capture-with-multiple-cameras
Examples
--------
Case1: The pair of primary and secondary cameras.
>>> serial_number_1 = "20541712" # primary camera
>>> serial_number_2 = "19412150" # secondary camera
>>> cap = EasyPySpin.SynchronizedVideoCapture(serial_number_1, serial_number_2)
>>> cap.isOpened()
[True, True]
>>> cap.set(cv2.CAP_PROP_EXPOSURE, 1000)
[True, True]
>>> cap.get(cv2.CAP_PROP_EXPOSURE)
[1000.0, 1000.0]
>>> cap[0].set(cv2.CAP_PROP_EXPOSURE, 2000)
True
>>> cap.get(cv2.CAP_PROP_EXPOSURE)
[2000.0, 1000.0]
>>> (ret0, frame0), (ret1, frame1) = cap.read()
Case2: The secondary camera and external trigger.
>>> serial_number = "19412150" # secondary camera
>>> cap = EasyPySpin.SynchronizedVideoCapture(None, serial_number_2)
Case3: The two (or more) secondary cameras and external trigger.
>>> serial_number_1 = "20541712" # secondary camera 1
>>> serial_number_2 = "19412150" # secondary camera 2
>>> cap = EasyPySpin.SynchronizedVideoCapture(None, serial_number_1, serial_number_2)
"""

series_names = ["BFS", "BFLY", "CM3", "FL3", "GS3", "ORX", "FFY-DL"]
for name in series_names:
if name in model_name:
return name
return None
def __init__(
self,
index_primary: Union[int, str],
*indexes_secondary: Tuple[Union[int, str], ...]
):
if index_primary is not None:
self.open_as_primary(index_primary)

for index_secondary in indexes_secondary:
self.open_as_secondary(index_secondary)

def open_as_primary(self, index: Union[int, str]) -> bool:
self.open(index)
cap = self[-1]
cap._configure_as_primary()
return cap.isOpened()

def open_as_secondary(self, index: Union[int, str]) -> bool:
self.open(index)
cap = self[-1]
cap._configure_as_secondary()
return cap.isOpened()
12 changes: 12 additions & 0 deletions EasyPySpin/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import warnings


class EasyPySpinWarning(Warning):
pass


def warn(
message: str, category: Warning = EasyPySpinWarning, stacklevel: int = 2
) -> None:
"""Default EasyPySpin warn"""
warnings.warn(message, category, stacklevel + 1)
Loading

0 comments on commit f1c97b1

Please sign in to comment.