Spaces:
Sleeping
Sleeping
Commit
·
97c3bf5
1
Parent(s):
2968926
added new api i.e getFen
Browse files- __pycache__/main.cpython-311.pyc +0 -0
- main.py +38 -0
- routes/__pycache__/chess_review.cpython-311.pyc +0 -0
- routes/chess_review.py +156 -0
__pycache__/main.cpython-311.pyc
CHANGED
Binary files a/__pycache__/main.cpython-311.pyc and b/__pycache__/main.cpython-311.pyc differ
|
|
main.py
CHANGED
@@ -1,12 +1,24 @@
|
|
1 |
import io
|
|
|
|
|
2 |
from fastapi import FastAPI, File, UploadFile, Form
|
3 |
from fastapi.responses import JSONResponse, StreamingResponse
|
4 |
from PIL import Image, UnidentifiedImageError
|
5 |
from routes.segmentation import segment_chess_board
|
6 |
from routes.detection import detect_pieces
|
7 |
from routes.fen_generator import gen_fen
|
|
|
8 |
from typing import List, Dict, Any, Union
|
9 |
from pydantic import BaseModel
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
|
11 |
app = FastAPI()
|
12 |
|
@@ -125,3 +137,29 @@ async def get_fen(file : UploadFile = File(), perspective : str = Form("w"), nex
|
|
125 |
|
126 |
except Exception as e:
|
127 |
return JSONResponse(content={"error": "Unexpected error occurred", "details": str(e)}, status_code=500)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import io
|
2 |
+
import os
|
3 |
+
import tempfile
|
4 |
from fastapi import FastAPI, File, UploadFile, Form
|
5 |
from fastapi.responses import JSONResponse, StreamingResponse
|
6 |
from PIL import Image, UnidentifiedImageError
|
7 |
from routes.segmentation import segment_chess_board
|
8 |
from routes.detection import detect_pieces
|
9 |
from routes.fen_generator import gen_fen
|
10 |
+
from routes.chess_review import analyze_pgn
|
11 |
from typing import List, Dict, Any, Union
|
12 |
from pydantic import BaseModel
|
13 |
+
import asyncio
|
14 |
+
import sys
|
15 |
+
import tracemalloc
|
16 |
+
tracemalloc.start()
|
17 |
+
|
18 |
+
|
19 |
+
if sys.platform == "win32":
|
20 |
+
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
|
21 |
+
|
22 |
|
23 |
app = FastAPI()
|
24 |
|
|
|
137 |
|
138 |
except Exception as e:
|
139 |
return JSONResponse(content={"error": "Unexpected error occurred", "details": str(e)}, status_code=500)
|
140 |
+
|
141 |
+
@app.post('/getReview')
|
142 |
+
async def getReview(file: UploadFile = File(...)):
|
143 |
+
print("call recieved")
|
144 |
+
|
145 |
+
if not file.filename.endswith(".pgn"):
|
146 |
+
return JSONResponse(content={"error": "Invalid file format. Please upload a PGN file"}, status_code=400)
|
147 |
+
|
148 |
+
try:
|
149 |
+
# Save the uploaded file to a temporary file
|
150 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".pgn") as tmp_file:
|
151 |
+
tmp_file.write(await file.read())
|
152 |
+
tmp_file_path = tmp_file.name
|
153 |
+
|
154 |
+
# Analyze the PGN file
|
155 |
+
analysis_result = await analyze_pgn(tmp_file_path)
|
156 |
+
|
157 |
+
# Clean up the temporary file
|
158 |
+
os.remove(tmp_file_path)
|
159 |
+
|
160 |
+
if not analysis_result:
|
161 |
+
return JSONResponse(content={"error": "No game found in the PGN file"}, status_code=400)
|
162 |
+
|
163 |
+
return JSONResponse(content=analysis_result, status_code=200)
|
164 |
+
except Exception as e:
|
165 |
+
return JSONResponse(content={"error": "Unexpected error occurred", "details": str(e)}, status_code=500)
|
routes/__pycache__/chess_review.cpython-311.pyc
ADDED
Binary file (10.2 kB). View file
|
|
routes/chess_review.py
ADDED
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import csv
|
2 |
+
import chess.pgn
|
3 |
+
import chess.engine
|
4 |
+
from enum import Enum
|
5 |
+
from typing import List, Dict
|
6 |
+
import asyncio
|
7 |
+
import json
|
8 |
+
import sys
|
9 |
+
|
10 |
+
if sys.platform == "win32":
|
11 |
+
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
|
12 |
+
|
13 |
+
|
14 |
+
def load_opening_book(csv_path):
|
15 |
+
opening_book = {}
|
16 |
+
try:
|
17 |
+
with open(csv_path, newline='', encoding='utf-8') as csvfile:
|
18 |
+
reader = csv.reader(csvfile)
|
19 |
+
next(reader)
|
20 |
+
for row in reader:
|
21 |
+
if len(row) < 3:
|
22 |
+
continue
|
23 |
+
pgn_moves = row[2]
|
24 |
+
game = chess.pgn.Game()
|
25 |
+
board = game.board()
|
26 |
+
for move in pgn_moves.split():
|
27 |
+
if "." in move:
|
28 |
+
continue
|
29 |
+
try:
|
30 |
+
chess_move = board.push_san(move)
|
31 |
+
fen = " ".join(board.fen().split()[:4])
|
32 |
+
opening_book[fen] = chess_move.uci()
|
33 |
+
except ValueError:
|
34 |
+
break
|
35 |
+
except Exception as e:
|
36 |
+
print(f"Error loading opening book: {e}")
|
37 |
+
return opening_book
|
38 |
+
|
39 |
+
|
40 |
+
engine_path = r"D:\venv\chess-vision\code\stockfish-windows-x86-64-avx2\stockfish\stockfish-windows-x86-64-avx2.exe"
|
41 |
+
csv_path = r"D:\venv\chess-vision\code\openings_master.csv"
|
42 |
+
opening_book = load_opening_book(csv_path)
|
43 |
+
|
44 |
+
|
45 |
+
class GamePhase(Enum):
|
46 |
+
OPENING = "opening"
|
47 |
+
MIDDLEGAME = "middlegame"
|
48 |
+
ENDGAME = "endgame"
|
49 |
+
|
50 |
+
class Classification(Enum):
|
51 |
+
BRILLIANT = "brilliant"
|
52 |
+
GREAT = "great"
|
53 |
+
BEST = "best"
|
54 |
+
EXCELLENT = "excellent"
|
55 |
+
GOOD = "good"
|
56 |
+
INACCURACY = "inaccuracy"
|
57 |
+
MISTAKE = "mistake"
|
58 |
+
MISS = "miss"
|
59 |
+
BLUNDER = "blunder"
|
60 |
+
BOOK = "book"
|
61 |
+
FORCED = "forced"
|
62 |
+
|
63 |
+
classification_values = {
|
64 |
+
Classification.BLUNDER: 0,
|
65 |
+
Classification.MISTAKE: 0.2,
|
66 |
+
Classification.MISS: 0.3,
|
67 |
+
Classification.INACCURACY: 0.4,
|
68 |
+
Classification.GOOD: 0.65,
|
69 |
+
Classification.EXCELLENT: 0.9,
|
70 |
+
Classification.BEST: 1,
|
71 |
+
Classification.GREAT: 1,
|
72 |
+
Classification.BRILLIANT: 1,
|
73 |
+
Classification.BOOK: 1,
|
74 |
+
Classification.FORCED: 1,
|
75 |
+
}
|
76 |
+
|
77 |
+
centipawn_classifications = [
|
78 |
+
Classification.BEST,
|
79 |
+
Classification.EXCELLENT,
|
80 |
+
Classification.GOOD,
|
81 |
+
Classification.INACCURACY,
|
82 |
+
Classification.MISS,
|
83 |
+
Classification.MISTAKE,
|
84 |
+
Classification.BLUNDER,
|
85 |
+
]
|
86 |
+
|
87 |
+
FORCED_WIN_THRESHOLD = 500
|
88 |
+
MISS_CENTIPAWN_LOSS = 300
|
89 |
+
MISS_MATE_THRESHOLD = 3
|
90 |
+
ENDGAME_MATERIAL_THRESHOLD = 24
|
91 |
+
QUEEN_VALUE = 9
|
92 |
+
|
93 |
+
def detect_game_phase(board: chess.Board, in_opening: bool) -> GamePhase:
|
94 |
+
if in_opening:
|
95 |
+
return GamePhase.OPENING
|
96 |
+
total_material = sum(
|
97 |
+
len(board.pieces(p, color)) * {1: 1, 2: 3, 3: 3, 4: 5, 5: QUEEN_VALUE}[p]
|
98 |
+
for color in [chess.WHITE, chess.BLACK] for p in chess.PIECE_TYPES if p != chess.KING
|
99 |
+
)
|
100 |
+
queens = sum(len(board.pieces(chess.QUEEN, color)) for color in [chess.WHITE, chess.BLACK])
|
101 |
+
return GamePhase.ENDGAME if total_material <= ENDGAME_MATERIAL_THRESHOLD or (queens == 0 and total_material <= ENDGAME_MATERIAL_THRESHOLD * 2) else GamePhase.MIDDLEGAME
|
102 |
+
|
103 |
+
def is_book_move(board, opening_book, max_depth=8):
|
104 |
+
return None if board.fullmove_number > max_depth else opening_book.get(" ".join(board.fen().split()[:4]))
|
105 |
+
|
106 |
+
async def analyze_pgn(pgn_file: str):
|
107 |
+
with open(pgn_file) as pgn:
|
108 |
+
game = chess.pgn.read_game(pgn)
|
109 |
+
if not game:
|
110 |
+
return json.dumps({"error": "No game found in PGN file."})
|
111 |
+
|
112 |
+
results = {
|
113 |
+
"moves": [],
|
114 |
+
"phases": {},
|
115 |
+
"players": {"white": {}, "black": {}}
|
116 |
+
}
|
117 |
+
|
118 |
+
with chess.engine.SimpleEngine.popen_uci(engine_path) as engine:
|
119 |
+
board = game.board()
|
120 |
+
classifications = {"white": {p.value: [] for p in GamePhase}, "black": {p.value: [] for p in GamePhase}}
|
121 |
+
in_opening = True
|
122 |
+
|
123 |
+
for move_number, node in enumerate(game.mainline(), start=1):
|
124 |
+
pre_info = engine.analyse(board, chess.engine.Limit(depth=20))
|
125 |
+
pre_eval = pre_info["score"].white().score(mate_score=10000) or 0
|
126 |
+
best_move = pre_info.get("pv", [None])[0]
|
127 |
+
move = node.move
|
128 |
+
board.push(move)
|
129 |
+
post_info = engine.analyse(board, chess.engine.Limit(depth=20))
|
130 |
+
post_eval = post_info["score"].white().score(mate_score=10000) or 0
|
131 |
+
book_move = is_book_move(board, opening_book)
|
132 |
+
current_phase = detect_game_phase(board, in_opening)
|
133 |
+
if not book_move and in_opening:
|
134 |
+
in_opening = False
|
135 |
+
eval_loss = abs(pre_eval - post_eval)
|
136 |
+
classification = Classification.BOOK if book_move else Classification.BLUNDER
|
137 |
+
player = "white" if board.turn == chess.BLACK else "black"
|
138 |
+
classifications[player][current_phase.value].append(classification.value)
|
139 |
+
results["moves"].append({
|
140 |
+
"move_number": move_number,
|
141 |
+
"player": player,
|
142 |
+
"move": move.uci(),
|
143 |
+
"evaluation": post_eval / 100,
|
144 |
+
"evaluation_loss": eval_loss / 100,
|
145 |
+
"classification": classification.value
|
146 |
+
})
|
147 |
+
|
148 |
+
for phase in GamePhase:
|
149 |
+
results["phases"][phase.value] = {
|
150 |
+
"white": classifications["white"][phase.value],
|
151 |
+
"black": classifications["black"][phase.value]
|
152 |
+
}
|
153 |
+
results["players"]["white"] = classifications["white"]
|
154 |
+
results["players"]["black"] = classifications["black"]
|
155 |
+
|
156 |
+
return json.dumps(results, indent=4)
|