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);
}
}
}