This commit is contained in:
alexgobbo
2024-09-11 16:17:17 +02:00
parent 4c44934e04
commit 75400ca453
49 changed files with 1507 additions and 593 deletions

View File

@@ -0,0 +1,275 @@
import sys
import time
import argparse
import socket
from bsread.sender import sender, PUSH, PUB
from typing import Optional, Tuple
from logging import getLogger
from vmbpy import *
from flask import Flask, request, jsonify
import threading
_logger = getLogger(__name__)
def can_bind_to_port(port, host='0.0.0.0'):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
try:
# Attempt to bind to the host and port
sock.bind((host, port))
return True # If binding is successful, the port is available
except OSError:
return False # If an OSError occurs, the port is not available
class VmbPyStreamServer():
def __init__(self, id=None, port=None, alloc=None, start = None):
self.id = id
self.port = port if port else 9000
self.alloc = AllocationMode.AllocAndAnnounceFrame if alloc else AllocationMode.AnnounceFrame
self.camera = None
self.handler_exception = None
self.exit_cmd = False
self.start_streaming = True if (start or (start is None)) else None
if self.port > 0:
if not can_bind_to_port(self.port):
raise Exception(f"Stream port {self.port} is not free")
def open_camera(self, camera_id: Optional[str]) -> Camera:
self.camera = None
self.handler_exception = None
with VmbSystem.get_instance() as vmb:
if camera_id:
try:
return vmb.get_camera_by_id(camera_id)
except VmbCameraError:
abort('Failed to access Camera \'{}\'. Abort.'.format(camera_id))
else:
cams = vmb.get_all_cameras()
if not cams:
abort('No Cameras accessible. Abort.')
return cams[0]
def init_camera(self):
#cam.set_pixel_format(PixelFormat.BayerGR8)
self.camera.set_pixel_format(PixelFormat.Mono8)
# Try to adjust GeV packet size. This Feature is only available for GigE - Cameras.
try:
stream = self.camera.get_streams()[0]
stream.GVSPAdjustPacketSize.run()
while not stream.GVSPAdjustPacketSize.is_done():
pass
except (AttributeError, VmbFeatureError):
pass
def get_camera_features(self, readonly=True, readwrite=True):
if self.camera is None:
raise Exception("No camera instantiated")
features = self.camera.get_all_features()
pars = {}
for f in features:
try:
name, v, (r, w) = f.get_name(), f.get(), f.get_access_mode()
ro, rw = not w, w
if (readonly and ro) or (readwrite and rw):
if type(v) is EnumEntry:
pars[name] = str(v)
else:
pars[name] = v
except:
pass
return pars
def get_camera_feature(self,name):
pars = self.get_camera_features()
if name in pars:
return pars[name]
else:
raise Exception( f"Invalid parameter: {name}")
def set_camera_feature(self, name, value):
feature = self.camera.get_feature_by_name(name)
feature.set(value)
def start_stream(self, publisher):
def frame_handler(cam: Camera, stream: Stream, frame: Frame):
try:
fmt = frame.get_pixel_format()
if fmt == PixelFormat.Mono8:
arr = frame.as_numpy_ndarray()
else:
arr = frame.convert_pixel_format(PixelFormat.Mono8).as_numpy_ndarray()
arr = arr[:, :, 0]
data = {"image": arr}
publisher.send(data=data, pulse_id=None, timestamp=time.time(), check_data=True)
cam.queue_frame(frame)
except Exception as e:
self.handler_exception = e
if not self.is_streaming():
self.camera.start_streaming(handler=frame_handler,
buffer_count=10,
allocation_mode=self.alloc)
def stop_stream(self):
if self.is_streaming():
self.camera.stop_streaming()
def is_streaming(self):
return self.camera.is_streaming()
def set_streaming(self, value):
if value!= self.is_streaming():
self.start_streaming = value
def run(self, retry=False):
while True:
try:
self.camera = None
self.handler_exception = None
with sender(queue_size=10, port=self.port, mode=PUB, start_pulse_id=1,
data_header_compression=None) as publisher:
with VmbSystem.get_instance():
with self.open_camera(self.id) as self.camera:
self.init_camera()
_logger.info(f"Connected to {self.camera}")
try:
while True:
if self.start_streaming is not None:
if self.start_streaming:
self.start_stream(publisher)
else:
self.stop_stream()
self.start_streaming = None
if self.handler_exception is not None:
raise self.handler_exception
time.sleep(0.1)
if self.exit_cmd:
break
finally:
self.stop_stream()
except Exception as e:
_logger.exception(e)
if not retry or self.exit_cmd:
break
time.sleep(3.0)
_logger.info("Restarting")
def start(self):
self.thread = threading.Thread(target=self.run, daemon=True)
self.thread.start()
def exit(self):
_logger.info(f"Received exit command")
self.exit_cmd = True
class VmbPyConfigServer():
def __init__(self, stream_server, port=None):
self.stream_server = stream_server
self.port = port if port else 9010
if self.port>0:
if not can_bind_to_port(self.port):
raise Exception(f"Config port {self.port} is not free")
def run(self):
if self.port > 0:
app = Flask(__name__)
@app.route('/api/parameters', methods=['GET'])
def get_parameters():
def str_to_bool(val):
return not str(val).lower() in ('false', '1', 'no')
try:
ro = str_to_bool(request.args.get("ro", True))
rw = str_to_bool(request.args.get("rw", True))
return jsonify(self.stream_server.get_camera_features(ro, rw))
except Exception as e:
return jsonify({"error": str(e)}), 404
# Route to get a specific parameter
@app.route('/api/parameters/<name>', methods=['GET'])
def get_parameter(name):
try:
value = self.stream_server.get_camera_feature(name)
return jsonify({name: value})
except Exception as e:
return jsonify({"error": str(e)}), 404
# Route to update a specific parameter
@app.route('/api/parameters/<name>', methods=['PUT'])
def update_parameter(name):
try:
pars = self.stream_server.get_camera_features(False, True)
if name in pars:
new_value = request.json.get('value')
self.stream_server.set_camera_feature(name, new_value)
updated_value = self.stream_server.get_camera_feature(name)
return jsonify({name: updated_value})
else:
return jsonify({"error": f"Not a writable parameter: {name}"}), 404
except Exception as e:
return jsonify({"error": str(e)}), 404
@app.route('/api/streaming', methods=['GET'])
def get_streaming():
try:
return jsonify({"value": self.stream_server.is_streaming()})
except Exception as e:
return jsonify({"error": str(e)}), 404
# Route to update a specific parameter
@app.route('/api/streaming', methods=['PUT'])
def update_streaming():
try:
self.stream_server.set_streaming(request.json.get('value'))
return jsonify({"value": self.stream_server.is_streaming()})
except Exception as e:
return jsonify({"error": str(e)}), 404
@app.route('/api/exit', methods=['GET'])
def exit():
self.stream_server.exit()
return jsonify({"exit": "ok"})
app.run(host='0.0.0.0', port=self.port, debug=False, use_reloader=False)
def start(self):
self.thread = threading.Thread(target=self.run, daemon=True)
self.thread.start()
def print_preamble():
print('///////////////////////////////////////')
print('/// VmbPy Camera Server ///')
print('///////////////////////////////////////\n')
def abort(reason: str, return_code: int = 1, usage: bool = False):
print(reason + '\n')
sys.exit(return_code)
def parse_args():
usage = None
desc=None
epilog=None
parser = argparse.ArgumentParser(usage=usage, description=desc, prefix_chars='--', epilog=epilog)
parser.add_argument("-i", "--id", help="Camera ID (default first camera)", required=False)
parser.add_argument("-p", "--port", help="Output stream port (default 9000)", required=False)
parser.add_argument("-c", "--config", help="Config port (default 9010, -1 for disabling config interface)", required=False)
parser.add_argument("-a", "--alloc", action='store_true', help="Alloc frames", required=False)
parser.add_argument("-s", "--start", action='store_true', help="Start streaming (default true)", default= True, required=False)
return parser.parse_args()
def main():
print_preamble()
args = parse_args()
stream_server = VmbPyStreamServer(args.id, args.port, args.alloc, args.start)
config_server = VmbPyConfigServer(stream_server, args.config)
config_server.start()
stream_server.run(retry=True)
sys.exit(0)
if __name__ == '__main__':
#args = ["-h"]
#sys.argv = sys.argv + args
main()