plugin system docs
custom buttons for plugins calibration functions for Leeform
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Channels;
|
||||
using OpenCvSharp;
|
||||
using VisionBuilder.UI.Common.Processing;
|
||||
using VisionBuilder.UI.RaspberryAcquisition.Models;
|
||||
|
||||
namespace VisionBuilder.UI.RaspberryAcquisition.Services;
|
||||
|
||||
public class ConfigurableLibcameraSource : IImageSource, IDisposable
|
||||
{
|
||||
private static readonly byte[] JpegHeader = [0xff, 0xd8];
|
||||
private static readonly byte[] JpegFooter = [0xff, 0xd9];
|
||||
private const int ChunkSize = 1024;
|
||||
|
||||
private readonly Channel<Mat> _imageChannel = Channel.CreateBounded<Mat>(
|
||||
new BoundedChannelOptions(1) { FullMode = BoundedChannelFullMode.DropOldest });
|
||||
|
||||
private CaptureSettings _settings;
|
||||
private CancellationTokenSource? _cts;
|
||||
private Thread? _captureThread;
|
||||
|
||||
public int FrameCount { get; private set; }
|
||||
public event Action<string>? StatusChanged;
|
||||
|
||||
public ConfigurableLibcameraSource(CaptureSettings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public Task<Mat> GetImage(CancellationToken token)
|
||||
{
|
||||
return _imageChannel.Reader.ReadAsync(token).AsTask();
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
Stop();
|
||||
FrameCount = 0;
|
||||
_cts = new CancellationTokenSource();
|
||||
_captureThread = new Thread(CaptureLoop) { IsBackground = true };
|
||||
_captureThread.Start();
|
||||
StatusChanged?.Invoke("Running");
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_cts?.Cancel();
|
||||
_captureThread?.Join(2000);
|
||||
_cts?.Dispose();
|
||||
_cts = null;
|
||||
_captureThread = null;
|
||||
StatusChanged?.Invoke("Stopped");
|
||||
}
|
||||
|
||||
public void UpdateSettings(CaptureSettings newSettings)
|
||||
{
|
||||
_settings = newSettings;
|
||||
Start();
|
||||
}
|
||||
|
||||
private void CaptureLoop()
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = "rpicam-vid",
|
||||
Arguments = _settings.BuildArguments(),
|
||||
RedirectStandardOutput = true,
|
||||
UseShellExecute = false
|
||||
};
|
||||
|
||||
Process? process = null;
|
||||
try
|
||||
{
|
||||
process = Process.Start(psi);
|
||||
if (process == null)
|
||||
{
|
||||
StatusChanged?.Invoke("Error: failed to start rpicam-vid");
|
||||
return;
|
||||
}
|
||||
|
||||
using var br = new BinaryReader(process.StandardOutput.BaseStream);
|
||||
var imageBuffer = new byte[1024 * 1024];
|
||||
var buff = br.ReadBytes(ChunkSize);
|
||||
|
||||
while (!_cts!.Token.IsCancellationRequested)
|
||||
{
|
||||
var imageStart = Find(buff, JpegHeader);
|
||||
if (imageStart == -1)
|
||||
{
|
||||
StatusChanged?.Invoke("Error: JPEG header not found");
|
||||
break;
|
||||
}
|
||||
|
||||
var size = buff.Length - imageStart;
|
||||
Array.Copy(buff, imageStart, imageBuffer, 0, size);
|
||||
|
||||
while (!_cts.Token.IsCancellationRequested)
|
||||
{
|
||||
buff = br.ReadBytes(ChunkSize);
|
||||
var imageEnd = Find(buff, JpegFooter);
|
||||
if (imageEnd != -1)
|
||||
{
|
||||
imageEnd += JpegFooter.Length;
|
||||
var frame = new byte[size + imageEnd];
|
||||
Array.Copy(imageBuffer, frame, size);
|
||||
Array.Copy(buff, 0, frame, size, imageEnd);
|
||||
|
||||
var decoded = Mat.ImDecode(frame);
|
||||
FrameCount++;
|
||||
_imageChannel.Writer.TryWrite(decoded);
|
||||
|
||||
var remaining = buff.Length - imageEnd;
|
||||
var newBuff = new byte[ChunkSize];
|
||||
Array.Copy(buff, imageEnd, newBuff, 0, remaining);
|
||||
var temp = br.ReadBytes(imageEnd);
|
||||
Array.Copy(temp, 0, newBuff, remaining, temp.Length);
|
||||
buff = newBuff;
|
||||
break;
|
||||
}
|
||||
|
||||
Array.Copy(buff, 0, imageBuffer, size, buff.Length);
|
||||
size += buff.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||
{
|
||||
StatusChanged?.Invoke($"Error: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (process is { HasExited: false })
|
||||
{
|
||||
process.Kill();
|
||||
process.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int Find(byte[] buff, byte[] search)
|
||||
{
|
||||
for (int start = 0; start <= buff.Length - search.Length; start++)
|
||||
{
|
||||
if (buff[start] == search[0])
|
||||
{
|
||||
int next;
|
||||
for (next = 1; next < search.Length; next++)
|
||||
{
|
||||
if (buff[start + next] != search[next])
|
||||
break;
|
||||
}
|
||||
if (next == search.Length)
|
||||
return start;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user