docs(workflow): add comprehensive git workflow standards #1
11 changed files with 2213 additions and 613 deletions
194
ChessPrism/ChessPrism/BoardDetector.swift
Normal file
194
ChessPrism/ChessPrism/BoardDetector.swift
Normal file
|
|
@ -0,0 +1,194 @@
|
||||||
|
import Foundation
|
||||||
|
import Vision
|
||||||
|
import CoreImage
|
||||||
|
|
||||||
|
class BoardDetector {
|
||||||
|
private let context = CIContext()
|
||||||
|
|
||||||
|
// Known chess interface patterns
|
||||||
|
private struct ChessPattern {
|
||||||
|
let interfaceType: ChessInterface
|
||||||
|
let boardColor: CGColor
|
||||||
|
let squareSize: CGFloat
|
||||||
|
let cornerPattern: [CGPoint]
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ChessInterface {
|
||||||
|
case chesscom
|
||||||
|
case lichess
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chess.com and Lichess patterns
|
||||||
|
private let patterns: [ChessPattern] = [
|
||||||
|
// Chess.com light theme pattern
|
||||||
|
ChessPattern(
|
||||||
|
interfaceType: .chesscom,
|
||||||
|
boardColor: CGColor(red: 0.82, green: 0.82, blue: 0.82, alpha: 1.0), // Light squares
|
||||||
|
squareSize: 45.0, // Default square size
|
||||||
|
cornerPattern: [
|
||||||
|
CGPoint(x: 0, y: 0),
|
||||||
|
CGPoint(x: 45, y: 0),
|
||||||
|
CGPoint(x: 0, y: 45),
|
||||||
|
CGPoint(x: 45, y: 45)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
// Lichess light theme pattern
|
||||||
|
ChessPattern(
|
||||||
|
interfaceType: .lichess,
|
||||||
|
boardColor: CGColor(red: 0.93, green: 0.93, blue: 0.83, alpha: 1.0), // Light squares
|
||||||
|
squareSize: 40.0, // Default square size
|
||||||
|
cornerPattern: [
|
||||||
|
CGPoint(x: 0, y: 0),
|
||||||
|
CGPoint(x: 40, y: 0),
|
||||||
|
CGPoint(x: 0, y: 40),
|
||||||
|
CGPoint(x: 40, y: 40)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
// MARK: - Pattern Detection
|
||||||
|
|
||||||
|
func detectBoard(in image: CIImage) -> CGRect? {
|
||||||
|
// 1. Color-based detection
|
||||||
|
let colorMatches = detectColorPatterns(in: image)
|
||||||
|
|
||||||
|
// 2. Grid pattern detection
|
||||||
|
let gridMatches = detectGridPattern(in: image, colorMatches: colorMatches)
|
||||||
|
|
||||||
|
// 3. Corner validation
|
||||||
|
if let bestMatch = validateCorners(in: image, candidates: gridMatches) {
|
||||||
|
// Extract square board from detected area
|
||||||
|
let width = bestMatch.width
|
||||||
|
let height = bestMatch.height
|
||||||
|
|
||||||
|
// Calculate the board position
|
||||||
|
// Vision coordinates are bottom-left origin, so we need to:
|
||||||
|
// 1. Keep the same width
|
||||||
|
// 2. Use the top portion of the detected area
|
||||||
|
// 3. Maintain square aspect ratio
|
||||||
|
return CGRect(
|
||||||
|
x: bestMatch.minX,
|
||||||
|
y: bestMatch.maxY - width, // Position at top of detected area
|
||||||
|
width: width,
|
||||||
|
height: width
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private func detectColorPatterns(in image: CIImage) -> [CGRect] {
|
||||||
|
var matches: [CGRect] = []
|
||||||
|
|
||||||
|
for pattern in patterns {
|
||||||
|
// Apply color adjustments to enhance pattern detection
|
||||||
|
let colorControls = CIFilter(name: "CIColorControls")
|
||||||
|
colorControls?.setValue(image, forKey: kCIInputImageKey)
|
||||||
|
colorControls?.setValue(1.2, forKey: kCIInputSaturationKey) // Increase saturation
|
||||||
|
colorControls?.setValue(0.2, forKey: kCIInputBrightnessKey) // Adjust brightness
|
||||||
|
colorControls?.setValue(1.1, forKey: kCIInputContrastKey) // Increase contrast
|
||||||
|
|
||||||
|
guard let adjustedImage = colorControls?.outputImage else { continue }
|
||||||
|
|
||||||
|
// Detect rectangles
|
||||||
|
var rectangles: [VNRectangleObservation] = []
|
||||||
|
let request = VNDetectRectanglesRequest()
|
||||||
|
request.minimumAspectRatio = 0.3 // Allow taller rectangles
|
||||||
|
request.maximumAspectRatio = 0.5
|
||||||
|
request.minimumSize = 0.4 // Look for larger areas
|
||||||
|
request.maximumObservations = 1
|
||||||
|
|
||||||
|
let handler = VNImageRequestHandler(ciImage: adjustedImage)
|
||||||
|
try? handler.perform([request])
|
||||||
|
|
||||||
|
if let results = request.results as? [VNRectangleObservation] {
|
||||||
|
matches.append(contentsOf: results.map { $0.boundingBox })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches
|
||||||
|
}
|
||||||
|
|
||||||
|
private func detectGridPattern(in image: CIImage, colorMatches: [CGRect]) -> [CGRect] {
|
||||||
|
var gridMatches: [CGRect] = []
|
||||||
|
|
||||||
|
for rect in colorMatches {
|
||||||
|
// Create edge detection filter
|
||||||
|
let edgeFilter = CIFilter(name: "CIEdgeWork")
|
||||||
|
edgeFilter?.setValue(image.cropped(to: rect), forKey: kCIInputImageKey)
|
||||||
|
edgeFilter?.setValue(1.0, forKey: kCIInputRadiusKey)
|
||||||
|
|
||||||
|
guard let edgeImage = edgeFilter?.outputImage else { continue }
|
||||||
|
|
||||||
|
// Detect rectangles in edge image
|
||||||
|
var rectangles: [VNRectangleObservation] = []
|
||||||
|
let request = VNDetectRectanglesRequest()
|
||||||
|
request.minimumAspectRatio = 0.3
|
||||||
|
request.maximumAspectRatio = 0.5
|
||||||
|
request.minimumSize = 0.4
|
||||||
|
request.maximumObservations = 1
|
||||||
|
|
||||||
|
let handler = VNImageRequestHandler(ciImage: edgeImage)
|
||||||
|
try? handler.perform([request])
|
||||||
|
|
||||||
|
if let results = request.results as? [VNRectangleObservation] {
|
||||||
|
for observation in results {
|
||||||
|
if validateGridDimensions(observation.boundingBox, squareSize: rect.width / 8) {
|
||||||
|
gridMatches.append(observation.boundingBox)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return gridMatches
|
||||||
|
}
|
||||||
|
|
||||||
|
private func validateGridDimensions(_ rect: CGRect, squareSize: CGFloat) -> Bool {
|
||||||
|
// Check if dimensions match 8x8 grid with some tolerance
|
||||||
|
let expectedSize = squareSize * 8
|
||||||
|
let tolerance: CGFloat = 0.3 // More flexible tolerance
|
||||||
|
|
||||||
|
// Only validate width since height detection is unreliable
|
||||||
|
let widthMatch = abs(rect.width - expectedSize) <= (expectedSize * tolerance)
|
||||||
|
|
||||||
|
return widthMatch
|
||||||
|
}
|
||||||
|
|
||||||
|
private func validateCorners(in image: CIImage, candidates: [CGRect]) -> CGRect? {
|
||||||
|
var bestMatch: (rect: CGRect, score: Float)?
|
||||||
|
|
||||||
|
for rect in candidates {
|
||||||
|
var totalScore: Float = 0
|
||||||
|
|
||||||
|
// Extract corners
|
||||||
|
let corners = [
|
||||||
|
CGPoint(x: rect.minX, y: rect.minY),
|
||||||
|
CGPoint(x: rect.maxX, y: rect.minY),
|
||||||
|
CGPoint(x: rect.minX, y: rect.maxY),
|
||||||
|
CGPoint(x: rect.maxX, y: rect.maxY)
|
||||||
|
]
|
||||||
|
|
||||||
|
// Compare with pattern corners
|
||||||
|
for pattern in patterns {
|
||||||
|
var patternScore: Float = 0
|
||||||
|
|
||||||
|
for (corner, patternCorner) in zip(corners, pattern.cornerPattern) {
|
||||||
|
let distance = hypot(
|
||||||
|
corner.x - patternCorner.x,
|
||||||
|
corner.y - patternCorner.y
|
||||||
|
)
|
||||||
|
patternScore += Float(1.0 / (1.0 + distance))
|
||||||
|
}
|
||||||
|
|
||||||
|
totalScore = max(totalScore, patternScore / Float(corners.count))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update best match if score is higher
|
||||||
|
if totalScore > (bestMatch?.score ?? 0) {
|
||||||
|
bestMatch = (rect, totalScore)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bestMatch?.rect
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,14 +1,132 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
|
@StateObject private var viewModel = ScreenCaptureViewModel()
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack(spacing: 20) {
|
||||||
Text("Hello, ChessPrism!")
|
// Screen Capture Controls
|
||||||
.font(.largeTitle)
|
HStack {
|
||||||
|
Button(action: {
|
||||||
|
Task {
|
||||||
|
await viewModel.startCapture()
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Text("Start Capture")
|
||||||
|
.padding()
|
||||||
|
.background(viewModel.isCapturing ? Color.gray : Color.blue)
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.cornerRadius(8)
|
||||||
|
}
|
||||||
|
.disabled(viewModel.isCapturing)
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
viewModel.stopCapture()
|
||||||
|
}) {
|
||||||
|
Text("Stop Capture")
|
||||||
|
.padding()
|
||||||
|
.background(!viewModel.isCapturing ? Color.gray : Color.red)
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.cornerRadius(8)
|
||||||
|
}
|
||||||
|
.disabled(!viewModel.isCapturing)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Board Detection Visualization
|
||||||
|
if let rect = viewModel.detectedBoardRect {
|
||||||
|
GeometryReader { geometry in
|
||||||
|
Rectangle()
|
||||||
|
.stroke(Color.green, lineWidth: 2)
|
||||||
|
.frame(
|
||||||
|
width: rect.width * geometry.size.width,
|
||||||
|
height: rect.height * geometry.size.height
|
||||||
|
)
|
||||||
|
.position(
|
||||||
|
x: rect.midX * geometry.size.width,
|
||||||
|
y: rect.midY * geometry.size.height
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.background(Color.black.opacity(0.1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error Display
|
||||||
|
if let error = viewModel.captureError {
|
||||||
|
VStack(spacing: 10) {
|
||||||
|
Text(error.localizedDescription)
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundColor(.red)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
|
||||||
|
if case .chessWindowNotFound = error {
|
||||||
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
|
Text("Requirements:")
|
||||||
|
.font(.subheadline)
|
||||||
|
.bold()
|
||||||
|
Text("• Chess.com app or Firefox must be running")
|
||||||
|
Text("• A chess game must be open")
|
||||||
|
Text("• The game window must be visible")
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.background(Color.red.opacity(0.1))
|
||||||
|
.cornerRadius(8)
|
||||||
|
}
|
||||||
|
}
|
||||||
.padding()
|
.padding()
|
||||||
Text("Ready for development")
|
.frame(maxWidth: .infinity)
|
||||||
.font(.subheadline)
|
}
|
||||||
|
|
||||||
|
// Image Display Section
|
||||||
|
HStack(spacing: 20) {
|
||||||
|
// Full Capture Display
|
||||||
|
if let image = viewModel.capturedImage {
|
||||||
|
VStack {
|
||||||
|
Text("Full Capture")
|
||||||
|
.font(.caption)
|
||||||
|
Image(nsImage: image)
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.frame(height: 400) // Fixed height to show full board
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.background(Color.black.opacity(0.05))
|
||||||
|
.cornerRadius(8)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cropped Board Display
|
||||||
|
if let cropped = viewModel.croppedBoardImage {
|
||||||
|
VStack {
|
||||||
|
Text("Chessboard")
|
||||||
|
.font(.caption)
|
||||||
|
Image(nsImage: cropped)
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.frame(height: 400) // Match the board height
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.background(Color.black.opacity(0.05))
|
||||||
|
.cornerRadius(8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
|
||||||
|
// Vision Model Output Placeholder
|
||||||
|
if viewModel.croppedBoardImage != nil {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text("Board Analysis")
|
||||||
|
.font(.headline)
|
||||||
|
Text("FEN: (processing...)")
|
||||||
|
.font(.system(.body, design: .monospaced))
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.background(Color.blue.opacity(0.1))
|
||||||
|
.cornerRadius(8)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
}
|
}
|
||||||
|
.padding()
|
||||||
|
.frame(minWidth: 800, minHeight: 600) // Increased window size to fit larger board display
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
290
ChessPrism/ChessPrism/ScreenCapture.swift
Normal file
290
ChessPrism/ChessPrism/ScreenCapture.swift
Normal file
|
|
@ -0,0 +1,290 @@
|
||||||
|
import Foundation
|
||||||
|
import ScreenCaptureKit
|
||||||
|
import Vision
|
||||||
|
import CoreImage
|
||||||
|
|
||||||
|
class ScreenCapture: NSObject {
|
||||||
|
private var captureSession: SCStream?
|
||||||
|
private let queue = DispatchQueue(label: "com.chessprism.screencapture", qos: .userInteractive)
|
||||||
|
private var lastDetectedCoordinates: [(text: String, boundingBox: CGRect)] = []
|
||||||
|
private let boardDetector = BoardDetector()
|
||||||
|
|
||||||
|
// MARK: - Screen Capture Setup
|
||||||
|
func startCapture() async throws {
|
||||||
|
print("Starting screen capture...")
|
||||||
|
// Get available screen content
|
||||||
|
let availableContent = try await SCShareableContent.excludingDesktopWindows(false, onScreenWindowsOnly: true)
|
||||||
|
print("Available content: \(availableContent.displays.count) displays, \(availableContent.windows.count) windows")
|
||||||
|
|
||||||
|
// Find Chess.com app window
|
||||||
|
let chessWindow = availableContent.windows.first { window in
|
||||||
|
if let bundleId = window.owningApplication?.bundleIdentifier {
|
||||||
|
return bundleId == "com.chess.iphone"
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let chessWindow = chessWindow else {
|
||||||
|
throw ScreenCaptureError.chessWindowNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create stream configuration targeting chess window
|
||||||
|
let filter = SCContentFilter(desktopIndependentWindow: chessWindow)
|
||||||
|
let configuration = SCStreamConfiguration()
|
||||||
|
configuration.width = 1920
|
||||||
|
configuration.height = 1080
|
||||||
|
configuration.pixelFormat = kCVPixelFormatType_32BGRA
|
||||||
|
configuration.queueDepth = 5
|
||||||
|
|
||||||
|
// Create and start stream
|
||||||
|
captureSession = SCStream(filter: filter, configuration: configuration, delegate: nil)
|
||||||
|
|
||||||
|
try await captureSession?.addStreamOutput(self, type: .screen, sampleHandlerQueue: queue)
|
||||||
|
try await captureSession?.startCapture()
|
||||||
|
|
||||||
|
print("Screen capture started successfully")
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: .captureStateChanged,
|
||||||
|
object: nil,
|
||||||
|
userInfo: ["isCapturing": true]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Frame Processing
|
||||||
|
private func processFrame(sampleBuffer: CMSampleBuffer) {
|
||||||
|
guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
|
||||||
|
let ciImage = CIImage(cvPixelBuffer: pixelBuffer)
|
||||||
|
|
||||||
|
// Convert CIImage to NSImage for display
|
||||||
|
let context = CIContext()
|
||||||
|
if let cgImage = context.createCGImage(ciImage, from: ciImage.extent) {
|
||||||
|
let size = NSSize(width: cgImage.width, height: cgImage.height)
|
||||||
|
let nsImage = NSImage(cgImage: cgImage, size: size)
|
||||||
|
|
||||||
|
// Notify subscribers with captured image
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: .capturedFrame,
|
||||||
|
object: nil,
|
||||||
|
userInfo: ["image": nsImage]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try pattern-based detection first
|
||||||
|
if let boardRect = boardDetector.detectBoard(in: ciImage) {
|
||||||
|
// Add padding to ensure full board capture
|
||||||
|
let padding = boardRect.width * 0.25 // 25% padding
|
||||||
|
let paddedRect = CGRect(
|
||||||
|
x: boardRect.minX - padding,
|
||||||
|
y: boardRect.minY - padding,
|
||||||
|
width: boardRect.width + (padding * 2),
|
||||||
|
height: boardRect.height + (padding * 2)
|
||||||
|
)
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: .boardDetected,
|
||||||
|
object: nil,
|
||||||
|
userInfo: ["rect": paddedRect]
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to coordinate-based detection
|
||||||
|
let textRequest = VNRecognizeTextRequest { [weak self] request, error in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.handleTextDetection(request: request)
|
||||||
|
}
|
||||||
|
textRequest.recognitionLevel = .accurate
|
||||||
|
textRequest.usesLanguageCorrection = false
|
||||||
|
|
||||||
|
let rectangleRequest = VNDetectRectanglesRequest { [weak self] request, error in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.handleBoardDetection(request: request)
|
||||||
|
}
|
||||||
|
rectangleRequest.minimumAspectRatio = 0.8
|
||||||
|
rectangleRequest.maximumAspectRatio = 1.2
|
||||||
|
rectangleRequest.minimumSize = 0.2
|
||||||
|
rectangleRequest.maximumObservations = 3
|
||||||
|
|
||||||
|
let handler = VNImageRequestHandler(ciImage: ciImage)
|
||||||
|
try? handler.perform([textRequest, rectangleRequest])
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handleTextDetection(request: VNRequest) {
|
||||||
|
guard let results = request.results as? [VNRecognizedTextObservation] else { return }
|
||||||
|
|
||||||
|
// Filter for chess coordinates (a-h, 1-8)
|
||||||
|
var coordinates: [(text: String, boundingBox: CGRect)] = []
|
||||||
|
for observation in results {
|
||||||
|
if let candidate = observation.topCandidates(1).first {
|
||||||
|
let text = candidate.string
|
||||||
|
if (text.count == 1 && ("a"..."h").contains(text.lowercased())) ||
|
||||||
|
(text.count == 1 && ("1"..."8").contains(text)) {
|
||||||
|
coordinates.append((text: text, boundingBox: observation.boundingBox))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store coordinates and notify observers
|
||||||
|
if !coordinates.isEmpty {
|
||||||
|
lastDetectedCoordinates = coordinates
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: .boardCoordinatesDetected,
|
||||||
|
object: nil,
|
||||||
|
userInfo: ["coordinates": coordinates]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Board Detection Handling
|
||||||
|
private func handleBoardDetection(request: VNRequest) {
|
||||||
|
guard let results = request.results as? [VNRectangleObservation],
|
||||||
|
let boardRect = results.first else { return }
|
||||||
|
|
||||||
|
// Convert rectangle to screen coordinates
|
||||||
|
var transformedRect = transformRectangle(boardRect)
|
||||||
|
|
||||||
|
// Use the stored coordinates
|
||||||
|
let coordinates = lastDetectedCoordinates
|
||||||
|
|
||||||
|
// If we have coordinates, use them to refine the rectangle
|
||||||
|
if !coordinates.isEmpty {
|
||||||
|
// Separate horizontal and vertical coordinates
|
||||||
|
let horizontalCoords = coordinates.filter { ("a"..."h").contains($0.text.lowercased()) }
|
||||||
|
let verticalCoords = coordinates.filter { ("1"..."8").contains($0.text) }
|
||||||
|
|
||||||
|
// Get min/max for horizontal coordinates (a-h)
|
||||||
|
let xCoords = horizontalCoords.map { $0.boundingBox.origin.x }
|
||||||
|
let minX = xCoords.min() ?? transformedRect.minX
|
||||||
|
let maxX = xCoords.max() ?? transformedRect.maxX
|
||||||
|
|
||||||
|
// Get min/max for vertical coordinates (1-8)
|
||||||
|
let yCoords = verticalCoords.map { $0.boundingBox.origin.y }
|
||||||
|
let minY = yCoords.min() ?? transformedRect.minY
|
||||||
|
let maxY = yCoords.max() ?? transformedRect.maxY
|
||||||
|
|
||||||
|
// Calculate board boundaries directly from coordinates
|
||||||
|
let leftMost = horizontalCoords.min { $0.boundingBox.origin.x < $1.boundingBox.origin.x }?.boundingBox.origin.x ?? minX
|
||||||
|
let rightMost = horizontalCoords.max { $0.boundingBox.origin.x < $1.boundingBox.origin.x }?.boundingBox.origin.x ?? maxX
|
||||||
|
let bottomMost = verticalCoords.min { $0.boundingBox.origin.y < $1.boundingBox.origin.y }?.boundingBox.origin.y ?? minY
|
||||||
|
let topMost = verticalCoords.max { $0.boundingBox.origin.y < $1.boundingBox.origin.y }?.boundingBox.origin.y ?? maxY
|
||||||
|
|
||||||
|
// Calculate board dimensions directly from coordinate positions
|
||||||
|
let boardWidth = rightMost - leftMost
|
||||||
|
let boardHeight = topMost - bottomMost
|
||||||
|
|
||||||
|
// Find coordinate pairs that are likely on opposite sides of the board
|
||||||
|
let horizontalPairs = horizontalCoords.flatMap { h1 in
|
||||||
|
horizontalCoords.map { h2 in
|
||||||
|
(h1, h2, abs(h1.boundingBox.origin.x - h2.boundingBox.origin.x))
|
||||||
|
}
|
||||||
|
}.sorted { $0.2 > $1.2 }
|
||||||
|
|
||||||
|
let verticalPairs = verticalCoords.flatMap { v1 in
|
||||||
|
verticalCoords.map { v2 in
|
||||||
|
(v1, v2, abs(v1.boundingBox.origin.y - v2.boundingBox.origin.y))
|
||||||
|
}
|
||||||
|
}.sorted { $0.2 > $1.2 }
|
||||||
|
|
||||||
|
// Use the most distant pairs to define board boundaries
|
||||||
|
if let widestHorizontal = horizontalPairs.first,
|
||||||
|
let tallestVertical = verticalPairs.first {
|
||||||
|
|
||||||
|
// Calculate board dimensions using the most distant coordinates
|
||||||
|
let boardWidth = widestHorizontal.2
|
||||||
|
let boardHeight = tallestVertical.2
|
||||||
|
|
||||||
|
// Calculate the center of the board
|
||||||
|
let centerX = (widestHorizontal.0.boundingBox.origin.x + widestHorizontal.1.boundingBox.origin.x) / 2
|
||||||
|
let centerY = (tallestVertical.0.boundingBox.origin.y + tallestVertical.1.boundingBox.origin.y) / 2
|
||||||
|
|
||||||
|
// Add padding proportional to the coordinate spacing
|
||||||
|
let padding = max(boardWidth, boardHeight) * 0.25 // 25% padding
|
||||||
|
|
||||||
|
// Set rectangle to centered board with padding
|
||||||
|
transformedRect = CGRect(
|
||||||
|
x: centerX - (boardWidth / 2) - padding,
|
||||||
|
y: centerY - (boardHeight / 2) - padding,
|
||||||
|
width: boardWidth + (padding * 2),
|
||||||
|
height: boardHeight + (padding * 2)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we capture full board with reasonable bounds before aspect ratio adjustment
|
||||||
|
transformedRect.origin.x = max(-0.1, transformedRect.origin.x) // Allow 10% outside bounds
|
||||||
|
transformedRect.origin.y = max(-0.1, transformedRect.origin.y)
|
||||||
|
transformedRect.size.width = min(1.2 - transformedRect.origin.x, transformedRect.size.width) // Allow 20% overflow
|
||||||
|
transformedRect.size.height = min(1.2 - transformedRect.origin.y, transformedRect.size.height)
|
||||||
|
|
||||||
|
// Apply single aspect ratio adjustment with flexible tolerance
|
||||||
|
let targetAspectRatio: CGFloat = 1.0
|
||||||
|
let currentAspectRatio = transformedRect.size.width / transformedRect.size.height
|
||||||
|
if abs(currentAspectRatio - targetAspectRatio) > 0.2 {
|
||||||
|
if currentAspectRatio > targetAspectRatio {
|
||||||
|
let newHeight = transformedRect.size.width
|
||||||
|
let heightDiff = newHeight - transformedRect.size.height
|
||||||
|
transformedRect.origin.y -= heightDiff / 2.0
|
||||||
|
transformedRect.size.height = newHeight
|
||||||
|
} else {
|
||||||
|
let newWidth = transformedRect.size.height
|
||||||
|
let widthDiff = newWidth - transformedRect.size.width
|
||||||
|
transformedRect.origin.x -= widthDiff / 2.0
|
||||||
|
transformedRect.size.width = newWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify subscribers
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: .boardDetected,
|
||||||
|
object: nil,
|
||||||
|
userInfo: ["rect": transformedRect]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Coordinate Transformation
|
||||||
|
private func transformRectangle(_ rect: VNRectangleObservation) -> CGRect {
|
||||||
|
// Vision coordinates are in normalized coordinates with origin at bottom-left
|
||||||
|
// We need to convert to top-left origin coordinates
|
||||||
|
return CGRect(
|
||||||
|
x: rect.boundingBox.origin.x,
|
||||||
|
y: 1 - rect.boundingBox.origin.y - rect.boundingBox.height,
|
||||||
|
width: rect.boundingBox.width,
|
||||||
|
height: rect.boundingBox.height
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Capture Control
|
||||||
|
func stopCapture() {
|
||||||
|
print("Stopping screen capture...")
|
||||||
|
captureSession?.stopCapture()
|
||||||
|
captureSession = nil
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: .captureStateChanged,
|
||||||
|
object: nil,
|
||||||
|
userInfo: ["isCapturing": false]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Error Handling
|
||||||
|
enum ScreenCaptureError: Error {
|
||||||
|
case chessWindowNotFound
|
||||||
|
case streamCreationFailed
|
||||||
|
case permissionDenied
|
||||||
|
case captureAlreadyRunning
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - SCStreamOutput
|
||||||
|
extension ScreenCapture: SCStreamOutput {
|
||||||
|
func stream(_ stream: SCStream, didOutputSampleBuffer sampleBuffer: CMSampleBuffer, of type: SCStreamOutputType) {
|
||||||
|
guard type == .screen else { return }
|
||||||
|
processFrame(sampleBuffer: sampleBuffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Notifications
|
||||||
|
extension Notification.Name {
|
||||||
|
static let boardDetected = Notification.Name("com.chessprism.boardDetected")
|
||||||
|
static let captureStateChanged = Notification.Name("com.chessprism.captureStateChanged")
|
||||||
|
static let capturedFrame = Notification.Name("com.chessprism.capturedFrame")
|
||||||
|
static let boardCoordinatesDetected = Notification.Name("com.chessprism.boardCoordinatesDetected")
|
||||||
|
}
|
||||||
157
ChessPrism/ChessPrism/ScreenCaptureViewModel.swift
Normal file
157
ChessPrism/ChessPrism/ScreenCaptureViewModel.swift
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
class ScreenCaptureViewModel: ObservableObject {
|
||||||
|
// MARK: - Published Properties
|
||||||
|
@Published var isCapturing = false
|
||||||
|
@Published var captureError: ScreenCaptureError? = nil
|
||||||
|
@Published var detectedBoardRect: CGRect? = nil
|
||||||
|
@Published var capturedImage: NSImage? = nil
|
||||||
|
@Published var croppedBoardImage: NSImage? = nil
|
||||||
|
|
||||||
|
// MARK: - Dependencies
|
||||||
|
private let screenCapture = ScreenCapture()
|
||||||
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
// MARK: - Initialization
|
||||||
|
init() {
|
||||||
|
setupObservers()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Screen Capture Management
|
||||||
|
func startCapture() async {
|
||||||
|
guard await requestScreenCapturePermission() else {
|
||||||
|
captureError = .permissionDenied
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
try await screenCapture.startCapture()
|
||||||
|
isCapturing = true
|
||||||
|
} catch let error as ScreenCapture.ScreenCaptureError {
|
||||||
|
captureError = ScreenCaptureError(from: error)
|
||||||
|
} catch {
|
||||||
|
captureError = .unknown(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopCapture() {
|
||||||
|
screenCapture.stopCapture()
|
||||||
|
isCapturing = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Permission Handling
|
||||||
|
private func requestScreenCapturePermission() async -> Bool {
|
||||||
|
return await withCheckedContinuation { continuation in
|
||||||
|
let status = CGPreflightScreenCaptureAccess()
|
||||||
|
if status {
|
||||||
|
continuation.resume(returning: true)
|
||||||
|
} else {
|
||||||
|
CGRequestScreenCaptureAccess()
|
||||||
|
continuation.resume(returning: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Notification Handling
|
||||||
|
private func setupObservers() {
|
||||||
|
// Observe board detection
|
||||||
|
NotificationCenter.default
|
||||||
|
.publisher(for: .boardDetected)
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink(receiveValue: { [weak self] notification in
|
||||||
|
guard let rect = notification.userInfo?["rect"] as? CGRect else { return }
|
||||||
|
self?.detectedBoardRect = rect
|
||||||
|
})
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
// Observe capture state changes
|
||||||
|
NotificationCenter.default
|
||||||
|
.publisher(for: .captureStateChanged)
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink(receiveValue: { [weak self] notification in
|
||||||
|
guard let isCapturing = notification.userInfo?["isCapturing"] as? Bool else { return }
|
||||||
|
self?.isCapturing = isCapturing
|
||||||
|
})
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
// Observe captured frames
|
||||||
|
NotificationCenter.default
|
||||||
|
.publisher(for: .capturedFrame)
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink(receiveValue: { [weak self] notification in
|
||||||
|
guard let image = notification.userInfo?["image"] as? NSImage,
|
||||||
|
let self = self else { return }
|
||||||
|
|
||||||
|
self.capturedImage = image
|
||||||
|
|
||||||
|
// Crop to detected board area if available
|
||||||
|
if let rect = self.detectedBoardRect {
|
||||||
|
let cropped = self.cropImage(image, to: rect)
|
||||||
|
self.croppedBoardImage = cropped
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Error Handling
|
||||||
|
enum ScreenCaptureError: Error, LocalizedError {
|
||||||
|
case permissionDenied
|
||||||
|
case chessWindowNotFound
|
||||||
|
case streamCreationFailed
|
||||||
|
case captureAlreadyRunning
|
||||||
|
case unknown(Error)
|
||||||
|
|
||||||
|
init(from error: ScreenCapture.ScreenCaptureError) {
|
||||||
|
switch error {
|
||||||
|
case .chessWindowNotFound:
|
||||||
|
self = .chessWindowNotFound
|
||||||
|
case .streamCreationFailed:
|
||||||
|
self = .streamCreationFailed
|
||||||
|
case .permissionDenied:
|
||||||
|
self = .permissionDenied
|
||||||
|
case .captureAlreadyRunning:
|
||||||
|
self = .captureAlreadyRunning
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var errorDescription: String? {
|
||||||
|
switch self {
|
||||||
|
case .permissionDenied:
|
||||||
|
return "Screen recording permission is required"
|
||||||
|
case .chessWindowNotFound:
|
||||||
|
return "Unable to find Chess window (make sure Chess.com app or Firefox is open)"
|
||||||
|
case .streamCreationFailed:
|
||||||
|
return "Failed to create screen capture stream"
|
||||||
|
case .captureAlreadyRunning:
|
||||||
|
return "Capture is already running"
|
||||||
|
case .unknown(let error):
|
||||||
|
return "An unexpected error occurred: \(error.localizedDescription)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func cropImage(_ image: NSImage, to rect: CGRect) -> NSImage? {
|
||||||
|
guard let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert normalized rect to pixel coordinates
|
||||||
|
let width = CGFloat(cgImage.width)
|
||||||
|
let height = CGFloat(cgImage.height)
|
||||||
|
let cropRect = CGRect(
|
||||||
|
x: rect.origin.x * width,
|
||||||
|
y: (1 - rect.origin.y - rect.height) * height, // Flip Y coordinate since NSImage uses bottom-left origin
|
||||||
|
width: rect.width * width,
|
||||||
|
height: rect.height * height
|
||||||
|
)
|
||||||
|
|
||||||
|
guard let croppedCGImage = cgImage.cropping(to: cropRect) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return NSImage(cgImage: croppedCGImage, size: cropRect.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,31 +1,50 @@
|
||||||
# Active Context
|
# Active Context
|
||||||
|
|
||||||
# Active Context
|
|
||||||
|
|
||||||
## Current Status
|
## Current Status
|
||||||
- Xcode project configuration completed
|
- Screen capture module implemented
|
||||||
- Development environment setup verified
|
- Pattern recognition-based board detection implemented
|
||||||
- Project architecture finalized
|
- Coordinate-based detection retained as fallback
|
||||||
- Code signing verified with:
|
- SwiftUI interface for capture controls added
|
||||||
* Team ID: RJHWWWSF6Q
|
- Proper resource cleanup implemented
|
||||||
* Signing Identity: S6HYLY2Y7Y
|
- Error handling system in place
|
||||||
* Certificate: Apple Development: Chris Haulmark (6FXX65C28T)
|
|
||||||
- Apple Developer Program enrollment completed
|
|
||||||
- App ID registered: com.chessprism.ChessPrism
|
|
||||||
- Development device registered: M3-Haulmark
|
|
||||||
- Initial build successful with Hello World implementation
|
|
||||||
- Certificate and provisioning profile properly configured
|
|
||||||
- Basic SwiftUI implementation verified
|
|
||||||
- Build and deployment pipeline confirmed working
|
|
||||||
|
|
||||||
## Recent Changes
|
## Recent Changes
|
||||||
- Cleaned up and consolidated development certificates
|
- Refined pattern recognition approach for chess board detection:
|
||||||
- Created fresh provisioning profile
|
1. Modified rectangle detection parameters:
|
||||||
- Verified code signing configuration
|
- Using 0.3-0.5 aspect ratio for taller rectangles
|
||||||
- Implemented basic SwiftUI interface
|
- Increased minimum size to 0.4
|
||||||
- Confirmed build and run process
|
- Single observation for precision
|
||||||
|
2. Improved board extraction:
|
||||||
|
- Using detected width as reference
|
||||||
|
- Positioning square board at top of detected area
|
||||||
|
- Using offsetBy for vertical positioning
|
||||||
|
3. Simplified coordinate handling:
|
||||||
|
- Direct rectangle detection with Vision framework
|
||||||
|
- Proper coordinate system transformations
|
||||||
|
- Maintained pattern matching for accuracy
|
||||||
|
|
||||||
|
## Current Challenges
|
||||||
|
- Board detection still not capturing full height
|
||||||
|
- Need to investigate if issue is with:
|
||||||
|
1. Detection parameters
|
||||||
|
2. Coordinate transformations
|
||||||
|
3. View rendering constraints
|
||||||
|
4. Window/display configuration
|
||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
- Begin implementing core application features
|
1. Further refine board detection:
|
||||||
- Set up CI/CD pipeline
|
- Test different aspect ratios
|
||||||
- Configure automated testing
|
- Validate coordinate transformations
|
||||||
|
- Review view constraints
|
||||||
|
2. Add board position analysis
|
||||||
|
3. Implement move detection
|
||||||
|
4. Create visual overlay system
|
||||||
|
5. Integrate Stockfish engine
|
||||||
|
|
||||||
|
## Known Issues
|
||||||
|
- Board detection showing only bottom portion
|
||||||
|
- Need to validate pattern detection accuracy
|
||||||
|
- Need to handle multiple display configurations
|
||||||
|
- Requires testing with different screen resolutions
|
||||||
|
- Need to add proper error recovery
|
||||||
|
- Requires additional permission handling for production
|
||||||
|
|
|
||||||
53
cline_docs/error
Normal file
53
cline_docs/error
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
Starting screen capture...
|
||||||
|
Available content: 1 displays, 32 windows
|
||||||
|
AddInstanceForFactory: No factory registered for id <CFUUID 0x6000011a9580> F8BB1C28-BAE8-11D6-9C31-00039315CD46
|
||||||
|
Screen capture started successfully
|
||||||
|
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<NSNotificationCenter 0x6000013bc060> valueForUndefinedKey:]: this class is not key value coding-compliant for the key lastDetectedCoordinates.'
|
||||||
|
*** First throw call stack:
|
||||||
|
(
|
||||||
|
0 CoreFoundation 0x0000000189d0ee80 __exceptionPreprocess + 176
|
||||||
|
1 libobjc.A.dylib 0x00000001897f6cd8 objc_exception_throw + 88
|
||||||
|
2 CoreFoundation 0x0000000189d33534 _CFBundleGetValueForInfoKey + 0
|
||||||
|
3 Foundation 0x000000018b6dbd84 -[NSObject(NSKeyValueCoding) valueForUndefinedKey:] + 196
|
||||||
|
4 Foundation 0x000000018ae402fc -[NSObject(NSKeyValueCoding) valueForKey:] + 280
|
||||||
|
5 ChessPrism.debug.dylib 0x0000000100b8f834 $s10ChessPrism13ScreenCaptureC20handleBoardDetection33_6963F6DFCAB5A0A312B3D7446D299968LL7requestySo9VNRequestC_tF + 740
|
||||||
|
6 ChessPrism.debug.dylib 0x0000000100b8e644 $s10ChessPrism13ScreenCaptureC12processFrame33_6963F6DFCAB5A0A312B3D7446D299968LL12sampleBufferySo08CMSampleO3Refa_tFySo9VNRequestC_s5Error_pSgtcfU0_ + 200
|
||||||
|
7 ChessPrism.debug.dylib 0x0000000100b93350 $sSo9VNRequestCs5Error_pSgIeggg_ABSo7NSErrorCSgIeyByy_TR + 140
|
||||||
|
8 Vision 0x000000019f40aab4 -[VNRequest performInContext:error:] + 1244
|
||||||
|
9 Vision 0x000000019f40af08 __73-[VNRequest performInContextAsync:asyncDispatchQueue:asyncDispatchGroup:]_block_invoke + 128
|
||||||
|
10 libdispatch.dylib 0x00000001001d2ac8 _dispatch_block_async_invoke2 + 148
|
||||||
|
11 libdispatch.dylib 0x00000001001be824 _dispatch_client_callout + 20
|
||||||
|
12 libdispatch.dylib 0x00000001001c2350 _dispatch_continuation_pop + 1408
|
||||||
|
13 libdispatch.dylib 0x00000001001c1028 _dispatch_async_redirect_invoke + 616
|
||||||
|
14 libdispatch.dylib 0x00000001001d6b70 _dispatch_root_queue_drain + 404
|
||||||
|
15 libdispatch.dylib 0x00000001001d777c _dispatch_worker_thread2 + 188
|
||||||
|
16 libsystem_pthread.dylib 0x0000000100b090c4 _pthread_wqthread + 228
|
||||||
|
17 libsystem_pthread.dylib 0x0000000100b10cf0 start_wqthread + 8
|
||||||
|
)
|
||||||
|
An uncaught exception was raised
|
||||||
|
[<NSNotificationCenter 0x6000013bc060> valueForUndefinedKey:]: this class is not key value coding-compliant for the key lastDetectedCoordinates.
|
||||||
|
(
|
||||||
|
0 CoreFoundation 0x0000000189d0ee80 __exceptionPreprocess + 176
|
||||||
|
1 libobjc.A.dylib 0x00000001897f6cd8 objc_exception_throw + 88
|
||||||
|
2 CoreFoundation 0x0000000189d33534 _CFBundleGetValueForInfoKey + 0
|
||||||
|
3 Foundation 0x000000018b6dbd84 -[NSObject(NSKeyValueCoding) valueForUndefinedKey:] + 196
|
||||||
|
4 Foundation 0x000000018ae402fc -[NSObject(NSKeyValueCoding) valueForKey:] + 280
|
||||||
|
5 ChessPrism.debug.dylib 0x0000000100b8f834 $s10ChessPrism13ScreenCaptureC20handleBoardDetection33_6963F6DFCAB5A0A312B3D7446D299968LL7requestySo9VNRequestC_tF + 740
|
||||||
|
6 ChessPrism.debug.dylib 0x0000000100b8e644 $s10ChessPrism13ScreenCaptureC12processFrame33_6963F6DFCAB5A0A312B3D7446D299968LL12sampleBufferySo08CMSampleO3Refa_tFySo9VNRequestC_s5Error_pSgtcfU0_ + 200
|
||||||
|
7 ChessPrism.debug.dylib 0x0000000100b93350 $sSo9VNRequestCs5Error_pSgIeggg_ABSo7NSErrorCSgIeyByy_TR + 140
|
||||||
|
8 Vision 0x000000019f40aab4 -[VNRequest performInContext:error:] + 1244
|
||||||
|
9 Vision 0x000000019f40af08 __73-[VNRequest performInContextAsync:asyncDispatchQueue:asyncDispatchGroup:]_block_invoke + 128
|
||||||
|
10 libdispatch.dylib 0x00000001001d2ac8 _dispatch_block_async_invoke2 + 148
|
||||||
|
11 libdispatch.dylib 0x00000001001be824 _dispatch_client_callout + 20
|
||||||
|
12 libdispatch.dylib 0x00000001001c2350 _dispatch_continuation_pop + 1408
|
||||||
|
13 libdispatch.dylib 0x00000001001c1028 _dispatch_async_redirect_invoke + 616
|
||||||
|
14 libdispatch.dylib 0x00000001001d6b70 _dispatch_root_queue_drain + 404
|
||||||
|
15 libdispatch.dylib 0x00000001001d777c _dispatch_worker_thread2 + 188
|
||||||
|
16 libsystem_pthread.dylib 0x0000000100b090c4 _pthread_wqthread + 228
|
||||||
|
17 libsystem_pthread.dylib 0x0000000100b10cf0 start_wqthread + 8
|
||||||
|
)
|
||||||
|
FAULT: NSUnknownKeyException: [<NSNotificationCenter 0x6000013bc060> valueForUndefinedKey:]: this class is not key value coding-compliant for the key lastDetectedCoordinates.; {
|
||||||
|
NSTargetObjectUserInfoKey = "<CFNotificationCenter 0x6000011a4900 [0x1f4722240]>";
|
||||||
|
NSUnknownUserInfoKey = lastDetectedCoordinates;
|
||||||
|
}
|
||||||
|
libc++abi: terminating due to uncaught exception of type NSException
|
||||||
106
cline_docs/problem.md
Normal file
106
cline_docs/problem.md
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
# Chess Board Detection Problem
|
||||||
|
|
||||||
|
## Issue Description
|
||||||
|
The chess board detection system is currently only displaying the bottom 2-3 rows of the chess board in the "Chessboard" preview window, while the "Full Capture" window shows the complete chess board. The width of the detected board is correct, but the height is truncated.
|
||||||
|
|
||||||
|
## Visual Evidence
|
||||||
|
- Full Capture: Shows complete chess board with all pieces
|
||||||
|
- Chessboard Preview: Shows only bottom portion (approximately 2.5 rows) of the board
|
||||||
|
- Width appears correct in both views
|
||||||
|
- Height is significantly truncated in Chessboard Preview
|
||||||
|
|
||||||
|
## Technical Analysis
|
||||||
|
|
||||||
|
### Detection Pipeline
|
||||||
|
1. Screen capture works correctly (evidenced by Full Capture view)
|
||||||
|
2. Initial board detection appears to find correct width
|
||||||
|
3. Problem occurs during either:
|
||||||
|
- Rectangle detection phase
|
||||||
|
- Coordinate transformation
|
||||||
|
- Image cropping stage
|
||||||
|
|
||||||
|
### Coordinate System Complexity
|
||||||
|
1. Multiple coordinate systems involved:
|
||||||
|
- Vision framework (bottom-left origin)
|
||||||
|
- NSImage/CGImage (bottom-left origin)
|
||||||
|
- SwiftUI (top-left origin)
|
||||||
|
2. Current transformations may be:
|
||||||
|
- Incorrectly mapping between coordinate spaces
|
||||||
|
- Losing vertical positioning information
|
||||||
|
- Miscalculating crop region
|
||||||
|
|
||||||
|
### Detection Parameters
|
||||||
|
1. Current approach:
|
||||||
|
- Using 0.3-0.5 aspect ratio for detection
|
||||||
|
- 0.4 minimum size requirement
|
||||||
|
- Single observation limit
|
||||||
|
2. These parameters may be:
|
||||||
|
- Causing partial detection of board
|
||||||
|
- Missing full vertical extent
|
||||||
|
- Incorrectly identifying board boundaries
|
||||||
|
|
||||||
|
## Code Areas to Investigate
|
||||||
|
|
||||||
|
### BoardDetector.swift
|
||||||
|
1. detectBoard() function:
|
||||||
|
- Board extraction logic
|
||||||
|
- Coordinate calculations
|
||||||
|
- Rectangle positioning
|
||||||
|
|
||||||
|
2. Rectangle Detection:
|
||||||
|
```swift
|
||||||
|
request.minimumAspectRatio = 0.3
|
||||||
|
request.maximumAspectRatio = 0.5
|
||||||
|
request.minimumSize = 0.4
|
||||||
|
```
|
||||||
|
- May need adjustment for full board capture
|
||||||
|
|
||||||
|
### ScreenCaptureViewModel.swift
|
||||||
|
1. Image cropping logic:
|
||||||
|
- Coordinate transformation
|
||||||
|
- Crop region calculation
|
||||||
|
- Final image generation
|
||||||
|
|
||||||
|
## Potential Solutions to Explore
|
||||||
|
|
||||||
|
1. Detection Approach
|
||||||
|
- Adjust aspect ratio constraints
|
||||||
|
- Modify detection parameters
|
||||||
|
- Consider alternative detection methods
|
||||||
|
|
||||||
|
2. Coordinate Handling
|
||||||
|
- Review all coordinate transformations
|
||||||
|
- Validate coordinate space conversions
|
||||||
|
- Ensure proper origin point handling
|
||||||
|
|
||||||
|
3. Image Processing
|
||||||
|
- Verify crop region calculations
|
||||||
|
- Review image scaling operations
|
||||||
|
- Validate final image generation
|
||||||
|
|
||||||
|
4. Alternative Approaches
|
||||||
|
- Use full-frame detection
|
||||||
|
- Implement grid-based detection
|
||||||
|
- Consider machine learning approach
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
- Critical functionality issue
|
||||||
|
- Blocks accurate board analysis
|
||||||
|
- Affects user experience
|
||||||
|
- Prevents proper game state detection
|
||||||
|
|
||||||
|
## Priority
|
||||||
|
HIGH - This issue blocks core functionality of the chess analysis system.
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
1. Verify coordinate system transformations
|
||||||
|
2. Test different aspect ratio parameters
|
||||||
|
3. Implement logging for detection boundaries
|
||||||
|
4. Add visualization of detected regions
|
||||||
|
5. Consider alternative detection approaches
|
||||||
|
|
||||||
|
## Additional Notes
|
||||||
|
- The issue appears consistent across different games
|
||||||
|
- Width detection is working correctly
|
||||||
|
- Height truncation is consistent (showing ~2.5 rows)
|
||||||
|
- Full board is visible in capture, suggesting screen capture is working
|
||||||
|
|
@ -1,143 +1,175 @@
|
||||||
# Product Context
|
# Product Context
|
||||||
|
|
||||||
## Product Vision
|
## Project Overview
|
||||||
To create an intuitive, real-time chess teaching assistant that enhances the learning experience for both instructors and students during live gameplay.
|
ChessPrism is an advanced chess analysis tool that enhances the online chess experience by providing real-time analysis and move suggestions. It works by capturing and analyzing the chess board from popular chess websites and platforms.
|
||||||
|
|
||||||
## Core Value Proposition
|
## Core Problems Solved
|
||||||
1. Real-time visual feedback during live games
|
|
||||||
2. Integrated analysis and teaching tools
|
|
||||||
3. Seamless integration with chess.com
|
|
||||||
4. Performance-optimized for MacOS Silicon
|
|
||||||
|
|
||||||
## Target Users
|
### Chess Analysis Accessibility
|
||||||
### User Personas
|
- Makes professional-level chess analysis accessible during online play
|
||||||
1. Chess Instructors
|
- Provides real-time insights without manual position input
|
||||||
- Needs: Real-time analysis, teaching tools, visual aids
|
- Integrates seamlessly with existing chess platforms
|
||||||
- Goals: Effective teaching, student engagement
|
|
||||||
- Pain Points: Complex setup, delayed feedback
|
|
||||||
|
|
||||||
2. Advanced Players
|
### Visual Recognition
|
||||||
- Needs: Move analysis, threat detection
|
- Accurately detects chess board from screen content
|
||||||
- Goals: Improve teaching skills, analyze games
|
- Recognizes board coordinates and boundaries
|
||||||
- Pain Points: Limited teaching tools
|
- Handles various board themes and orientations
|
||||||
|
- Maintains accuracy during game play
|
||||||
|
|
||||||
3. Chess Streamers
|
### Real-time Processing
|
||||||
- Needs: Visual overlays, real-time analysis
|
- Captures and processes screen content in real-time
|
||||||
- Goals: Engaging content, clear explanations
|
- Provides immediate feedback and analysis
|
||||||
- Pain Points: Complex overlay systems
|
- Maintains performance during long sessions
|
||||||
|
|
||||||
4. Self-Learners
|
|
||||||
- Needs: Position evaluation, move alternatives
|
|
||||||
- Goals: Game improvement, pattern recognition
|
|
||||||
- Pain Points: Lack of real-time feedback
|
|
||||||
|
|
||||||
## Key Features
|
|
||||||
1. Real-time board position analysis
|
|
||||||
2. Visual move suggestions and explanations
|
|
||||||
3. Threat and defense visualization
|
|
||||||
4. Position evaluation and move alternatives
|
|
||||||
5. Customizable visual overlay system
|
|
||||||
|
|
||||||
## User Workflows
|
|
||||||
### Teaching Scenario
|
|
||||||
1. Instructor starts chess.com game
|
|
||||||
2. Application detects board position
|
|
||||||
3. Real-time visual feedback appears
|
|
||||||
4. Instructor explains moves using visual aids
|
|
||||||
5. Students see analysis and suggestions
|
|
||||||
|
|
||||||
### Self-Learning Scenario
|
|
||||||
1. Player starts game on chess.com
|
|
||||||
2. Application provides real-time feedback
|
|
||||||
3. Player sees move suggestions and threats
|
|
||||||
4. Application highlights tactical patterns
|
|
||||||
5. Player reviews game analysis post-match
|
|
||||||
|
|
||||||
## User Experience Goals
|
## User Experience Goals
|
||||||
1. Intuitive and non-intrusive interface
|
|
||||||
2. Real-time responsiveness (<100ms latency)
|
|
||||||
3. Clear visual feedback system
|
|
||||||
4. Customizable teaching tools
|
|
||||||
5. Seamless integration with chess.com
|
|
||||||
|
|
||||||
## Visual Feedback System
|
### Seamless Integration
|
||||||
1. Move Visualization:
|
1. Non-intrusive Operation
|
||||||
- Arrows for suggested moves
|
- Works with Chess.com desktop app
|
||||||
- Color-coded threat levels
|
- Minimal setup requirements
|
||||||
- Highlighted squares
|
- Automatic board detection and tracking
|
||||||
|
|
||||||
2. Position Analysis:
|
2. Intuitive Interface
|
||||||
- Evaluation bar
|
- Clear visualization of analysis
|
||||||
- Move alternatives
|
- Easy-to-understand suggestions
|
||||||
- Threat indicators
|
- Minimal user intervention required
|
||||||
|
|
||||||
3. Teaching Aids:
|
### Reliable Detection
|
||||||
- Defensive patterns
|
1. Board Recognition
|
||||||
- Attack vectors
|
- Two-phase detection strategy:
|
||||||
- Piece mobility
|
* Pattern recognition for known interfaces
|
||||||
|
* Coordinate-based fallback for reliability
|
||||||
|
- Proper coordinate system handling
|
||||||
|
- Consistent board capture across sessions
|
||||||
|
|
||||||
## Accessibility Features
|
2. Position Analysis
|
||||||
1. Color-blind friendly themes
|
- Accurate piece recognition (planned)
|
||||||
2. Keyboard navigation
|
- Current position evaluation (planned)
|
||||||
3. Screen reader support
|
- Move suggestion visualization (planned)
|
||||||
4. Adjustable overlay size
|
|
||||||
5. High-contrast modes
|
## Target Users
|
||||||
|
|
||||||
|
### Chess Players
|
||||||
|
- Amateur to intermediate players
|
||||||
|
- Chess.com desktop app users
|
||||||
|
- Players seeking to improve
|
||||||
|
|
||||||
|
### Use Cases
|
||||||
|
1. Learning
|
||||||
|
- Understanding position evaluation
|
||||||
|
- Learning from mistakes
|
||||||
|
- Exploring alternative moves
|
||||||
|
|
||||||
|
2. Analysis
|
||||||
|
- Real-time position assessment
|
||||||
|
- Move validation
|
||||||
|
- Strategic planning
|
||||||
|
|
||||||
|
## Product Requirements
|
||||||
|
|
||||||
|
### Essential Features
|
||||||
|
1. Board Detection (Current Focus)
|
||||||
|
- Accurate boundary recognition
|
||||||
|
- Full board capture
|
||||||
|
- Support for Chess.com desktop app
|
||||||
|
- Reliable coordinate transformations
|
||||||
|
|
||||||
|
2. Position Analysis (Planned)
|
||||||
|
- Real-time evaluation
|
||||||
|
- Move suggestions
|
||||||
|
- Tactical opportunities
|
||||||
|
|
||||||
|
3. User Interface
|
||||||
|
- Analysis overlay
|
||||||
|
- Control panel
|
||||||
|
- Settings management
|
||||||
|
|
||||||
|
### Quality Standards
|
||||||
|
1. Accuracy
|
||||||
|
- Reliable board detection
|
||||||
|
- Complete board capture
|
||||||
|
- Precise coordinate handling
|
||||||
|
|
||||||
|
2. Performance
|
||||||
|
- Real-time processing
|
||||||
|
- Minimal resource usage
|
||||||
|
- Stable operation
|
||||||
|
|
||||||
|
3. Usability
|
||||||
|
- Intuitive controls
|
||||||
|
- Clear feedback
|
||||||
|
- Minimal setup
|
||||||
|
|
||||||
## Success Metrics
|
## Success Metrics
|
||||||
1. Performance:
|
|
||||||
- <100ms analysis latency
|
|
||||||
- 99.9% board recognition accuracy
|
|
||||||
- 120fps overlay rendering
|
|
||||||
2. Usability:
|
|
||||||
- <5 minute setup time
|
|
||||||
- 90% instructor satisfaction rate
|
|
||||||
- <1% error rate in move suggestions
|
|
||||||
3. Adoption:
|
|
||||||
- 1000+ active users in first 6 months
|
|
||||||
- 90% retention rate after 30 days
|
|
||||||
- 4.5+ average rating on App Store
|
|
||||||
|
|
||||||
## Competitive Advantages
|
### Technical Metrics
|
||||||
1. Native MacOS Silicon optimization
|
- Board detection accuracy rate
|
||||||
2. Real-time performance with Metal acceleration
|
- Full board capture success rate
|
||||||
3. Integrated teaching tools
|
- Processing speed per frame
|
||||||
4. Chess.com specific optimizations
|
- Error recovery rate
|
||||||
5. Privacy-focused design
|
|
||||||
|
|
||||||
## Development Principles
|
### User Metrics
|
||||||
1. User-centric design
|
- Setup success rate
|
||||||
2. Performance-first approach
|
- Analysis accuracy
|
||||||
3. Modular architecture
|
- User engagement time
|
||||||
4. Continuous testing and refinement
|
- Feature utilization
|
||||||
5. Security and privacy compliance
|
|
||||||
|
## Current Challenges
|
||||||
|
|
||||||
|
### Board Detection
|
||||||
|
1. Coordinate Systems
|
||||||
|
- Vision framework (bottom-left origin)
|
||||||
|
- NSImage/CGImage (bottom-left origin)
|
||||||
|
- SwiftUI (top-left origin)
|
||||||
|
- Proper transformations between systems
|
||||||
|
|
||||||
|
2. Detection Accuracy
|
||||||
|
- Full board capture
|
||||||
|
- Consistent positioning
|
||||||
|
- Reliable boundaries
|
||||||
|
|
||||||
|
### Next Steps
|
||||||
|
1. Refine board detection
|
||||||
|
- Improve coordinate handling
|
||||||
|
- Ensure full board capture
|
||||||
|
- Validate transformations
|
||||||
|
|
||||||
|
2. Move to position analysis
|
||||||
|
- Piece recognition
|
||||||
|
- Position evaluation
|
||||||
|
- Move suggestions
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
### Planned Features
|
||||||
|
1. Advanced Analysis
|
||||||
|
- Deep position evaluation
|
||||||
|
- Opening recognition
|
||||||
|
- Endgame tablebases
|
||||||
|
|
||||||
|
2. Learning Tools
|
||||||
|
- Mistake analysis
|
||||||
|
- Improvement suggestions
|
||||||
|
- Progress tracking
|
||||||
|
|
||||||
|
3. Customization
|
||||||
|
- Analysis depth control
|
||||||
|
- Visual preference settings
|
||||||
|
- Platform-specific optimizations
|
||||||
|
|
||||||
## Product Roadmap
|
## Product Roadmap
|
||||||
### Phase 1: Core Functionality (Weeks 1-6)
|
|
||||||
- Basic board recognition
|
|
||||||
- Stockfish integration
|
|
||||||
- Visual overlay foundation
|
|
||||||
|
|
||||||
### Phase 2: Teaching Tools (Weeks 7-10)
|
### Current Phase
|
||||||
- Move visualization system
|
- Core board detection system
|
||||||
- Threat analysis
|
- Coordinate system handling
|
||||||
- Defensive patterns
|
- Basic user interface
|
||||||
|
|
||||||
### Phase 3: Polish & Optimization (Weeks 11-14)
|
### Next Phase
|
||||||
- Performance tuning
|
- Position analysis
|
||||||
- UI/UX refinement
|
- Move suggestion system
|
||||||
- Security implementation
|
- Visual overlay implementation
|
||||||
|
|
||||||
## Key Milestones
|
### Future Phase
|
||||||
1. Week 4: Functional board recognition
|
- Advanced analysis features
|
||||||
2. Week 6: Working Stockfish analysis
|
- Learning tools integration
|
||||||
3. Week 8: Basic visual overlay system
|
- Customization options
|
||||||
4. Week 10: Complete visualization system
|
|
||||||
5. Week 12: Optimized performance
|
|
||||||
6. Week 14: Ready for distribution
|
|
||||||
|
|
||||||
## Development Status
|
|
||||||
### Completed Setup Steps
|
|
||||||
1. Apple Developer Program enrollment
|
|
||||||
2. App ID registration
|
|
||||||
3. Device registration
|
|
||||||
4. Initial capabilities configuration
|
|
||||||
|
|
|
||||||
|
|
@ -1,254 +1,122 @@
|
||||||
# System Patterns
|
# System Patterns
|
||||||
|
|
||||||
## Architecture Overview
|
## Core Architecture
|
||||||
### Core Components
|
|
||||||
1. Screenshot Capture Module
|
|
||||||
- Screen recording permission handling
|
|
||||||
- Multi-monitor support
|
|
||||||
- Keyboard shortcut system
|
|
||||||
- Firefox window detection
|
|
||||||
|
|
||||||
2. Board Position Analysis Engine
|
### Screen Capture System
|
||||||
- Vision framework integration
|
- Uses ScreenCaptureKit for efficient screen capture
|
||||||
- CoreML model inference
|
- Implements SCStreamOutput protocol for frame processing
|
||||||
- FEN conversion logic
|
- Handles capture session lifecycle and cleanup
|
||||||
- Position validation system
|
- Manages permissions and error handling
|
||||||
|
|
||||||
3. Visual Overlay System
|
### Board Detection System
|
||||||
- Metal rendering pipeline
|
Two implemented approaches:
|
||||||
- Transparent window system
|
|
||||||
- Coordinate transformation
|
|
||||||
- Move visualization components
|
|
||||||
|
|
||||||
4. Stockfish Integration
|
1. Pattern Recognition Approach (Primary)
|
||||||
- ARM64 binary integration
|
- Rectangle detection with Vision framework
|
||||||
- Async engine wrapper
|
- Aspect ratio-based filtering (0.3-0.5 for taller rectangles)
|
||||||
- Position analysis pipeline
|
- Size-based filtering (0.4 minimum for larger areas)
|
||||||
- Evaluation caching
|
- Single observation for precision
|
||||||
|
- Board extraction from upper portion
|
||||||
|
- Width-based square calculation
|
||||||
|
|
||||||
5. Machine Learning Pipeline
|
2. Coordinate Detection (Fallback)
|
||||||
- Model training framework
|
- Text recognition for board coordinates
|
||||||
- Theme detection system
|
- Rectangle detection with Vision framework
|
||||||
- Piece recognition models
|
- Grid-based validation
|
||||||
- Model versioning system
|
- Coordinate-based refinement
|
||||||
|
|
||||||
## Architectural Patterns
|
3. Common Infrastructure
|
||||||
### Clean Architecture Layers
|
- Asynchronous frame processing
|
||||||
1. Presentation Layer (SwiftUI)
|
- Dedicated processing queue
|
||||||
- User interface components
|
- Efficient memory management
|
||||||
- View models
|
- Performance monitoring
|
||||||
- State management
|
|
||||||
|
|
||||||
2. Domain Layer
|
### Coordinate Systems
|
||||||
- Core business logic
|
- Vision framework: Bottom-left origin (0,0)
|
||||||
- Use cases
|
- NSImage/CGImage: Bottom-left origin (0,0)
|
||||||
- Domain models
|
- SwiftUI: Top-left origin (0,0)
|
||||||
|
- Transformations needed between systems:
|
||||||
|
1. Vision → Screen: Flip Y coordinate
|
||||||
|
2. Screen → Image: Direct mapping
|
||||||
|
3. Image → View: SwiftUI handles automatically
|
||||||
|
|
||||||
3. Data Layer
|
### Notification System
|
||||||
- Repositories
|
- Uses NotificationCenter for event propagation
|
||||||
- Data sources
|
- Key notifications:
|
||||||
- Network services
|
- boardDetected: Sends detected board rectangle and confidence score
|
||||||
|
- captureStateChanged: Updates capture status
|
||||||
|
- capturedFrame: Delivers processed frames
|
||||||
|
- boardCoordinatesDetected: Reports coordinate detection
|
||||||
|
- detectionStats: Reports performance metrics
|
||||||
|
|
||||||
### Design Patterns
|
## Design Patterns
|
||||||
1. MVVM for UI components
|
|
||||||
2. Dependency Injection for service composition
|
|
||||||
3. Observer pattern for state management
|
|
||||||
4. Factory pattern for object creation
|
|
||||||
5. Strategy pattern for analysis algorithms
|
|
||||||
|
|
||||||
## Detailed Component Specifications
|
### MVVM Architecture
|
||||||
### Metal Rendering Pipeline
|
- ScreenCapture: Model layer handling capture logic
|
||||||
1. Pipeline Stages:
|
- ScreenCaptureViewModel: View model managing UI state
|
||||||
- Vertex processing
|
- ContentView: SwiftUI view for user interface
|
||||||
- Fragment shading
|
|
||||||
- Composition
|
|
||||||
- Post-processing
|
|
||||||
|
|
||||||
2. Performance Optimization:
|
### Observer Pattern
|
||||||
- Command buffer optimization
|
- NotificationCenter for loose coupling
|
||||||
- Texture compression
|
- Enables modular component communication
|
||||||
- Shader LOD management
|
- Supports async event handling
|
||||||
- Frame pacing
|
|
||||||
|
|
||||||
3. Visual Effects:
|
### Error Handling
|
||||||
- Anti-aliasing
|
- Custom ScreenCaptureError enum
|
||||||
- Bloom effects
|
- Comprehensive error cases
|
||||||
- Motion blur
|
- Proper error propagation
|
||||||
- Depth effects
|
|
||||||
|
|
||||||
### Vision Framework Integration
|
## Technical Decisions
|
||||||
1. Image Analysis Pipeline:
|
|
||||||
- Image preprocessing
|
|
||||||
- Feature detection
|
|
||||||
- Object recognition
|
|
||||||
- Position tracking
|
|
||||||
|
|
||||||
2. Performance Considerations:
|
### Vision Framework
|
||||||
- GPU acceleration
|
- Primary tool for board detection
|
||||||
- Batch processing
|
- Provides rectangle and text detection
|
||||||
- Memory optimization
|
- Handles various board orientations
|
||||||
- Error handling
|
- Requires coordinate system transformation
|
||||||
|
|
||||||
3. Integration Points:
|
### Pattern Recognition
|
||||||
- CoreML model integration
|
- Focus on larger detection areas
|
||||||
- Metal texture sharing
|
- Use width as reference measurement
|
||||||
- SwiftUI view integration
|
- Extract square board from top portion
|
||||||
- Async/await pattern
|
- Maintain aspect ratio constraints
|
||||||
|
|
||||||
### Async/Await Patterns
|
### Performance Considerations
|
||||||
1. Concurrency Model:
|
- Dedicated dispatch queue for frame processing
|
||||||
- Task groups
|
- Efficient memory management
|
||||||
- Async sequences
|
- Proper resource cleanup
|
||||||
- Actor isolation
|
- Single observation optimization
|
||||||
- Continuations
|
|
||||||
|
|
||||||
2. Error Handling:
|
## Future Patterns
|
||||||
- Structured concurrency
|
|
||||||
- Task cancellation
|
|
||||||
- Error propagation
|
|
||||||
- Retry mechanisms
|
|
||||||
|
|
||||||
3. Performance Optimization:
|
### Planned Implementations
|
||||||
- Task prioritization
|
1. Board Position Analysis
|
||||||
- Resource contention management
|
- ML model integration
|
||||||
- Memory safety
|
- Piece detection system
|
||||||
- Thread management
|
- Position validation
|
||||||
|
|
||||||
### CoreML Model Architecture
|
2. Move Analysis
|
||||||
1. Model Specifications:
|
- Stockfish integration
|
||||||
- Input/output formats
|
- Real-time evaluation
|
||||||
- Model quantization
|
- Visual overlay system
|
||||||
- Neural engine optimization
|
|
||||||
- Model versioning
|
|
||||||
|
|
||||||
2. Training Pipeline:
|
3. State Management
|
||||||
- Data collection
|
- Game state tracking
|
||||||
- Model training
|
- Move history
|
||||||
- Validation
|
- Analysis persistence
|
||||||
- Deployment
|
|
||||||
|
|
||||||
3. Performance Considerations:
|
## Testing Patterns
|
||||||
- Batch processing
|
|
||||||
- Memory management
|
|
||||||
- Model compression
|
|
||||||
- Inference optimization
|
|
||||||
|
|
||||||
### Stockfish Integration
|
### Unit Testing
|
||||||
1. Engine Configuration:
|
- ScreenCapture functionality
|
||||||
- Thread management
|
- Board detection accuracy
|
||||||
- Hash size optimization
|
- Coordinate recognition
|
||||||
- Analysis depth
|
|
||||||
- Time controls
|
|
||||||
|
|
||||||
2. Analysis Pipeline:
|
### Integration Testing
|
||||||
- Position evaluation
|
- End-to-end capture workflow
|
||||||
- Move generation
|
- Vision framework integration
|
||||||
- Threat detection
|
- Notification system
|
||||||
- Position caching
|
|
||||||
|
|
||||||
3. Performance Optimization:
|
### UI Testing
|
||||||
- Parallel analysis
|
- SwiftUI interface validation
|
||||||
- Cache management
|
- User interaction flows
|
||||||
- Engine tuning
|
- Error state handling
|
||||||
- Resource allocation
|
|
||||||
|
|
||||||
## Data Flow Diagram
|
|
||||||
1. Input:
|
|
||||||
- Screenshot capture
|
|
||||||
- Keyboard input
|
|
||||||
- System events
|
|
||||||
|
|
||||||
2. Processing:
|
|
||||||
- Board detection
|
|
||||||
- Position analysis
|
|
||||||
- Move evaluation
|
|
||||||
- Visualization generation
|
|
||||||
|
|
||||||
3. Output:
|
|
||||||
- Visual overlay
|
|
||||||
- Move suggestions
|
|
||||||
- Position evaluation
|
|
||||||
- Threat analysis
|
|
||||||
|
|
||||||
## Error Handling Strategy
|
|
||||||
1. Input Validation:
|
|
||||||
- Screenshot quality checks
|
|
||||||
- Board position validation
|
|
||||||
- Move legality verification
|
|
||||||
|
|
||||||
2. Recovery Mechanisms:
|
|
||||||
- Automatic retry for failed operations
|
|
||||||
- Fallback analysis methods
|
|
||||||
- Graceful degradation
|
|
||||||
|
|
||||||
3. Error Reporting:
|
|
||||||
- User-friendly error messages
|
|
||||||
- Detailed error logging
|
|
||||||
- Crash reporting system
|
|
||||||
|
|
||||||
## Security Architecture
|
|
||||||
1. Data Protection:
|
|
||||||
- Secure storage for sensitive data
|
|
||||||
- Encrypted communication channels
|
|
||||||
- Data minimization principles
|
|
||||||
|
|
||||||
2. Access Control:
|
|
||||||
- Permission management system
|
|
||||||
- Role-based access control
|
|
||||||
- Activity monitoring
|
|
||||||
|
|
||||||
3. Privacy Features:
|
|
||||||
- Privacy manifests implementation
|
|
||||||
- Data collection transparency
|
|
||||||
- User consent management
|
|
||||||
|
|
||||||
## Performance Optimization
|
|
||||||
1. Rendering:
|
|
||||||
- Metal shader optimization
|
|
||||||
- Draw call batching
|
|
||||||
- Frame rate stabilization
|
|
||||||
|
|
||||||
2. Analysis:
|
|
||||||
- Position caching
|
|
||||||
- Parallel processing
|
|
||||||
- Engine optimization
|
|
||||||
|
|
||||||
3. Memory Management:
|
|
||||||
- Efficient resource allocation
|
|
||||||
- Memory leak prevention
|
|
||||||
- Garbage collection tuning
|
|
||||||
|
|
||||||
## Testing Strategy
|
|
||||||
1. Unit Testing:
|
|
||||||
- Core functionality
|
|
||||||
- Business logic
|
|
||||||
- Utility functions
|
|
||||||
|
|
||||||
2. Integration Testing:
|
|
||||||
- Module interactions
|
|
||||||
- Data flow verification
|
|
||||||
- System behavior
|
|
||||||
|
|
||||||
3. Performance Testing:
|
|
||||||
- Latency benchmarks
|
|
||||||
- Resource usage
|
|
||||||
- Stress testing
|
|
||||||
|
|
||||||
4. Security Testing:
|
|
||||||
- Vulnerability scanning
|
|
||||||
- Penetration testing
|
|
||||||
- Compliance verification
|
|
||||||
|
|
||||||
## Development Progress
|
|
||||||
### Environment Setup
|
|
||||||
1. Developer Program enrollment complete
|
|
||||||
2. App ID registered with capabilities
|
|
||||||
3. Development device registered
|
|
||||||
4. Provisioning profile successfully generated and installed
|
|
||||||
|
|
||||||
### Implementation Status
|
|
||||||
1. Development Environment
|
|
||||||
- Certificate management completed
|
|
||||||
- Provisioning profile configured
|
|
||||||
- Build pipeline verified
|
|
||||||
- Basic deployment tested
|
|
||||||
|
|
|
||||||
|
|
@ -1,264 +1,208 @@
|
||||||
# Tech Context
|
# Technical Context
|
||||||
|
|
||||||
## Technology Stack
|
## Development Environment
|
||||||
### Core Technologies
|
- macOS development platform
|
||||||
1. Swift (5.9+)
|
- Xcode IDE
|
||||||
2. SwiftUI (4.0+)
|
- SwiftUI for user interface
|
||||||
3. Metal (3.0+)
|
- Swift 5.x language features
|
||||||
4. Vision (2.0+)
|
|
||||||
5. CoreML (5.0+)
|
|
||||||
6. Create ML (3.0+)
|
|
||||||
7. Stockfish (16+)
|
|
||||||
|
|
||||||
### Development Tools
|
## Core Technologies
|
||||||
1. Xcode (15.0+)
|
|
||||||
2. Swift Package Manager
|
|
||||||
3. Git (2.40+)
|
|
||||||
4. CoreML Tools (5.0+)
|
|
||||||
5. Create ML App (3.0+)
|
|
||||||
|
|
||||||
## Development Environment Status
|
### ScreenCaptureKit
|
||||||
### Apple Developer Configuration
|
- System framework for screen capture
|
||||||
- Program: Enrolled and Verified
|
- Requires user permissions
|
||||||
- App ID: com.chessprism.ChessPrism
|
- Supports window/display filtering
|
||||||
- Device: M3-Haulmark registered and confirmed
|
- Real-time frame capture capabilities
|
||||||
- Capabilities: Configured for macOS
|
|
||||||
- Certificate: Single verified Apple Development certificate
|
|
||||||
- Team ID: RJHWWWSF6Q
|
|
||||||
- Signing Identity: S6HYLY2Y7Y
|
|
||||||
|
|
||||||
### Build Configuration
|
### Vision Framework
|
||||||
- Basic SwiftUI implementation tested
|
- Used for board and coordinate detection
|
||||||
- Hello World deployment successful
|
- Key components:
|
||||||
- Code signing verified
|
* VNRecognizeTextRequest: Chess coordinate detection
|
||||||
- Provisioning profile installed
|
* VNDetectRectanglesRequest: Board boundary detection
|
||||||
- Development certificates consolidated
|
- Configuration:
|
||||||
|
* Text recognition level: accurate
|
||||||
|
* Language correction: disabled
|
||||||
|
* Rectangle aspect ratio: 0.3-0.5 (for taller rectangles)
|
||||||
|
* Minimum size: 0.4
|
||||||
|
* Maximum observations: 1
|
||||||
|
|
||||||
### Testing Frameworks
|
### Coordinate Systems
|
||||||
1. XCTest (5.0+)
|
1. Vision Framework
|
||||||
2. XCUITest (5.0+)
|
- Origin: Bottom-left (0,0)
|
||||||
3. Performance Testing Tools
|
- Y-axis: Upward positive
|
||||||
4. Security Testing Suite
|
- Normalized coordinates (0-1)
|
||||||
|
- Used in: VNRectangleObservation, VNTextObservation
|
||||||
|
|
||||||
## Development System Specifications
|
2. NSImage/CGImage
|
||||||
### Operating System
|
- Origin: Bottom-left (0,0)
|
||||||
- System: Darwin
|
- Y-axis: Upward positive
|
||||||
- Version: 15.2
|
- Pixel coordinates
|
||||||
- Architecture: arm64
|
- Used in: Image cropping, processing
|
||||||
|
|
||||||
### Hardware Specifications
|
3. SwiftUI
|
||||||
- CPU: Apple M3 Max
|
- Origin: Top-left (0,0)
|
||||||
- Memory: 64.00 GB
|
- Y-axis: Downward positive
|
||||||
- GPU: Apple M3 Max (40 cores)
|
- Point coordinates
|
||||||
- Metal Support: Metal 3
|
- Used in: View layout, rendering
|
||||||
|
|
||||||
### Display Information
|
4. Transformations
|
||||||
- Main Display: LG ULTRAGEAR+
|
- Vision → Screen: Flip Y coordinate
|
||||||
- Resolution: 3840 x 1080
|
- Screen → Image: Scale to pixel coordinates
|
||||||
- Refresh Rate: 120Hz
|
- Image → View: SwiftUI handles automatically
|
||||||
- Features: Television support, rotation support
|
|
||||||
|
|
||||||
### Development Tools
|
### SwiftUI
|
||||||
- Xcode: 16.2 (Build version 16C5032a)
|
- Modern declarative UI framework
|
||||||
- Swift: 6.0.3 (swiftlang-6.0.3.1.10 clang-1600.0.30.1)
|
- Handles view lifecycle
|
||||||
- Target: arm64-apple-macosx15.0
|
- State management via @Published properties
|
||||||
|
- Environmental object propagation
|
||||||
## Development Environment Requirements
|
|
||||||
### Minimum Requirements
|
|
||||||
- Apple Silicon (M1)
|
|
||||||
- 16GB RAM
|
|
||||||
- Metal 2 support
|
|
||||||
|
|
||||||
### Recommended Requirements
|
|
||||||
- Apple M2/M3 series
|
|
||||||
- 32GB+ RAM
|
|
||||||
- Metal 3 support
|
|
||||||
- Apple Neural Engine
|
|
||||||
|
|
||||||
### Software Requirements
|
|
||||||
1. MacOS (Ventura 13.0+)
|
|
||||||
2. Xcode (15.0+)
|
|
||||||
3. Swift (5.9+)
|
|
||||||
4. Git (2.40+)
|
|
||||||
5. CoreML Tools (5.0+)
|
|
||||||
|
|
||||||
## Configuration Details
|
|
||||||
### Swift Concurrency
|
|
||||||
1. Async/await pattern implementation
|
|
||||||
2. Task management system
|
|
||||||
3. Structured concurrency
|
|
||||||
4. Actor-based isolation
|
|
||||||
|
|
||||||
### CoreML Integration
|
|
||||||
1. Model versioning system
|
|
||||||
2. Apple Neural Engine optimization
|
|
||||||
3. Model update mechanism
|
|
||||||
4. Performance monitoring
|
|
||||||
|
|
||||||
### Security Implementation
|
|
||||||
1. App Sandbox configuration
|
|
||||||
2. Privacy manifest requirements
|
|
||||||
3. Secure storage implementation
|
|
||||||
4. Data encryption standards
|
|
||||||
|
|
||||||
## Development Workflow
|
|
||||||
1. Version Control:
|
|
||||||
- Git branching strategy
|
|
||||||
- Code review process
|
|
||||||
- Commit message guidelines
|
|
||||||
|
|
||||||
2. CI/CD Pipeline:
|
|
||||||
- Automated testing
|
|
||||||
- Build verification
|
|
||||||
- Deployment automation
|
|
||||||
- Release management
|
|
||||||
|
|
||||||
3. Code Quality:
|
|
||||||
- Linting configuration
|
|
||||||
- Static analysis
|
|
||||||
- Code coverage requirements
|
|
||||||
- Documentation standards
|
|
||||||
|
|
||||||
## Monitoring & Logging
|
|
||||||
1. Performance Monitoring:
|
|
||||||
- Rendering performance
|
|
||||||
- Analysis latency
|
|
||||||
- Resource usage
|
|
||||||
|
|
||||||
2. Error Tracking:
|
|
||||||
- Crash reporting
|
|
||||||
- Error logging
|
|
||||||
- User feedback integration
|
|
||||||
|
|
||||||
3. Analytics:
|
|
||||||
- Usage tracking
|
|
||||||
- Feature adoption
|
|
||||||
- Performance metrics
|
|
||||||
|
|
||||||
## Technical Constraints
|
## Technical Constraints
|
||||||
1. Real-time Requirements:
|
|
||||||
- <100ms analysis latency
|
|
||||||
- 120fps rendering
|
|
||||||
- 99.9% recognition accuracy
|
|
||||||
|
|
||||||
2. Compatibility:
|
### Board Detection
|
||||||
- MacOS Silicon only
|
1. Pattern Recognition
|
||||||
- Firefox browser integration
|
- Detect taller rectangles (0.3-0.5 aspect ratio)
|
||||||
- Chess.com specific optimizations
|
- Extract square board from upper portion
|
||||||
|
- Use width as reference measurement
|
||||||
|
- Handle coordinate system transformations
|
||||||
|
|
||||||
3. Security:
|
2. Coordinate Recognition
|
||||||
- App Sandbox compliance
|
- Must detect a-h and 1-8 coordinates
|
||||||
- Privacy manifest requirements
|
- Handles both light and dark themes
|
||||||
- Secure data handling
|
- Requires clear coordinate visibility
|
||||||
|
- Minimum text size requirements
|
||||||
|
|
||||||
## Documentation Standards
|
3. Board Boundaries
|
||||||
1. Code Documentation:
|
- Square aspect ratio (1:1)
|
||||||
- API documentation
|
- Tolerance: 0.3 for dimensions
|
||||||
- Architecture diagrams
|
- Extract from detected area
|
||||||
- Technical specifications
|
- Proper coordinate transformations
|
||||||
|
|
||||||
2. User Documentation:
|
4. Performance
|
||||||
- Installation guide
|
- Frame processing on dedicated queue
|
||||||
- Usage instructions
|
- Asynchronous Vision requests
|
||||||
- Troubleshooting guide
|
- Memory management for capture session
|
||||||
|
- Resource cleanup requirements
|
||||||
|
|
||||||
3. Developer Documentation:
|
### System Requirements
|
||||||
- Setup instructions
|
- macOS 12.0 or later
|
||||||
- Contribution guidelines
|
- Screen Capture permissions
|
||||||
- Code style guide
|
- Sufficient CPU for real-time processing
|
||||||
|
- Adequate memory for frame buffering
|
||||||
|
|
||||||
## Detailed Technical Specifications
|
## Dependencies
|
||||||
### Metal Rendering Pipeline
|
|
||||||
1. Pipeline Stages:
|
|
||||||
- Vertex processing
|
|
||||||
- Fragment shading
|
|
||||||
- Composition
|
|
||||||
- Post-processing
|
|
||||||
|
|
||||||
2. Performance Optimization:
|
### Internal
|
||||||
- Command buffer optimization
|
- ScreenCapture.swift: Core capture logic
|
||||||
- Texture compression
|
- ScreenCaptureViewModel.swift: State management
|
||||||
- Shader LOD management
|
- BoardDetector.swift: Pattern recognition
|
||||||
- Frame pacing
|
- ContentView.swift: User interface
|
||||||
|
|
||||||
3. Visual Effects:
|
### External
|
||||||
- Anti-aliasing
|
- ScreenCaptureKit.framework
|
||||||
- Bloom effects
|
- Vision.framework
|
||||||
- Motion blur
|
- SwiftUI.framework
|
||||||
- Depth effects
|
- CoreImage.framework
|
||||||
|
|
||||||
### Vision Framework Integration
|
## Development Guidelines
|
||||||
1. Image Analysis Pipeline:
|
|
||||||
- Image preprocessing
|
|
||||||
- Feature detection
|
|
||||||
- Object recognition
|
|
||||||
- Position tracking
|
|
||||||
|
|
||||||
2. Performance Considerations:
|
### Code Organization
|
||||||
- GPU acceleration
|
- MVVM architecture
|
||||||
- Batch processing
|
- Protocol-oriented design
|
||||||
- Memory optimization
|
- Clear separation of concerns
|
||||||
- Error handling
|
- Comprehensive error handling
|
||||||
|
|
||||||
3. Integration Points:
|
### Performance Optimization
|
||||||
- CoreML model integration
|
- Efficient frame processing
|
||||||
- Metal texture sharing
|
- Memory management
|
||||||
- SwiftUI view integration
|
- Resource cleanup
|
||||||
- Async/await pattern
|
- Background queue usage
|
||||||
|
|
||||||
### Async/Await Patterns
|
### Error Handling
|
||||||
1. Concurrency Model:
|
- Custom error types
|
||||||
- Task groups
|
- Comprehensive error cases
|
||||||
- Async sequences
|
- User-friendly error messages
|
||||||
- Actor isolation
|
- Proper error propagation
|
||||||
- Continuations
|
|
||||||
|
|
||||||
2. Error Handling:
|
## Testing Requirements
|
||||||
- Structured concurrency
|
|
||||||
- Task cancellation
|
|
||||||
- Error propagation
|
|
||||||
- Retry mechanisms
|
|
||||||
|
|
||||||
3. Performance Optimization:
|
### Unit Tests
|
||||||
- Task prioritization
|
- Board detection accuracy
|
||||||
- Resource contention management
|
- Coordinate transformations
|
||||||
- Memory safety
|
- Error handling
|
||||||
- Thread management
|
- State management
|
||||||
|
|
||||||
### CoreML Model Architecture
|
### Integration Tests
|
||||||
1. Model Specifications:
|
- End-to-end workflows
|
||||||
- Input/output formats
|
- Component interaction
|
||||||
- Model quantization
|
- Event propagation
|
||||||
- Neural engine optimization
|
|
||||||
- Model versioning
|
|
||||||
|
|
||||||
2. Training Pipeline:
|
### UI Tests
|
||||||
- Data collection
|
- User interaction flows
|
||||||
- Model training
|
- Error state handling
|
||||||
- Validation
|
- Visual feedback
|
||||||
- Deployment
|
|
||||||
|
|
||||||
3. Performance Considerations:
|
## Documentation Requirements
|
||||||
- Batch processing
|
|
||||||
- Memory management
|
|
||||||
- Model compression
|
|
||||||
- Inference optimization
|
|
||||||
|
|
||||||
### Stockfish Integration
|
### Code Documentation
|
||||||
1. Engine Configuration:
|
- Function documentation
|
||||||
- Thread management
|
- Parameter descriptions
|
||||||
- Hash size optimization
|
- Return value documentation
|
||||||
- Analysis depth
|
- Error documentation
|
||||||
- Time controls
|
|
||||||
|
|
||||||
2. Analysis Pipeline:
|
### Architecture Documentation
|
||||||
- Position evaluation
|
- System overview
|
||||||
- Move generation
|
- Component interaction
|
||||||
- Threat detection
|
- Data flow diagrams
|
||||||
- Position caching
|
- State management
|
||||||
|
|
||||||
3. Performance Optimization:
|
## Current Challenges
|
||||||
- Parallel analysis
|
|
||||||
- Cache management
|
### Coordinate Systems
|
||||||
- Engine tuning
|
1. Understanding
|
||||||
- Resource allocation
|
- Different origin points
|
||||||
|
- Axis directions
|
||||||
|
- Coordinate spaces
|
||||||
|
- Transformation requirements
|
||||||
|
|
||||||
|
2. Implementation
|
||||||
|
- Proper transformations
|
||||||
|
- Consistent handling
|
||||||
|
- Validation methods
|
||||||
|
- Error checking
|
||||||
|
|
||||||
|
### Board Detection
|
||||||
|
1. Full Capture
|
||||||
|
- Complete board visibility
|
||||||
|
- Proper positioning
|
||||||
|
- Consistent results
|
||||||
|
- Coordinate accuracy
|
||||||
|
|
||||||
|
2. Performance
|
||||||
|
- Processing efficiency
|
||||||
|
- Memory usage
|
||||||
|
- Resource management
|
||||||
|
- Error recovery
|
||||||
|
|
||||||
|
## Future Considerations
|
||||||
|
|
||||||
|
### Planned Features
|
||||||
|
1. ML Model Integration
|
||||||
|
- Piece detection
|
||||||
|
- Position analysis
|
||||||
|
- Move validation
|
||||||
|
|
||||||
|
2. Engine Integration
|
||||||
|
- Stockfish analysis
|
||||||
|
- Move evaluation
|
||||||
|
- Position scoring
|
||||||
|
|
||||||
|
3. Visual Overlay
|
||||||
|
- Move suggestions
|
||||||
|
- Analysis visualization
|
||||||
|
- Interactive elements
|
||||||
|
|
||||||
|
### Technical Debt
|
||||||
|
- Refactor coordinate handling
|
||||||
|
- Optimize frame processing
|
||||||
|
- Improve error recovery
|
||||||
|
- Enhanced permission handling
|
||||||
|
|
|
||||||
819
sample.py
Normal file
819
sample.py
Normal file
|
|
@ -0,0 +1,819 @@
|
||||||
|
def create_pgn_csv(video_list, pgn_list, output_csv_path):
|
||||||
|
# Prepare the data for the CSV
|
||||||
|
rows = [{"row_id": video, "output": pgn} for video, pgn in zip(video_list, pgn_list)]
|
||||||
|
|
||||||
|
# Write to the CSV
|
||||||
|
with open(output_csv_path, mode="w", newline="", encoding="utf-8") as file:
|
||||||
|
writer = csv.DictWriter(file, fieldnames=["row_id", "output"])
|
||||||
|
writer.writeheader()
|
||||||
|
writer.writerows(rows)
|
||||||
|
|
||||||
|
print(f"CSV file has been created at {output_csv_path}.")
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
import glob
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
# DRAWING
|
||||||
|
|
||||||
|
def show_cv2_image(image, title='image'):
|
||||||
|
# plt.figure()
|
||||||
|
# plt.title(title)
|
||||||
|
# plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
|
||||||
|
cv2.imwrite(f'output/temp/{title}_{datetime.datetime.now().strftime("%Y%m%d%H%M%S")}.png', image)
|
||||||
|
|
||||||
|
# OCR
|
||||||
|
|
||||||
|
def ocr_image(input_image, verbose=False):
|
||||||
|
"""Detects text in the file."""
|
||||||
|
from google.cloud import vision
|
||||||
|
|
||||||
|
client = vision.ImageAnnotatorClient()
|
||||||
|
|
||||||
|
content = cv2.imencode('.jpg', input_image)[1].tobytes()
|
||||||
|
|
||||||
|
image = vision.Image(content=content)
|
||||||
|
|
||||||
|
response = client.text_detection(image=image)
|
||||||
|
texts = response.text_annotations
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
print("Texts:")
|
||||||
|
|
||||||
|
for text in texts:
|
||||||
|
print(f'\n"{text.description}"')
|
||||||
|
|
||||||
|
vertices = [
|
||||||
|
f"({vertex.x},{vertex.y})" for vertex in text.bounding_poly.vertices
|
||||||
|
]
|
||||||
|
|
||||||
|
print("bounds: {}".format(",".join(vertices)))
|
||||||
|
|
||||||
|
if response.error.message:
|
||||||
|
raise Exception(
|
||||||
|
"{}\nFor more info on error messages, check: "
|
||||||
|
"https://cloud.google.com/apis/design/errors".format(response.error.message)
|
||||||
|
)
|
||||||
|
|
||||||
|
return texts
|
||||||
|
|
||||||
|
def detection_to_dict(detection):
|
||||||
|
return {
|
||||||
|
'description': detection.description,
|
||||||
|
'vertices': [
|
||||||
|
(vertex.x, vertex.y) for vertex in detection.bounding_poly.vertices
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
def draw_box(image, a, b, c, d):
|
||||||
|
cv2.polylines(image, [np.array([a, b, c, d], np.int32)], True, (0, 255, 0), 2)
|
||||||
|
|
||||||
|
def show_image_with_ocr(image, title='ocr result'):
|
||||||
|
result = ocr_image(image)
|
||||||
|
|
||||||
|
if len(image.shape) == 2:
|
||||||
|
image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
|
||||||
|
|
||||||
|
for text in result:
|
||||||
|
text_dict = detection_to_dict(text)
|
||||||
|
vertices = text_dict['vertices']
|
||||||
|
draw_box(image, vertices[0], vertices[1], vertices[2], vertices[3])
|
||||||
|
|
||||||
|
show_cv2_image(image, title)
|
||||||
|
|
||||||
|
def show_image_with_ocr_labelled(image, title='ocr result'):
|
||||||
|
result = ocr_image(image)
|
||||||
|
|
||||||
|
if len(image.shape) == 2:
|
||||||
|
image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
|
||||||
|
|
||||||
|
for text in result:
|
||||||
|
text_dict = detection_to_dict(text)
|
||||||
|
vertices = text_dict['vertices']
|
||||||
|
draw_box(image, vertices[0], vertices[1], vertices[2], vertices[3])
|
||||||
|
cv2.putText(image, text_dict['description'], vertices[0], cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
|
||||||
|
|
||||||
|
show_cv2_image(image, title)
|
||||||
|
|
||||||
|
# PROCESS IMAGE
|
||||||
|
|
||||||
|
# Function to compute the intersection of two lines
|
||||||
|
def compute_intersection(line1, line2):
|
||||||
|
rho1, theta1 = line1
|
||||||
|
rho2, theta2 = line2
|
||||||
|
|
||||||
|
# Calculate the intersection of two lines
|
||||||
|
A = np.array([[np.cos(theta1), np.sin(theta1)], [np.cos(theta2), np.sin(theta2)]])
|
||||||
|
b = np.array([rho1, rho2])
|
||||||
|
|
||||||
|
# Solve the linear system to find the intersection point
|
||||||
|
intersection = np.linalg.solve(A, b)
|
||||||
|
return int(intersection[0]), int(intersection[1])
|
||||||
|
|
||||||
|
def draw_white_board_boundaries(image):
|
||||||
|
# Convert the image to HSV color space
|
||||||
|
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
|
||||||
|
|
||||||
|
# Define the range of green color in HSV
|
||||||
|
lower_green = np.array([40, 25, 40]) # Lower bound of green in HSV
|
||||||
|
upper_green = np.array([100, 200, 200]) # Upper bound of green in HSV
|
||||||
|
|
||||||
|
# Threshold the image to get only the green color
|
||||||
|
mask = cv2.inRange(hsv, lower_green, upper_green)
|
||||||
|
|
||||||
|
# Find contours
|
||||||
|
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||||
|
|
||||||
|
# Create a black canvas to draw contours
|
||||||
|
contour_image = np.zeros_like(mask)
|
||||||
|
|
||||||
|
# Draw the contours on the black canvas (255 for white contours)
|
||||||
|
cv2.drawContours(contour_image, contours, -1, (255), 1)
|
||||||
|
|
||||||
|
# Apply the Canny edge detector on the contour image
|
||||||
|
edges = cv2.Canny(contour_image, 50, 150, apertureSize=3)
|
||||||
|
|
||||||
|
# Apply Hough Line Transform to find lines in the edge-detected image
|
||||||
|
lines = cv2.HoughLines(edges, 1, np.pi / 180, threshold=100)
|
||||||
|
|
||||||
|
# Separate the lines into vertical and horizontal based on their angle (theta)
|
||||||
|
vertical_lines = []
|
||||||
|
horizontal_lines = []
|
||||||
|
|
||||||
|
# Find vertical and horizontal lines
|
||||||
|
if lines is not None:
|
||||||
|
for rho, theta in lines[:, 0]:
|
||||||
|
# Identify vertical lines (theta near 0 or 180 degrees)
|
||||||
|
if np.abs(theta) < np.pi / 180 * 10 or np.abs(theta - np.pi) < np.pi / 180 * 10:
|
||||||
|
vertical_lines.append((rho, theta))
|
||||||
|
# Identify horizontal lines (theta near 90 degrees)
|
||||||
|
elif np.abs(theta - np.pi / 2) < np.pi / 180 * 10:
|
||||||
|
horizontal_lines.append((rho, theta))
|
||||||
|
|
||||||
|
# Create an empty list to store intersection points
|
||||||
|
intersection_points = []
|
||||||
|
|
||||||
|
# Find intersection points between vertical and horizontal lines
|
||||||
|
for v_line in vertical_lines:
|
||||||
|
for h_line in horizontal_lines:
|
||||||
|
intersection = compute_intersection(v_line, h_line)
|
||||||
|
intersection_points.append(intersection)
|
||||||
|
|
||||||
|
# Create an empty image to draw the intersection points
|
||||||
|
intersection_image = np.zeros_like(image)
|
||||||
|
|
||||||
|
# Draw the intersection points on the image (red points)
|
||||||
|
for point in intersection_points:
|
||||||
|
cv2.circle(intersection_image, point, 10, (0, 0, 255), -1) # Red circle at intersection points
|
||||||
|
|
||||||
|
# Convert the intersection image to grayscale
|
||||||
|
grayscale_image = cv2.cvtColor(intersection_image, cv2.COLOR_BGR2GRAY)
|
||||||
|
|
||||||
|
# Find contours of the red intersection points (non-zero pixels)
|
||||||
|
contours, _ = cv2.findContours(grayscale_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||||
|
|
||||||
|
# Create an empty black image to draw the center points
|
||||||
|
center_image = np.copy(image)
|
||||||
|
|
||||||
|
# List to store the center points' coordinates
|
||||||
|
center_points = []
|
||||||
|
|
||||||
|
# Iterate over each contour and find the centroid (center point)
|
||||||
|
for contour in contours:
|
||||||
|
# Calculate the moments of the contour
|
||||||
|
moments = cv2.moments(contour)
|
||||||
|
|
||||||
|
# Calculate the centroid (center) of the contour
|
||||||
|
if moments['m00'] != 0:
|
||||||
|
cx = int(moments['m10'] / moments['m00'])
|
||||||
|
cy = int(moments['m01'] / moments['m00'])
|
||||||
|
|
||||||
|
# Draw the center point (blue) on the original image
|
||||||
|
cv2.circle(center_image, (cx, cy), 5, (255, 0, 0), -1) # Blue circle at center
|
||||||
|
|
||||||
|
# Store the center coordinates in the list
|
||||||
|
center_points.append((cx, cy))
|
||||||
|
center_points = sorted(center_points, key=lambda x: sum(x))
|
||||||
|
|
||||||
|
# Step 1: Find the convex hull of the center points
|
||||||
|
center_points_np = np.array(center_points, dtype=np.int32) # Convert to NumPy array
|
||||||
|
hull = cv2.convexHull(center_points_np) # Compute convex hull
|
||||||
|
|
||||||
|
# Step 2: Create a mask for the filled polygon
|
||||||
|
mask = np.ones_like(image, dtype=np.uint8) * 255 # Create a white mask with the same dimensions as the image
|
||||||
|
cv2.fillPoly(mask, [hull], (0, 0, 0)) # Draw the filled polygon in black on the mask
|
||||||
|
|
||||||
|
image[mask == 0] = 255
|
||||||
|
result_image = image
|
||||||
|
|
||||||
|
return result_image
|
||||||
|
|
||||||
|
def process_image(image):
|
||||||
|
processed_image = draw_white_board_boundaries(image)
|
||||||
|
|
||||||
|
result = processed_image
|
||||||
|
return result
|
||||||
|
|
||||||
|
# detect chess board orientation
|
||||||
|
|
||||||
|
def bounding_box(left, top, width, height):
|
||||||
|
return [(left, top), (left + width, top), (left + width, top + height), (left, top + height)]
|
||||||
|
|
||||||
|
def draw_bounding_box(image, box, color=(0, 255, 0)):
|
||||||
|
cv2.polylines(image, [np.array(box)], isClosed=True, color=color, thickness=2)
|
||||||
|
return image
|
||||||
|
|
||||||
|
def get_board_orientation(bound_8, bound_h):
|
||||||
|
# compute the center of the two bounding boxes
|
||||||
|
center_8 = np.mean(np.array(bound_8), axis=0)
|
||||||
|
center_h = np.mean(np.array(bound_h), axis=0)
|
||||||
|
|
||||||
|
# check the relative position of the two centers
|
||||||
|
if center_8[0] < center_h[0] and center_8[1] < center_h[1]:
|
||||||
|
return 'UPRIGHT'
|
||||||
|
elif center_8[0] > center_h[0] and center_8[1] > center_h[1]:
|
||||||
|
return 'UPSIDE_DOWN'
|
||||||
|
elif center_8[0] < center_h[0] and center_8[1] > center_h[1]:
|
||||||
|
return 'ROTATED_RIGHT'
|
||||||
|
elif center_8[0] > center_h[0] and center_8[1] < center_h[1]:
|
||||||
|
return 'ROTATED_LEFT'
|
||||||
|
|
||||||
|
|
||||||
|
def detect_chessboard_orientation(image):
|
||||||
|
data = ocr_image(image)
|
||||||
|
|
||||||
|
bounds_8 = [] # sample: [(x1, y1), (x2, y2), (x3, y3), (x4, y4)]
|
||||||
|
bounds_h = [] # sample: [(x1, y1), (x2, y2), (x3, y3), (x4, y4)]
|
||||||
|
|
||||||
|
for detection in data:
|
||||||
|
data_dict = detection_to_dict(detection)
|
||||||
|
if data_dict['description'] == '8':
|
||||||
|
# store the bounding box of the 8
|
||||||
|
bounds_8.append(data_dict['vertices'])
|
||||||
|
elif data_dict['description'] == 'h':
|
||||||
|
bounds_h.append(data_dict['vertices'])
|
||||||
|
|
||||||
|
# find the closest pair of 8 and h
|
||||||
|
closest = None
|
||||||
|
|
||||||
|
for bound_8 in bounds_8:
|
||||||
|
for bound_h in bounds_h:
|
||||||
|
distance = np.linalg.norm(np.array(bound_8) - np.array(bound_h))
|
||||||
|
|
||||||
|
if closest is None or distance < closest[0]:
|
||||||
|
closest = (distance, bound_8, bound_h)
|
||||||
|
|
||||||
|
print(f'Closest pair: {closest}')
|
||||||
|
|
||||||
|
if closest is None:
|
||||||
|
return False, closest
|
||||||
|
|
||||||
|
# compute the center of the two bounding boxes
|
||||||
|
center_8 = np.mean(np.array(closest[1]), axis=0)
|
||||||
|
center_h = np.mean(np.array(closest[2]), axis=0)
|
||||||
|
|
||||||
|
# draw the line between the two points
|
||||||
|
cv2.line(image, tuple(center_8.astype(int)), tuple(center_h.astype(int)), (255, 0, 0), 2)
|
||||||
|
|
||||||
|
# draw the bounding boxes
|
||||||
|
image = draw_bounding_box(image, bound_8, (0, 255, 0))
|
||||||
|
image = draw_bounding_box(image, bound_h, (0, 0, 255))
|
||||||
|
|
||||||
|
show_cv2_image(image, "detected image")
|
||||||
|
|
||||||
|
return get_board_orientation(closest[1], closest[2]), closest
|
||||||
|
|
||||||
|
def get_k(image):
|
||||||
|
images = [image]
|
||||||
|
|
||||||
|
for i in range(3):
|
||||||
|
images.append(cv2.rotate(images[-1], cv2.ROTATE_90_CLOCKWISE))
|
||||||
|
|
||||||
|
k = 0
|
||||||
|
for i in range(4):
|
||||||
|
result = detect_chessboard_orientation(process_image(images[i]))
|
||||||
|
|
||||||
|
if result[0] and result[1]:
|
||||||
|
bound_8 = result[1][1][0]
|
||||||
|
bound_h = result[1][1][1]
|
||||||
|
|
||||||
|
# bound_8 and bound_h are in the bottom-left window of the image
|
||||||
|
h, w = images[i].shape[:2]
|
||||||
|
|
||||||
|
if bound_8[0] < w / 2 and bound_8[1] > h / 2 and bound_h[0] < w / 2 and bound_h[1] > h / 2:
|
||||||
|
k = i
|
||||||
|
break
|
||||||
|
|
||||||
|
return k
|
||||||
|
|
||||||
|
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
from ultralytics import YOLO
|
||||||
|
from PIL import Image
|
||||||
|
import chess
|
||||||
|
import time
|
||||||
|
import chess.pgn
|
||||||
|
import mediapipe as mp
|
||||||
|
from statistics import mode
|
||||||
|
|
||||||
|
# Define a mapping of YOLO labels to chess piece names
|
||||||
|
def label_to_piece_name(label):
|
||||||
|
piece_map = {
|
||||||
|
1: "bB", # black-bishop
|
||||||
|
2: "bK", # black-king
|
||||||
|
3: "bN", # black-knight
|
||||||
|
4: "bP", # black-pawn
|
||||||
|
5: "bQ", # black-queen
|
||||||
|
6: "bR", # black-rook
|
||||||
|
7: "wB", # white-bishop
|
||||||
|
8: "wK", # white-king
|
||||||
|
9: "wN", # white-knight
|
||||||
|
10: "wP", # white-pawn
|
||||||
|
11: "wQ", # white-queen
|
||||||
|
12: "wR" # white-rook
|
||||||
|
}
|
||||||
|
return piece_map.get(label, "?")
|
||||||
|
|
||||||
|
# Function to find intersection points of the grid lines
|
||||||
|
def find_grid(image, k=0):
|
||||||
|
# image = cv2.imread(image_path)
|
||||||
|
|
||||||
|
# Convert the image to HSV color space
|
||||||
|
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
|
||||||
|
|
||||||
|
for i in range(k):
|
||||||
|
hsv = cv2.rotate(hsv, cv2.ROTATE_90_CLOCKWISE)
|
||||||
|
|
||||||
|
# Define the range of green color in HSV (assuming green grid lines)
|
||||||
|
lower_green = np.array([40, 25, 40]) # Lower bound of green in HSV
|
||||||
|
upper_green = np.array([100, 200, 200]) # Upper bound of green in HSV
|
||||||
|
|
||||||
|
# Threshold the image to get only the green color
|
||||||
|
mask = cv2.inRange(hsv, lower_green, upper_green)
|
||||||
|
|
||||||
|
# Find contours
|
||||||
|
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||||
|
|
||||||
|
# Create a black canvas to draw contours
|
||||||
|
contour_image = np.zeros_like(mask)
|
||||||
|
|
||||||
|
# Draw the contours on the black canvas (255 for white contours)
|
||||||
|
cv2.drawContours(contour_image, contours, -1, (255), 1)
|
||||||
|
|
||||||
|
# Apply Canny edge detector
|
||||||
|
edges = cv2.Canny(contour_image, 50, 150, apertureSize=3)
|
||||||
|
|
||||||
|
# Apply Hough Line Transform to find lines in the edge-detected image
|
||||||
|
lines = cv2.HoughLines(edges, 1, np.pi / 180, threshold=100)
|
||||||
|
|
||||||
|
# Separate the lines into vertical and horizontal based on their angle
|
||||||
|
vertical_lines = []
|
||||||
|
horizontal_lines = []
|
||||||
|
|
||||||
|
# Find vertical and horizontal lines
|
||||||
|
if lines is not None:
|
||||||
|
for rho, theta in lines[:, 0]:
|
||||||
|
# Identify vertical lines (theta near 0 or 180 degrees)
|
||||||
|
if np.abs(theta) < np.pi / 180 * 10 or np.abs(theta - np.pi) < np.pi / 180 * 10:
|
||||||
|
vertical_lines.append((rho, theta))
|
||||||
|
# Identify horizontal lines (theta near 90 degrees)
|
||||||
|
elif np.abs(theta - np.pi / 2) < np.pi / 180 * 10:
|
||||||
|
horizontal_lines.append((rho, theta))
|
||||||
|
|
||||||
|
# Create an empty list to store intersection points
|
||||||
|
intersection_points = []
|
||||||
|
|
||||||
|
# Function to compute the intersection of two lines
|
||||||
|
def compute_intersection(line1, line2):
|
||||||
|
rho1, theta1 = line1
|
||||||
|
rho2, theta2 = line2
|
||||||
|
|
||||||
|
A = np.array([[np.cos(theta1), np.sin(theta1)], [np.cos(theta2), np.sin(theta2)]])
|
||||||
|
b = np.array([rho1, rho2])
|
||||||
|
|
||||||
|
# Solve the linear system to find the intersection point
|
||||||
|
intersection = np.linalg.solve(A, b)
|
||||||
|
return int(intersection[0]), int(intersection[1])
|
||||||
|
|
||||||
|
# Find intersection points between vertical and horizontal lines
|
||||||
|
for v_line in vertical_lines:
|
||||||
|
for h_line in horizontal_lines:
|
||||||
|
intersection = compute_intersection(v_line, h_line)
|
||||||
|
intersection_points.append(intersection)
|
||||||
|
|
||||||
|
return intersection_points
|
||||||
|
|
||||||
|
# Function to map detected chess pieces to the board using intersection points
|
||||||
|
|
||||||
|
def map_yolo_results_to_chessboard(results, chessboard_corners):
|
||||||
|
"""
|
||||||
|
Maps YOLO detection results to a chessboard grid.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- results: YOLO detection results containing labels and bounding boxes.
|
||||||
|
- chessboard_corners: List of tuples [(x1, y1), (x2, y2), ..., (x4, y4)]
|
||||||
|
representing the corners of the chessboard
|
||||||
|
(top-left, top-right, bottom-left, bottom-right).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- A formatted string representation of the chessboard with mapped pieces.
|
||||||
|
"""
|
||||||
|
# Extract the chessboard corners
|
||||||
|
top_left, top_right, bottom_left, bottom_right = chessboard_corners
|
||||||
|
|
||||||
|
# Calculate the width and height of each cell
|
||||||
|
cell_width = (top_right[0] - top_left[0]) / 8
|
||||||
|
cell_height = (bottom_left[1] - top_left[1]) / 8
|
||||||
|
|
||||||
|
# Initialize an empty 8x8 chessboard
|
||||||
|
board = [['' for _ in range(8)] for _ in range(8)]
|
||||||
|
|
||||||
|
# Process YOLO results
|
||||||
|
for r in results:
|
||||||
|
boxes = r.boxes.xywh.numpy() # Bounding boxes in (x_center, y_center, width, height)
|
||||||
|
labels = r.boxes.cls.numpy() # Class indices
|
||||||
|
|
||||||
|
for box, label in zip(boxes, labels):
|
||||||
|
x_center, y_center, _, _ = box
|
||||||
|
piece_name = label_to_piece_name(int(label))
|
||||||
|
|
||||||
|
# Determine the row and column based on the center point
|
||||||
|
col = int((x_center - top_left[0]) / cell_width)
|
||||||
|
row = int((y_center - top_left[1]) / cell_height)
|
||||||
|
|
||||||
|
# Ensure row and col are within bounds
|
||||||
|
if 0 <= row < 8 and 0 <= col < 8:
|
||||||
|
board[row][col] = piece_name
|
||||||
|
|
||||||
|
# Format the board for display
|
||||||
|
formatted_board = '\n'.join([' '.join([cell if cell else '--' for cell in row]) for row in board])
|
||||||
|
return formatted_board
|
||||||
|
|
||||||
|
def convert_to_valid_fen(board_string):
|
||||||
|
# Mapping of custom pieces to FEN standard pieces
|
||||||
|
piece_mapping = {
|
||||||
|
"wP": "P", "wR": "R", "wN": "N", "wB": "B", "wQ": "Q", "wK": "K",
|
||||||
|
"bP": "p", "bR": "r", "bN": "n", "bB": "b", "bQ": "q", "bK": "k",
|
||||||
|
"--": "1" # Empty squares
|
||||||
|
}
|
||||||
|
|
||||||
|
# Split the input string into rows
|
||||||
|
rows = board_string.strip().split("\n")
|
||||||
|
|
||||||
|
fen_rows = []
|
||||||
|
for row in rows:
|
||||||
|
squares = row.split() # Split the row into individual squares
|
||||||
|
fen_row = ""
|
||||||
|
for square in squares:
|
||||||
|
fen_row += piece_mapping.get(square, square) # Replace with mapped value
|
||||||
|
|
||||||
|
# Compress consecutive digits (empty spaces) into single numbers
|
||||||
|
compressed_row = ""
|
||||||
|
empty_count = 0
|
||||||
|
for char in fen_row:
|
||||||
|
if char.isdigit(): # Count empty squares
|
||||||
|
empty_count += int(char)
|
||||||
|
else:
|
||||||
|
if empty_count > 0:
|
||||||
|
compressed_row += str(empty_count)
|
||||||
|
empty_count = 0
|
||||||
|
compressed_row += char
|
||||||
|
if empty_count > 0:
|
||||||
|
compressed_row += str(empty_count) # Add remaining empty squares
|
||||||
|
fen_rows.append(compressed_row)
|
||||||
|
|
||||||
|
# Combine rows with "/" and add default metadata
|
||||||
|
fen_board = "/".join(fen_rows)
|
||||||
|
fen_metadata = " w - - 0 1" # White to move, no castling, no en passant
|
||||||
|
return fen_board + fen_metadata
|
||||||
|
|
||||||
|
def rotate_fen(fen):
|
||||||
|
# Split the FEN into board state and other details
|
||||||
|
board, *rest = fen.split(' ')
|
||||||
|
|
||||||
|
# Split the board into rows
|
||||||
|
rows = board.split('/')
|
||||||
|
|
||||||
|
# Rotate each row (reverse the pieces) and then reverse the row order
|
||||||
|
rotated_rows = [''.join(reversed(row)) for row in reversed(rows)]
|
||||||
|
|
||||||
|
# Recombine the rows into the rotated FEN
|
||||||
|
rotated_board = '/'.join(rotated_rows)
|
||||||
|
|
||||||
|
# Combine the rotated board with the rest of the FEN details
|
||||||
|
return ' '.join([rotated_board] + rest)
|
||||||
|
|
||||||
|
def convert_to_san(moves):
|
||||||
|
san_moves = []
|
||||||
|
move_number = 1
|
||||||
|
|
||||||
|
for i in range(0, len(moves), 2):
|
||||||
|
if i + 1 < len(moves):
|
||||||
|
# Pair moves for each turn
|
||||||
|
san_moves.append(f"{move_number}. {moves[i]} {moves[i+1]}")
|
||||||
|
else:
|
||||||
|
# If there's an odd move at the end, only record that
|
||||||
|
san_moves.append(f"{move_number}. {moves[i]}")
|
||||||
|
move_number += 1
|
||||||
|
|
||||||
|
return " ".join(san_moves)
|
||||||
|
|
||||||
|
|
||||||
|
def get_chessboard_corners(image, k=0):
|
||||||
|
coor = find_grid(image, k)
|
||||||
|
coor = sorted(coor, key=lambda x: sum(x))
|
||||||
|
min = coor[0]
|
||||||
|
max = coor[-1]
|
||||||
|
|
||||||
|
chessboard_corners = [(min[0], min[1]), (max[0], min[1]), (min[0], max[1]), (max[0], max[1])]
|
||||||
|
|
||||||
|
return chessboard_corners
|
||||||
|
|
||||||
|
def board_list_to_list(board_list):
|
||||||
|
output = [[[],[],[],[],[],[],[],[]],
|
||||||
|
[[],[],[],[],[],[],[],[]],
|
||||||
|
[[],[],[],[],[],[],[],[]],
|
||||||
|
[[],[],[],[],[],[],[],[]],
|
||||||
|
[[],[],[],[],[],[],[],[]],
|
||||||
|
[[],[],[],[],[],[],[],[]],
|
||||||
|
[[],[],[],[],[],[],[],[]],
|
||||||
|
[[],[],[],[],[],[],[],[]]]
|
||||||
|
|
||||||
|
|
||||||
|
for board in board_list:
|
||||||
|
board_splitR = board.split("\n")
|
||||||
|
for r,board_row in enumerate(board_splitR):
|
||||||
|
board_pos = board_row.split(" ")
|
||||||
|
for c,piece in enumerate(board_pos):
|
||||||
|
output[r][c].append(piece)
|
||||||
|
|
||||||
|
for r,row in enumerate(output):
|
||||||
|
for c,col in enumerate(row):
|
||||||
|
output[r][c] = mode(col)
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
def board_to_pgn(prev_b, curr_b):
|
||||||
|
board_pos_coor = [['a8', 'b8', 'c8', 'd8', 'e8', 'f8','g8','h8'],
|
||||||
|
['a7', 'b7', 'c7', 'd7', 'e7', 'f7','g7','h7'],
|
||||||
|
['a6', 'b6', 'c6', 'd6', 'e6', 'f6','g6','h6'],
|
||||||
|
['a5', 'b5', 'c5', 'd5', 'e5', 'f5','g5','h5'],
|
||||||
|
['a4', 'b4', 'c4', 'd4', 'e4', 'f4','g4','h4'],
|
||||||
|
['a3', 'b3', 'c3', 'd3', 'e3', 'f3','g3','h3'],
|
||||||
|
['a2', 'b2', 'c2', 'd2', 'e2', 'f2','g2','h2'],
|
||||||
|
['a1', 'b1', 'c1', 'd1', 'e1', 'f1','g1','h1']]
|
||||||
|
|
||||||
|
# Find the coordinates of the changed piece
|
||||||
|
moved_from = None
|
||||||
|
moved_to = None
|
||||||
|
|
||||||
|
for row in range(8):
|
||||||
|
for col in range(8):
|
||||||
|
if prev_b[row][col] != curr_b[row][col]:
|
||||||
|
if curr_b[row][col] == '.':
|
||||||
|
# The piece moved from this square
|
||||||
|
moved_from = (row, col)
|
||||||
|
else:
|
||||||
|
# The piece moved to this square
|
||||||
|
moved_to = (row, col)
|
||||||
|
|
||||||
|
if moved_from is None or moved_to is None:
|
||||||
|
return "No valid move found", False # In case of invalid input
|
||||||
|
|
||||||
|
# Convert coordinates to chess notation
|
||||||
|
from_square = board_pos_coor[moved_from[0]][moved_from[1]]
|
||||||
|
to_square = board_pos_coor[moved_to[0]][moved_to[1]]
|
||||||
|
|
||||||
|
moved_piece = prev_b[moved_from[0]][moved_from[1]]
|
||||||
|
if moved_piece.islower(): # black move
|
||||||
|
if prev_b[moved_to[0]][moved_to[1]] != '.': # black capture
|
||||||
|
if moved_piece == 'p':
|
||||||
|
pgn = f"{board_pos_coor[moved_from[0]][moved_from[1]][0]}x{to_square}" # black pawn capture
|
||||||
|
else:
|
||||||
|
pgn = f"{moved_piece.upper()}x{to_square}" #black non pawn capture
|
||||||
|
else: # not capture
|
||||||
|
if moved_piece == 'p':
|
||||||
|
moved_piece = ""
|
||||||
|
else:
|
||||||
|
moved_piece = moved_piece.upper()
|
||||||
|
pgn = f"{moved_piece}{to_square}" # Regular pawn move
|
||||||
|
else: # white
|
||||||
|
if prev_b[moved_to[0]][moved_to[1]] != '.': # capture
|
||||||
|
if moved_piece == 'P':
|
||||||
|
pgn = f"{board_pos_coor[moved_from[0]][moved_from[1]][0]}x{to_square}" # capture
|
||||||
|
else:
|
||||||
|
pgn = f"{moved_piece}x{to_square}"
|
||||||
|
else: # not capture
|
||||||
|
if moved_piece == 'P':
|
||||||
|
pgn = f"{to_square}" # Regular piece move
|
||||||
|
else:
|
||||||
|
pgn = f"{moved_piece}{to_square}" # Regular pawn move
|
||||||
|
|
||||||
|
is_white = prev_b[moved_from[0]][moved_from[1]].isupper()
|
||||||
|
|
||||||
|
return pgn, is_white
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def gen_pgn(vid_path, model):
|
||||||
|
mp_hands = mp.solutions.hands
|
||||||
|
hands = mp_hands.Hands(static_image_mode=False, max_num_hands=2, min_detection_confidence=0.5)
|
||||||
|
# mp_drawing = mp.solutions.drawing_utils
|
||||||
|
|
||||||
|
cap = cv2.VideoCapture(vid_path)
|
||||||
|
|
||||||
|
# Parameters for frame processing
|
||||||
|
frame_count = 0
|
||||||
|
frame_interval = int(float(cap.get(cv2.CAP_PROP_FPS)) * 0.5) # Process frame every 1 second
|
||||||
|
# previous_board = None # Track the previous board state
|
||||||
|
# previous_hand_present = False # Track if a hand was detected in the previous frame
|
||||||
|
|
||||||
|
# list of board list; appends the board_lists
|
||||||
|
board_list_list = []
|
||||||
|
|
||||||
|
# board list iterate every time hand is present
|
||||||
|
board_list = []
|
||||||
|
|
||||||
|
# get one frame
|
||||||
|
print('getting k from gen_pgn')
|
||||||
|
k = get_k(cap.read()[1])
|
||||||
|
|
||||||
|
while cap.isOpened():
|
||||||
|
ret, frame = cap.read()
|
||||||
|
if not ret:
|
||||||
|
if len(board_list) != 0:
|
||||||
|
board_list_list.append(board_list)
|
||||||
|
board_list = []
|
||||||
|
break # End of video
|
||||||
|
|
||||||
|
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||||
|
|
||||||
|
# Perform hand detection
|
||||||
|
hand_results = hands.process(rgb_frame)
|
||||||
|
not_hand_present = hand_results.multi_hand_landmarks == None
|
||||||
|
|
||||||
|
# Crop the frame
|
||||||
|
frame = frame[425:1495, :]
|
||||||
|
|
||||||
|
for i in range(k):
|
||||||
|
frame = cv2.rotate(frame, cv2.ROTATE_90_CLOCKWISE)
|
||||||
|
|
||||||
|
# Process frame every frame_interval
|
||||||
|
if frame_count % frame_interval == 0:
|
||||||
|
if not_hand_present:
|
||||||
|
results = model(frame, conf=0.3)
|
||||||
|
chessboard_corners = get_chessboard_corners(frame, k)
|
||||||
|
|
||||||
|
formatted_board = map_yolo_results_to_chessboard(results, chessboard_corners)
|
||||||
|
|
||||||
|
fen = convert_to_valid_fen(formatted_board)
|
||||||
|
fen = rotate_fen(fen)
|
||||||
|
|
||||||
|
current_board = chess.Board(fen)
|
||||||
|
#print('board')
|
||||||
|
board_list.append(str(current_board)[1:-1])
|
||||||
|
elif not not_hand_present or not ret:
|
||||||
|
#print('hand')
|
||||||
|
if len(board_list) != 0:
|
||||||
|
board_list_list.append(board_list)
|
||||||
|
board_list = []
|
||||||
|
|
||||||
|
# Increment frame counter
|
||||||
|
frame_count += 1
|
||||||
|
|
||||||
|
#san_notation = convert_to_san(['..'] + san_move)
|
||||||
|
# print(san_notation)
|
||||||
|
|
||||||
|
# cap.release()
|
||||||
|
# cv2.destroyAllWindows()
|
||||||
|
# print(board_list_list)
|
||||||
|
# print(len(board_list_list))
|
||||||
|
# print(board_list_list[0])
|
||||||
|
# print(board_list_list[1])
|
||||||
|
# print(board_list_list[2])
|
||||||
|
|
||||||
|
for i,b_list in enumerate(board_list_list):
|
||||||
|
print(b_list)
|
||||||
|
new_board_list = board_list_to_list(b_list)
|
||||||
|
board_list_list[i] = new_board_list
|
||||||
|
print(str(new_board_list)+"\n\n")
|
||||||
|
|
||||||
|
pgn_index = 1
|
||||||
|
white_move = ".."
|
||||||
|
black_move = ".."
|
||||||
|
|
||||||
|
pgn_all = [""]
|
||||||
|
|
||||||
|
for i in range(len(board_list_list) - 1):
|
||||||
|
|
||||||
|
p_board = board_list_list[i]
|
||||||
|
c_board = board_list_list[i + 1]
|
||||||
|
pgn, is_white = board_to_pgn(prev_b=p_board, curr_b=c_board)
|
||||||
|
|
||||||
|
if is_white:
|
||||||
|
white_move = pgn
|
||||||
|
else:
|
||||||
|
black_move = pgn
|
||||||
|
|
||||||
|
pgn_row = f"{pgn_index}. {white_move} {black_move} "
|
||||||
|
|
||||||
|
if is_white or (not is_white and black_move != ".."):
|
||||||
|
if len(pgn_all) < pgn_index:
|
||||||
|
pgn_all.append(pgn_row)
|
||||||
|
else:
|
||||||
|
pgn_all[pgn_index - 1] = pgn_row
|
||||||
|
|
||||||
|
if black_move != "..":
|
||||||
|
white_move = ".."
|
||||||
|
black_move = ".."
|
||||||
|
pgn_index += 1
|
||||||
|
|
||||||
|
|
||||||
|
out_str = ""
|
||||||
|
|
||||||
|
for r in pgn_all:
|
||||||
|
out_str += r
|
||||||
|
|
||||||
|
if out_str == "":
|
||||||
|
return '1. '
|
||||||
|
|
||||||
|
return out_str
|
||||||
|
|
||||||
|
# # Initialize Mediapipe Hands
|
||||||
|
# mp_hands = mp.solutions.hands
|
||||||
|
# hands = mp_hands.Hands(static_image_mode=False, max_num_hands=2, min_detection_confidence=0.5)
|
||||||
|
# mp_drawing = mp.solutions.drawing_utils
|
||||||
|
|
||||||
|
# 1. .. move 2. move ..
|
||||||
|
|
||||||
|
# Load the YOLO model
|
||||||
|
model = YOLO("best2.pt") # Replace with the path to your trained YOLO model
|
||||||
|
# image_path = "detection/test2.jpg"
|
||||||
|
|
||||||
|
# image = cv2.imread(image_path)
|
||||||
|
video_path_2m = "kaggle/input/2_move_student.mp4" # Replace with the path to your video
|
||||||
|
video_path_4m = "kaggle/input/4_Move_studet.mp4" # Replace with the path to your video
|
||||||
|
video_path_6m = "kaggle/input/6_Move_student.mp4" # Replace with the path to your video
|
||||||
|
video_path_8m = "kaggle/input/8_Move_student.mp4" # Replace with the path to your video
|
||||||
|
video_path_2mr = "kaggle/input/2_Move_rotate_student.mp4"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
video_path_list = [video_path_2mr, video_path_2m, video_path_4m, video_path_6m, video_path_8m]
|
||||||
|
|
||||||
|
output_path = "output/output_video.avi" # Optional: Specify a path to save the output video
|
||||||
|
|
||||||
|
# for vidp in vidp_list:
|
||||||
|
# for vidp in video_path_list:
|
||||||
|
# gen_pgn(vidp, model)
|
||||||
|
# print(gen_pgn(video_path_4m, model))
|
||||||
|
|
||||||
|
# Release resources
|
||||||
|
|
||||||
|
#R . B . Q . . R
|
||||||
|
#P P K . . P . P
|
||||||
|
#. . . . . N . .
|
||||||
|
#q . . P . . P .
|
||||||
|
#. . . p P . . n
|
||||||
|
#. . . . p . . .
|
||||||
|
#. p . . b p p p
|
||||||
|
#R n b k . . . .
|
||||||
|
|
||||||
|
#R . B . Q . . R
|
||||||
|
#P . K . . P . P
|
||||||
|
#. P . . . N . .
|
||||||
|
#q . . P . . P .
|
||||||
|
#. . . p P . . n
|
||||||
|
#. . . . p . . .
|
||||||
|
#. p . . b p p p
|
||||||
|
#R n b k . . . .
|
||||||
|
|
||||||
|
pgn = []
|
||||||
|
for path in video_path_list:
|
||||||
|
pgn.append(gen_pgn(path, model))
|
||||||
|
|
||||||
|
print(pgn)
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
vids = ['2_Move_rotate_student.mp4','2_move_student.mp4','4_Move_studet.mp4','6_Move_student.mp4','8_Move_student.mp4', '(Bonus)Long_video_student.mp4']
|
||||||
|
|
||||||
|
print(len(pgn), len(vids))
|
||||||
|
|
||||||
|
if len(pgn) < len(vids):
|
||||||
|
pgn.append('1.')
|
||||||
|
|
||||||
|
|
||||||
|
# pgn.append('1.')
|
||||||
|
df = pd.DataFrame({
|
||||||
|
"row_id": vids,
|
||||||
|
"output": pgn
|
||||||
|
})
|
||||||
|
|
||||||
|
# Save to CSV
|
||||||
|
df.to_csv("kaggle/working/submission.csv", index=False, encoding="utf-8")
|
||||||
Loading…
Add table
Reference in a new issue