using OpenCvSharp;
using Godot;
using ETPreferences;
using System;
public partial class DebugViewer : EyeTrackingConsumer
{
// One Euro Filters for smoothing eye tracking data
private OneEuroFilter _leftEyeXFilter;
private OneEuroFilter _leftEyeYFilter;
private OneEuroFilter _rightEyeXFilter;
private OneEuroFilter _rightEyeYFilter;
// Filter configuration
[Export]
public float MinCutoff { get; set; } = 1.0f; // Minimum cutoff frequency
[Export]
public float Beta { get; set; } = ET_Preferences.filter_default; // Speed coefficient
[Export]
public float DCutoff { get; set; } = 1.0f; // Derivative cutoff frequency
///
/// Initializes a new instance of the DebugViewer class
///
/// The name of this consumer
public DebugViewer(string name = "Debug Viewer") : base(name)
{
InitializeFilters();
}
///
/// Initialize filter instances
///
public void InitializeFilters()
{
// Set coefficient from user preference
if (ET_Preferences.Instance != null && float.TryParse(ET_Preferences.Instance.smoothing_intensity, out float parsed))
{
Beta = Math.Clamp(parsed, 0, 1);
}
_leftEyeXFilter = new OneEuroFilter(MinCutoff, Beta, DCutoff);
_leftEyeYFilter = new OneEuroFilter(MinCutoff, Beta, DCutoff);
_rightEyeXFilter = new OneEuroFilter(MinCutoff, Beta, DCutoff);
_rightEyeYFilter = new OneEuroFilter(MinCutoff, Beta, DCutoff);
}
public override void OnEyeDataUpdate(EyeTrackingData data)
{
base.OnEyeDataUpdate(data);
// Close the viewers if they are active
if (!Enabled)
{
if (Cv2.GetWindowProperty("Left Eye", WindowPropertyFlags.Visible) >= 1)
{
Cv2.DestroyWindow("Left Eye");
}
if (Cv2.GetWindowProperty("Right Eye", WindowPropertyFlags.Visible) >= 1)
{
Cv2.DestroyWindow("Right Eye");
}
}
}
protected override void ProcessData(EyeTrackingData data)
{
// Get current time for filtering
float currentTime = (float)Time.GetTicksMsec() / 1000.0f;
// User preference for smoothing filter
bool filterDisabled = ET_Preferences.Instance != null && !ET_Preferences.Instance.smoothing_enabled;
BlinkMode blinkMode = EyeTrackingPreferences.GetBlinkMode();
bool isBlinkFrame = data.HasAnyFlagCondition();
bool skipFilterForBlink = (blinkMode == BlinkMode.Blinking || blinkMode == BlinkMode.Winking) && isBlinkFrame;
// Apply one euro filter to smooth eye positions
float leftX = (filterDisabled || skipFilterForBlink) ? data.LeftEyeX : _leftEyeXFilter.Filter(data.LeftEyeX, currentTime);
float leftY = (filterDisabled || skipFilterForBlink) ? data.LeftEyeY : _leftEyeYFilter.Filter(data.LeftEyeY, currentTime);
float rightX = (filterDisabled || skipFilterForBlink) ? data.RightEyeX : _rightEyeXFilter.Filter(data.RightEyeX, currentTime);
float rightY = (filterDisabled || skipFilterForBlink) ? data.RightEyeY : _rightEyeYFilter.Filter(data.RightEyeY, currentTime);
if (data.LeftEyeImage != null)
{
Mat leftEyeWithOverlay = new Mat();
Cv2.Flip(data.LeftEyeImage, leftEyeWithOverlay, FlipMode.Y);
DrawGrid(leftEyeWithOverlay);
// Draw raw eye position (white)
DrawEyePosition(leftEyeWithOverlay, data.LeftEyeX, data.LeftEyeY, new Scalar(100, 100, 100), "Raw");
// Draw filtered eye position (green)
DrawEyePosition(leftEyeWithOverlay, leftX, leftY, new Scalar(255, 255, 255), "Filtered", new System.Numerics.Vector2(0, -15));
Cv2.ImShow("Left Eye", leftEyeWithOverlay);
}
if (data.RightEyeImage != null)
{
Mat rightEyeWithOverlay = new Mat();
Cv2.Flip(data.RightEyeImage, rightEyeWithOverlay, FlipMode.Y);
DrawGrid(rightEyeWithOverlay);
// Draw raw eye position (white)
DrawEyePosition(rightEyeWithOverlay, data.RightEyeX, data.RightEyeY, new Scalar(100, 100, 100), "Raw");
// Draw filtered eye position (green)
DrawEyePosition(rightEyeWithOverlay, rightX, rightY, new Scalar(255, 255, 255), "Filtered", new System.Numerics.Vector2(0, -15));
Cv2.ImShow("Right Eye", rightEyeWithOverlay);
}
Cv2.WaitKey(1); // Required to update window
}
private void DrawGrid(Mat image)
{
int height = image.Height;
int width = image.Width;
// Draw vertical lines
for (int i = 0; i < 5; i++)
{
int x = (int)(width * i / 4.0);
Cv2.Line(image, new Point(x, 0), new Point(x, height), new Scalar(128, 128, 128), 1);
// Draw coordinate
float coord = (float)i / 4.0f;
Cv2.PutText(
image,
$"{coord:F1}",
new Point(x + 5, 20),
HersheyFonts.HersheySimplex,
0.5,
new Scalar(255, 255, 255),
1
);
}
// Draw horizontal lines
for (int i = 0; i < 5; i++)
{
int y = (int)(height * i / 4.0);
Cv2.Line(image, new Point(0, y), new Point(width, y), new Scalar(128, 128, 128), 1);
// Draw coordinate
float coord = (float)i / 4.0f;
Cv2.PutText(
image,
$"{coord:F1}",
new Point(5, y + 20),
HersheyFonts.HersheySimplex,
0.5,
new Scalar(255, 255, 255),
1
);
}
}
private void DrawEyePosition(Mat image, float eyeX, float eyeY, Scalar color, string label, System.Numerics.Vector2 labelOffset = new System.Numerics.Vector2())
{
int height = image.Height;
int width = image.Width;
// Convert normalized coordinates to pixel coordinates
int pixelX = (int)(eyeX * width);
int pixelY = (int)((1 - eyeY) * height); // Flip Y coordinate (0 at top in image)
// Draw circle at eye position
Cv2.Circle(image, new Point(pixelX, pixelY), 5, color, -1);
// Draw coordinate values with label
Cv2.PutText(
image,
$"{label}: ({eyeX:F2}, {eyeY:F2})",
new Point(pixelX + 10 + labelOffset.X, pixelY + labelOffset.Y),
HersheyFonts.HersheySimplex,
0.5,
color,
1
);
}
}