-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7 from elerac/next
Next ver2.0.0
- Loading branch information
Showing
14 changed files
with
1,201 additions
and
644 deletions.
There are no files selected for viewing
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
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 |
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
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
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() |
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
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() |
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
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) |
Oops, something went wrong.