general: attempts at better window management
This commit is contained in:
@@ -17,8 +17,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
|
|||||||
private var updateManager: UpdateManager?
|
private var updateManager: UpdateManager?
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
private var hasStartedTimers = false
|
private var hasStartedTimers = false
|
||||||
private var isSettingsWindowOpen = false
|
|
||||||
private var isOnboardingWindowOpen = false
|
|
||||||
|
|
||||||
// Convenience accessor for settings
|
// Convenience accessor for settings
|
||||||
private var settingsManager: any SettingsProviding {
|
private var settingsManager: any SettingsProviding {
|
||||||
@@ -51,11 +49,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
|
|||||||
|
|
||||||
serviceContainer.setupSmartModeServices()
|
serviceContainer.setupSmartModeServices()
|
||||||
|
|
||||||
// Check if onboarding needs to be shown automatically
|
|
||||||
if !settingsManager.settings.hasCompletedOnboarding {
|
|
||||||
// Set the flag to indicate we expect an onboarding window
|
|
||||||
isOnboardingWindowOpen = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize update manager after onboarding is complete
|
// Initialize update manager after onboarding is complete
|
||||||
if settingsManager.settings.hasCompletedOnboarding {
|
if settingsManager.settings.hasCompletedOnboarding {
|
||||||
@@ -228,45 +221,19 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private let menuDismissalDelay: TimeInterval = 0.1
|
||||||
|
|
||||||
func openSettings(tab: Int = 0) {
|
func openSettings(tab: Int = 0) {
|
||||||
// If settings window is already open, focus it instead of opening new one
|
performAfterMenuDismissal { [weak self] in
|
||||||
if isSettingsWindowOpen {
|
|
||||||
// Try to focus existing window
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
NotificationCenter.default.post(
|
|
||||||
name: Notification.Name("SwitchToSettingsTab"),
|
|
||||||
object: tab
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMenuDismissal()
|
|
||||||
isSettingsWindowOpen = true
|
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
|
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
windowManager.showSettings(settingsManager: self.settingsManager, initialTab: tab)
|
self.windowManager.showSettings(settingsManager: self.settingsManager, initialTab: tab)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func openOnboarding() {
|
func openOnboarding() {
|
||||||
// If onboarding window is already open, focus it instead of opening new one
|
performAfterMenuDismissal { [weak self] in
|
||||||
if isOnboardingWindowOpen {
|
|
||||||
// Try to activate existing window
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
OnboardingWindowPresenter.shared.activateIfPresent()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMenuDismissal()
|
|
||||||
// Explicitly set the flag to true when we're about to show the onboarding window
|
|
||||||
isOnboardingWindowOpen = true
|
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
|
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
windowManager.showOnboarding(settingsManager: self.settingsManager)
|
self.windowManager.showOnboarding(settingsManager: self.settingsManager)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,8 +242,14 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
|
|||||||
windowManager.dismissOverlayReminder()
|
windowManager.dismissOverlayReminder()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func performAfterMenuDismissal(_ action: @escaping () -> Void) {
|
||||||
|
handleMenuDismissal()
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + menuDismissalDelay) {
|
||||||
|
action()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func setupWindowCloseObservers() {
|
private func setupWindowCloseObservers() {
|
||||||
// Observe settings window closing
|
|
||||||
NotificationCenter.default.addObserver(
|
NotificationCenter.default.addObserver(
|
||||||
self,
|
self,
|
||||||
selector: #selector(settingsWindowDidClose),
|
selector: #selector(settingsWindowDidClose),
|
||||||
@@ -284,7 +257,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
|
|||||||
object: nil
|
object: nil
|
||||||
)
|
)
|
||||||
|
|
||||||
// Observe onboarding window closing
|
|
||||||
NotificationCenter.default.addObserver(
|
NotificationCenter.default.addObserver(
|
||||||
self,
|
self,
|
||||||
selector: #selector(onboardingWindowDidClose),
|
selector: #selector(onboardingWindowDidClose),
|
||||||
@@ -293,13 +265,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func settingsWindowDidClose() {
|
@objc private func settingsWindowDidClose() {}
|
||||||
isSettingsWindowOpen = false
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func onboardingWindowDidClose() {
|
@objc private func onboardingWindowDidClose() {}
|
||||||
// Reset the flag when we receive the close notification
|
|
||||||
isOnboardingWindowOpen = false
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,6 +76,11 @@ enum EyeTrackingConstants: Sendable {
|
|||||||
/// > 1.0 = More aggressive scaling
|
/// > 1.0 = More aggressive scaling
|
||||||
static let distanceSensitivity: Double = 1.0
|
static let distanceSensitivity: Double = 1.0
|
||||||
|
|
||||||
|
/// Default reference face width for distance scaling when uncalibrated.
|
||||||
|
/// Measured from test videos at typical laptop distance (~60cm).
|
||||||
|
/// Face bounding box width as ratio of image width.
|
||||||
|
static let defaultReferenceFaceWidth: Double = 0.4566
|
||||||
|
|
||||||
/// Minimum confidence required for a valid pupil detection before updating the gaze average.
|
/// Minimum confidence required for a valid pupil detection before updating the gaze average.
|
||||||
/// Helps filter out blinks or noisy frames.
|
/// Helps filter out blinks or noisy frames.
|
||||||
static let minimumGazeConfidence: Int = 3 // consecutive valid frames
|
static let minimumGazeConfidence: Int = 3 // consecutive valid frames
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ struct GazeThresholds: Codable {
|
|||||||
screenRightBound: 0.20, // Right edge of screen
|
screenRightBound: 0.20, // Right edge of screen
|
||||||
screenTopBound: 0.35, // Top edge of screen
|
screenTopBound: 0.35, // Top edge of screen
|
||||||
screenBottomBound: 0.55, // Bottom edge of screen
|
screenBottomBound: 0.55, // Bottom edge of screen
|
||||||
referenceFaceWidth: 0.0 // 0.0 means unused/uncalibrated
|
referenceFaceWidth: 0.4566 // Measured from test videos (avg of inner/outer)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -535,8 +535,23 @@ class EyeTrackingService: NSObject, ObservableObject {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Fallback to default constants (no calibration)
|
// Fallback to default constants (no calibration)
|
||||||
let lookingRight = avgH <= EyeTrackingConstants.pixelGazeMinRatio
|
// Still apply distance scaling using default reference
|
||||||
let lookingLeft = avgH >= EyeTrackingConstants.pixelGazeMaxRatio
|
let currentFaceWidth = face.boundingBox.width
|
||||||
|
let refFaceWidth = EyeTrackingConstants.defaultReferenceFaceWidth
|
||||||
|
|
||||||
|
var distanceScale = 1.0
|
||||||
|
if refFaceWidth > 0 && currentFaceWidth > 0 {
|
||||||
|
let rawScale = refFaceWidth / currentFaceWidth
|
||||||
|
distanceScale = 1.0 + (rawScale - 1.0) * EyeTrackingConstants.distanceSensitivity
|
||||||
|
distanceScale = max(0.5, min(2.0, distanceScale))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Center is assumed at midpoint of the thresholds
|
||||||
|
let centerH = (EyeTrackingConstants.pixelGazeMinRatio + EyeTrackingConstants.pixelGazeMaxRatio) / 2.0
|
||||||
|
let normalizedH = centerH + (avgH - centerH) * distanceScale
|
||||||
|
|
||||||
|
let lookingRight = normalizedH <= EyeTrackingConstants.pixelGazeMinRatio
|
||||||
|
let lookingLeft = normalizedH >= EyeTrackingConstants.pixelGazeMaxRatio
|
||||||
eyesLookingAway = lookingRight || lookingLeft
|
eyesLookingAway = lookingRight || lookingLeft
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,9 +32,12 @@ final class OnboardingWindowPresenter {
|
|||||||
|
|
||||||
private weak var windowController: NSWindowController?
|
private weak var windowController: NSWindowController?
|
||||||
private var closeObserver: NSObjectProtocol?
|
private var closeObserver: NSObjectProtocol?
|
||||||
|
private var isShowingWindow = false
|
||||||
|
|
||||||
func show(settingsManager: SettingsManager) {
|
func show(settingsManager: SettingsManager) {
|
||||||
if activateIfPresent() { return }
|
if activateIfPresent() { return }
|
||||||
|
guard !isShowingWindow else { return }
|
||||||
|
isShowingWindow = true
|
||||||
createWindow(settingsManager: settingsManager)
|
createWindow(settingsManager: settingsManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,15 +47,26 @@ final class OnboardingWindowPresenter {
|
|||||||
windowController = nil
|
windowController = nil
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
window.makeKeyAndOrderFront(nil)
|
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
DispatchQueue.main.async {
|
||||||
window.makeMain()
|
NSApp.unhide(nil)
|
||||||
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
|
|
||||||
|
if window.isMiniaturized {
|
||||||
|
window.deminiaturize(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
window.makeKeyAndOrderFront(nil)
|
||||||
|
window.orderFrontRegardless()
|
||||||
|
window.makeMain()
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func close() {
|
func close() {
|
||||||
windowController?.close()
|
windowController?.close()
|
||||||
windowController = nil
|
windowController = nil
|
||||||
|
isShowingWindow = false
|
||||||
removeCloseObserver()
|
removeCloseObserver()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,14 +83,18 @@ final class OnboardingWindowPresenter {
|
|||||||
window.titlebarAppearsTransparent = true
|
window.titlebarAppearsTransparent = true
|
||||||
window.center()
|
window.center()
|
||||||
window.isReleasedWhenClosed = true
|
window.isReleasedWhenClosed = true
|
||||||
|
window.collectionBehavior = [.managed, .participatesInCycle, .moveToActiveSpace, .fullScreenAuxiliary]
|
||||||
|
|
||||||
window.contentView = NSHostingView(
|
window.contentView = NSHostingView(
|
||||||
rootView: OnboardingContainerView(settingsManager: settingsManager)
|
rootView: OnboardingContainerView(settingsManager: settingsManager)
|
||||||
)
|
)
|
||||||
|
|
||||||
let controller = NSWindowController(window: window)
|
let controller = NSWindowController(window: window)
|
||||||
controller.showWindow(nil)
|
controller.showWindow(nil)
|
||||||
window.makeKeyAndOrderFront(nil)
|
NSApp.unhide(nil)
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
|
window.makeKeyAndOrderFront(nil)
|
||||||
|
window.orderFrontRegardless()
|
||||||
|
|
||||||
windowController = controller
|
windowController = controller
|
||||||
|
|
||||||
@@ -88,6 +106,7 @@ final class OnboardingWindowPresenter {
|
|||||||
) { [weak self] _ in
|
) { [weak self] _ in
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
self?.windowController = nil
|
self?.windowController = nil
|
||||||
|
self?.isShowingWindow = false
|
||||||
self?.removeCloseObserver()
|
self?.removeCloseObserver()
|
||||||
}
|
}
|
||||||
NotificationCenter.default.post(
|
NotificationCenter.default.post(
|
||||||
|
|||||||
@@ -13,9 +13,12 @@ final class SettingsWindowPresenter {
|
|||||||
|
|
||||||
private weak var windowController: NSWindowController?
|
private weak var windowController: NSWindowController?
|
||||||
private var closeObserver: NSObjectProtocol?
|
private var closeObserver: NSObjectProtocol?
|
||||||
|
private var isShowingWindow = false
|
||||||
|
|
||||||
func show(settingsManager: SettingsManager, initialTab: Int = 0) {
|
func show(settingsManager: SettingsManager, initialTab: Int = 0) {
|
||||||
if focusExistingWindow(tab: initialTab) { return }
|
if focusExistingWindow(tab: initialTab) { return }
|
||||||
|
guard !isShowingWindow else { return }
|
||||||
|
isShowingWindow = true
|
||||||
createWindow(settingsManager: settingsManager, initialTab: initialTab)
|
createWindow(settingsManager: settingsManager, initialTab: initialTab)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,6 +29,7 @@ final class SettingsWindowPresenter {
|
|||||||
func close() {
|
func close() {
|
||||||
windowController?.close()
|
windowController?.close()
|
||||||
windowController = nil
|
windowController = nil
|
||||||
|
isShowingWindow = false
|
||||||
removeCloseObserver()
|
removeCloseObserver()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,15 +40,24 @@ final class SettingsWindowPresenter {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if let tab {
|
DispatchQueue.main.async {
|
||||||
NotificationCenter.default.post(
|
if let tab {
|
||||||
name: Notification.Name("SwitchToSettingsTab"),
|
NotificationCenter.default.post(
|
||||||
object: tab
|
name: Notification.Name("SwitchToSettingsTab"),
|
||||||
)
|
object: tab
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
|
||||||
window.makeKeyAndOrderFront(nil)
|
NSApp.unhide(nil)
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
|
|
||||||
|
if window.isMiniaturized {
|
||||||
|
window.deminiaturize(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
window.makeKeyAndOrderFront(nil)
|
||||||
|
window.orderFrontRegardless()
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,14 +78,18 @@ final class SettingsWindowPresenter {
|
|||||||
window.setFrameAutosaveName("SettingsWindow")
|
window.setFrameAutosaveName("SettingsWindow")
|
||||||
window.isReleasedWhenClosed = false
|
window.isReleasedWhenClosed = false
|
||||||
|
|
||||||
|
window.collectionBehavior = [.managed, .participatesInCycle, .moveToActiveSpace, .fullScreenAuxiliary]
|
||||||
|
|
||||||
window.contentView = NSHostingView(
|
window.contentView = NSHostingView(
|
||||||
rootView: SettingsWindowView(settingsManager: settingsManager, initialTab: initialTab)
|
rootView: SettingsWindowView(settingsManager: settingsManager, initialTab: initialTab)
|
||||||
)
|
)
|
||||||
|
|
||||||
let controller = NSWindowController(window: window)
|
let controller = NSWindowController(window: window)
|
||||||
controller.showWindow(nil)
|
controller.showWindow(nil)
|
||||||
window.makeKeyAndOrderFront(nil)
|
NSApp.unhide(nil)
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
|
window.makeKeyAndOrderFront(nil)
|
||||||
|
window.orderFrontRegardless()
|
||||||
|
|
||||||
windowController = controller
|
windowController = controller
|
||||||
|
|
||||||
@@ -84,10 +101,12 @@ final class SettingsWindowPresenter {
|
|||||||
) { [weak self] _ in
|
) { [weak self] _ in
|
||||||
Task { @MainActor [weak self] in
|
Task { @MainActor [weak self] in
|
||||||
self?.windowController = nil
|
self?.windowController = nil
|
||||||
|
self?.isShowingWindow = false
|
||||||
self?.removeCloseObserver()
|
self?.removeCloseObserver()
|
||||||
NotificationCenter.default.post(
|
NotificationCenter.default.post(
|
||||||
name: Notification.Name("SettingsWindowDidClose"), object: nil)
|
name: Notification.Name("SettingsWindowDidClose"), object: nil)
|
||||||
}
|
}
|
||||||
|
self?.isShowingWindow = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,13 @@ final class VideoGazeTests: XCTestCase {
|
|||||||
logLines.append(message)
|
logLines.append(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func attachLogs() {
|
||||||
|
let attachment = XCTAttachment(string: logLines.joined(separator: "\n"))
|
||||||
|
attachment.name = "Test Logs"
|
||||||
|
attachment.lifetime = .keepAlways
|
||||||
|
add(attachment)
|
||||||
|
}
|
||||||
|
|
||||||
/// Process the outer video (looking away from screen) - should detect "looking away"
|
/// Process the outer video (looking away from screen) - should detect "looking away"
|
||||||
func testOuterVideoGazeDetection() async throws {
|
func testOuterVideoGazeDetection() async throws {
|
||||||
logLines = []
|
logLines = []
|
||||||
@@ -34,6 +41,9 @@ final class VideoGazeTests: XCTestCase {
|
|||||||
log("🎯 OUTER video: \(String(format: "%.1f%%", nonCenterRatio * 100)) frames detected as non-center (expected: >50%)")
|
log("🎯 OUTER video: \(String(format: "%.1f%%", nonCenterRatio * 100)) frames detected as non-center (expected: >50%)")
|
||||||
log(" H-range: \(String(format: "%.3f", stats.minH)) to \(String(format: "%.3f", stats.maxH))")
|
log(" H-range: \(String(format: "%.3f", stats.minH)) to \(String(format: "%.3f", stats.maxH))")
|
||||||
log(" V-range: \(String(format: "%.3f", stats.minV)) to \(String(format: "%.3f", stats.maxV))")
|
log(" V-range: \(String(format: "%.3f", stats.minV)) to \(String(format: "%.3f", stats.maxV))")
|
||||||
|
log(" Face width: \(String(format: "%.3f", stats.avgFaceWidth)) (range: \(String(format: "%.3f", stats.minFaceWidth))-\(String(format: "%.3f", stats.maxFaceWidth)))")
|
||||||
|
|
||||||
|
attachLogs()
|
||||||
|
|
||||||
// At least 50% should be detected as non-center when looking away
|
// At least 50% should be detected as non-center when looking away
|
||||||
XCTAssertGreaterThan(nonCenterRatio, 0.5, "Looking away video should have >50% non-center detections. Log:\n\(logLines.joined(separator: "\n"))")
|
XCTAssertGreaterThan(nonCenterRatio, 0.5, "Looking away video should have >50% non-center detections. Log:\n\(logLines.joined(separator: "\n"))")
|
||||||
@@ -55,6 +65,9 @@ final class VideoGazeTests: XCTestCase {
|
|||||||
log("🎯 INNER video: \(String(format: "%.1f%%", centerRatio * 100)) frames detected as center (expected: >50%)")
|
log("🎯 INNER video: \(String(format: "%.1f%%", centerRatio * 100)) frames detected as center (expected: >50%)")
|
||||||
log(" H-range: \(String(format: "%.3f", stats.minH)) to \(String(format: "%.3f", stats.maxH))")
|
log(" H-range: \(String(format: "%.3f", stats.minH)) to \(String(format: "%.3f", stats.maxH))")
|
||||||
log(" V-range: \(String(format: "%.3f", stats.minV)) to \(String(format: "%.3f", stats.maxV))")
|
log(" V-range: \(String(format: "%.3f", stats.minV)) to \(String(format: "%.3f", stats.maxV))")
|
||||||
|
log(" Face width: \(String(format: "%.3f", stats.avgFaceWidth)) (range: \(String(format: "%.3f", stats.minFaceWidth))-\(String(format: "%.3f", stats.maxFaceWidth)))")
|
||||||
|
|
||||||
|
attachLogs()
|
||||||
|
|
||||||
// At least 50% should be detected as center when looking at screen
|
// At least 50% should be detected as center when looking at screen
|
||||||
XCTAssertGreaterThan(centerRatio, 0.5, "Looking at screen video should have >50% center detections. Log:\n\(logLines.joined(separator: "\n"))")
|
XCTAssertGreaterThan(centerRatio, 0.5, "Looking at screen video should have >50% center detections. Log:\n\(logLines.joined(separator: "\n"))")
|
||||||
@@ -70,6 +83,14 @@ final class VideoGazeTests: XCTestCase {
|
|||||||
var maxH = -Double.greatestFiniteMagnitude
|
var maxH = -Double.greatestFiniteMagnitude
|
||||||
var minV = Double.greatestFiniteMagnitude
|
var minV = Double.greatestFiniteMagnitude
|
||||||
var maxV = -Double.greatestFiniteMagnitude
|
var maxV = -Double.greatestFiniteMagnitude
|
||||||
|
var minFaceWidth = Double.greatestFiniteMagnitude
|
||||||
|
var maxFaceWidth = -Double.greatestFiniteMagnitude
|
||||||
|
var totalFaceWidth = 0.0
|
||||||
|
var faceWidthCount = 0
|
||||||
|
|
||||||
|
var avgFaceWidth: Double {
|
||||||
|
faceWidthCount > 0 ? totalFaceWidth / Double(faceWidthCount) : 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func processVideo(at url: URL, expectLookingAway: Bool) async throws -> VideoStats {
|
private func processVideo(at url: URL, expectLookingAway: Bool) async throws -> VideoStats {
|
||||||
@@ -159,6 +180,13 @@ final class VideoGazeTests: XCTestCase {
|
|||||||
|
|
||||||
stats.faceDetectedFrames += 1
|
stats.faceDetectedFrames += 1
|
||||||
|
|
||||||
|
// Track face width (bounding box width as ratio of image width)
|
||||||
|
let faceWidth = face.boundingBox.width
|
||||||
|
stats.minFaceWidth = min(stats.minFaceWidth, faceWidth)
|
||||||
|
stats.maxFaceWidth = max(stats.maxFaceWidth, faceWidth)
|
||||||
|
stats.totalFaceWidth += faceWidth
|
||||||
|
stats.faceWidthCount += 1
|
||||||
|
|
||||||
let imageSize = CGSize(
|
let imageSize = CGSize(
|
||||||
width: CVPixelBufferGetWidth(pixelBuffer),
|
width: CVPixelBufferGetWidth(pixelBuffer),
|
||||||
height: CVPixelBufferGetHeight(pixelBuffer)
|
height: CVPixelBufferGetHeight(pixelBuffer)
|
||||||
@@ -220,6 +248,7 @@ final class VideoGazeTests: XCTestCase {
|
|||||||
log(String(repeating: "=", count: 75))
|
log(String(repeating: "=", count: 75))
|
||||||
log("Summary: \(stats.totalFrames) frames sampled, \(stats.faceDetectedFrames) with face, \(stats.pupilDetectedFrames) with pupils")
|
log("Summary: \(stats.totalFrames) frames sampled, \(stats.faceDetectedFrames) with face, \(stats.pupilDetectedFrames) with pupils")
|
||||||
log("Center frames: \(stats.centerFrames), Non-center: \(stats.nonCenterFrames)")
|
log("Center frames: \(stats.centerFrames), Non-center: \(stats.nonCenterFrames)")
|
||||||
|
log("Face width: avg=\(String(format: "%.3f", stats.avgFaceWidth)), range=\(String(format: "%.3f", stats.minFaceWidth)) to \(String(format: "%.3f", stats.maxFaceWidth))")
|
||||||
log("Processing complete\n")
|
log("Processing complete\n")
|
||||||
|
|
||||||
return stats
|
return stats
|
||||||
|
|||||||
Reference in New Issue
Block a user