using Godot; using System; using System.IO; using System.Globalization; using System.Text.Json; using OpenCvSharp; /// /// Represents eye position data with left and right eye coordinates. /// public partial class EyeData { /// /// Position of the left eye (x, y). /// public Vector2 LeftPos { get; set; } /// /// Position of the right eye (x, y). /// public Vector2 RightPos { get; set; } /// /// Eyelid of the left eye (0-1). /// public float LeftOpenness { get; set; } /// /// Eyelid of the right eye (0-1). /// public float RightOpenness { get; set; } /// /// Initializes a new instance of the EyeData class. /// /// Position of the left eye. /// Position of the right eye. public EyeData(Vector2 leftPos, Vector2 rightPos) { LeftPos = leftPos; RightPos = rightPos; } /// /// Initializes a new instance of the EyeData class with default positions (0,0). /// public EyeData() : this(Vector2.Zero, Vector2.Zero) { } } /// /// Records frames and eye position data to disk. /// public class DataRecorder { private string _baseDir; private string _frameDir; private string _csvPath; private string _frustumPath; private StreamWriter _csvWriter; private bool _frustumRecorded = false; /// /// Initializes a new instance of the DataRecorder class. /// /// Base directory for recorded data. public DataRecorder(string baseDir = "recorded_data") { _baseDir = baseDir; _frameDir = Path.Combine(baseDir, "frames"); _csvPath = Path.Combine(baseDir, "eye_positions.csv"); _frustumPath = Path.Combine(baseDir, "frustum_data.json"); SetupDirectories(); } /// /// Creates necessary directories for data recording. /// private void SetupDirectories() { try { // Create base directory if it doesn't exist if (!Directory.Exists(_baseDir)) { Directory.CreateDirectory(_baseDir); // Create .gdignore file in the base directory File.WriteAllText(Path.Combine(_baseDir, ".gdignore"), ""); } // Create frames directory if it doesn't exist if (!Directory.Exists(_frameDir)) { Directory.CreateDirectory(_frameDir); } else { // Empty the frames directory Directory.Delete(_frameDir, true); Directory.CreateDirectory(_frameDir); } } catch (Exception ex) { GD.PrintErr($"Error creating directories: {ex.Message}"); GD.PrintErr(ex.StackTrace); } } /// /// Starts the recording session by initializing the CSV file. /// public void Start() { try { _csvWriter = new StreamWriter(_csvPath, false); _csvWriter.WriteLine("frame,timestamp,left_x,left_y,right_x,right_y,left_flag,right_flag"); _csvWriter.Flush(); // Reset frustum recording flag _frustumRecorded = false; } catch (Exception ex) { GD.PrintErr($"Error starting recording: {ex.Message}"); GD.PrintErr(ex.StackTrace); } } /// /// Clears any captures in the "frames" folder /// public void ClearFrames() { if (Directory.Exists(_frameDir)) { foreach (string file in Directory.GetFiles(_frameDir, "*.jpg")) { File.Delete(file); } } } /// /// Records a frame to disk. /// /// The OpenCV Mat frame to record. /// The frame number. public void RecordFrame(Mat frame, int frameCount) { try { string framePath = Path.Combine(_frameDir, $"frame_{frameCount:D6}.jpg"); Cv2.ImWrite(framePath, frame); } catch (Exception ex) { GD.PrintErr($"Error recording frame: {ex.Message}"); GD.PrintErr(ex.StackTrace); } } /// /// Records eye position data to the CSV file. /// /// The frame number. /// The timestamp of the frame. /// The eye position data. public void RecordEyeData(int frameCount, float frameTimestamp, EyeData eyeData) { try { if (_csvWriter == null) { GD.PrintErr("Cannot record eye data: CSV writer not initialized. Call Start() first."); return; } _csvWriter.WriteLine( $"{frameCount}," + $"{frameTimestamp.ToString("F3", CultureInfo.InvariantCulture)}," + $"{eyeData.LeftPos.X.ToString("F3", CultureInfo.InvariantCulture)}," + $"{eyeData.LeftPos.Y.ToString("F3", CultureInfo.InvariantCulture)}," + $"{eyeData.RightPos.X.ToString("F3", CultureInfo.InvariantCulture)}," + $"{eyeData.RightPos.Y.ToString("F3", CultureInfo.InvariantCulture)}," + $"{eyeData.LeftOpenness.ToString("F3", CultureInfo.InvariantCulture)}," + $"{eyeData.RightOpenness.ToString("F3", CultureInfo.InvariantCulture)}" ); _csvWriter.Flush(); } catch (Exception ex) { GD.PrintErr($"Error recording eye data: {ex.Message}"); GD.PrintErr(ex.StackTrace); } } /// /// Records the projection matrices (frustum) used for 3D to screen space conversion. /// This only needs to be called once per recording session. /// /// The projection matrix for the left eye. /// The projection matrix for the right eye. /// The near clipping plane distance. /// The far clipping plane distance. public void RecordFrustum(Projection leftProjection, Projection rightProjection, float near, float far) { try { // Only record once per session if (_frustumRecorded) { return; } // Create a data structure to hold the frustum information var frustumData = new { LeftProjection = new { X = new float[] { leftProjection.X.X, leftProjection.X.Y, leftProjection.X.Z, leftProjection.X.W }, Y = new float[] { leftProjection.Y.X, leftProjection.Y.Y, leftProjection.Y.Z, leftProjection.Y.W }, Z = new float[] { leftProjection.Z.X, leftProjection.Z.Y, leftProjection.Z.Z, leftProjection.Z.W }, W = new float[] { leftProjection.W.X, leftProjection.W.Y, leftProjection.W.Z, leftProjection.W.W } }, RightProjection = new { X = new float[] { rightProjection.X.X, rightProjection.X.Y, rightProjection.X.Z, rightProjection.X.W }, Y = new float[] { rightProjection.Y.X, rightProjection.Y.Y, rightProjection.Y.Z, rightProjection.Y.W }, Z = new float[] { rightProjection.Z.X, rightProjection.Z.Y, rightProjection.Z.Z, rightProjection.Z.W }, W = new float[] { rightProjection.W.X, rightProjection.W.Y, rightProjection.W.Z, rightProjection.W.W } }, NearClip = near, FarClip = far, RecordedAt = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss") }; // Serialize to JSON and write to file string jsonData = JsonSerializer.Serialize(frustumData, new JsonSerializerOptions { WriteIndented = true }); File.WriteAllText(_frustumPath, jsonData); _frustumRecorded = true; GD.Print("Frustum data recorded successfully"); } catch (Exception ex) { GD.PrintErr($"Error recording frustum data: {ex.Message}"); GD.PrintErr(ex.StackTrace); } } /// /// Cleans up resources used by the recorder. /// public void Cleanup() { try { if (_csvWriter != null) { _csvWriter.Flush(); _csvWriter.Close(); _csvWriter.Dispose(); _csvWriter = null; } } catch (Exception ex) { GD.PrintErr($"Error during cleanup: {ex.Message}"); GD.PrintErr(ex.StackTrace); } } }