Steph254 commited on
Commit
b9e0048
·
verified ·
1 Parent(s): 0056da1

Upload 26 files

Browse files
.env ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ SECRET_KEY=your-secret-key
2
+ DATABASE_URL=postgresql://postgres:mysecretpassword@localhost:5432/piecefinder
3
+ REDIS_URL=redis://localhost:6379/0
4
+ YOLO_MODEL_PATH=/app/models/yolov8n.pt
5
+ SAM_MODEL_PATH=/app/models/mobile_sam.pth
app.py CHANGED
@@ -2,6 +2,5 @@ from app import create_app
2
 
3
  app = create_app()
4
 
5
-
6
  if __name__ == '__main__':
7
- app.run()
 
2
 
3
  app = create_app()
4
 
 
5
  if __name__ == '__main__':
6
+ app.run()
app/__init__.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask
2
+ from flask_sqlalchemy import SQLAlchemy
3
+ from flask_cors import CORS
4
+
5
+ db = SQLAlchemy()
6
+
7
+ def create_app():
8
+ app = Flask(__name__)
9
+ app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://postgres:mysecretpassword@localhost:5432/piecefinder'
10
+ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
11
+ app.config['SECRET_KEY'] = 'your-secret-key'
12
+ app.config['REDIS_URL'] = 'redis://localhost:6379/0'
13
+
14
+ db.init_app(app)
15
+ CORS(app) # Enable CORS
16
+
17
+ with app.app_context():
18
+ db.create_all()
19
+
20
+ from app.routes import bp
21
+ app.register_blueprint(bp)
22
+
23
+ return app
app/config.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ class Config:
4
+ SECRET_KEY = os.environ.get('SECRET_KEY', 'default-secret-key')
5
+ DATABASE_URL = os.environ.get('DATABASE_URL', 'postgresql://user:pass@localhost/piecefinder')
6
+ REDIS_URL = os.environ.get('REDIS_URL', 'redis://localhost:6379/0')
7
+ YOLO_MODEL_PATH = os.environ.get('YOLO_MODEL_PATH', 'yolov8n.pt')
8
+ SAM_MODEL_PATH = os.environ.get('SAM_MODEL_PATH', 'mobile_sam.pth')
app/models/feedback_manager.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy import create_engine
2
+ import json
3
+ from app import create_app
4
+
5
+ class FeedbackManager:
6
+ def __init__(self):
7
+ app = create_app()
8
+ self.db = app.db
9
+
10
+ # Initialize feedback table if not exists
11
+ with self.db.connect() as conn:
12
+ conn.execute("""
13
+ CREATE TABLE IF NOT EXISTS feedback (
14
+ id SERIAL PRIMARY KEY,
15
+ data JSONB,
16
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
17
+ )
18
+ """)
19
+
20
+ def record_feedback(self, piece_id: int, slot_id: int, suggestions: dict, user_choice: int, is_correct: bool):
21
+ feedback_entry = {
22
+ 'piece_id': piece_id,
23
+ 'slot_id': slot_id,
24
+ 'suggestions': suggestions,
25
+ 'user_choice': user_choice,
26
+ 'is_correct': is_correct
27
+ }
28
+ with self.db.connect() as conn:
29
+ conn.execute(
30
+ "INSERT INTO feedback (data) VALUES (%s)",
31
+ (json.dumps(feedback_entry),)
32
+ )
33
+
34
+ def get_training_data(self) -> dict:
35
+ positive, negative = [], []
36
+ with self.db.connect() as conn:
37
+ results = conn.execute("SELECT data FROM feedback").fetchall()
38
+ for row in results:
39
+ entry = json.loads(row[0])
40
+ if entry['is_correct']:
41
+ positive.append({
42
+ 'piece_id': entry['piece_id'],
43
+ 'slot_id': entry['slot_id']
44
+ })
45
+ else:
46
+ negative.append({
47
+ 'piece_id': entry['piece_id'],
48
+ 'slot_id': entry['slot_id']
49
+ })
50
+ return {'positive': positive, 'negative': negative}
app/models/matcher.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import faiss
3
+ from sklearn.metrics.pairwise import cosine_similarity
4
+ import cv2
5
+
6
+ class PuzzleMatchingEngine:
7
+ def __init__(self):
8
+ self.weight_geometric = 0.35
9
+ self.weight_color = 0.30
10
+ self.weight_edge = 0.35
11
+ self.faiss_index = faiss.IndexFlatL2(10) # Assume 10D edge features
12
+
13
+ def find_matches(self, pieces: list, slots: list, top_k: int = 3) -> dict:
14
+ matches = {}
15
+ if not pieces or not slots:
16
+ return matches
17
+
18
+ # Prepare FAISS index with piece edge features
19
+ piece_edge_features = np.array([p['features']['edge'] for p in pieces], dtype=np.float32)
20
+ self.faiss_index.add(piece_edge_features)
21
+
22
+ for slot in slots:
23
+ slot_matches = []
24
+ slot_edge_features = np.array([slot['features']['edge']], dtype=np.float32)
25
+ distances, indices = self.faiss_index.search(slot_edge_features, top_k * 2)
26
+
27
+ for idx in indices[0]:
28
+ piece = pieces[idx]
29
+ geometric_score = self._calculate_geometric_similarity(piece['features']['geometric'], slot['features']['geometric'])
30
+ color_score = self._calculate_color_similarity(piece['features']['color'], slot['features']['color'])
31
+ edge_score = self._calculate_edge_similarity(piece['features']['edge'], slot['features']['edge'])
32
+ confidence = (
33
+ self.weight_geometric * geometric_score +
34
+ self.weight_color * color_score +
35
+ self.weight_edge * edge_score
36
+ )
37
+ slot_matches.append({
38
+ 'piece_id': piece['id'],
39
+ 'slot_id': slot['id'],
40
+ 'confidence': float(confidence),
41
+ 'geometric_score': float(geometric_score),
42
+ 'color_score': float(color_score),
43
+ 'edge_score': float(edge_score)
44
+ })
45
+
46
+ slot_matches.sort(key=lambda x: x['confidence'], reverse=True)
47
+ matches[slot['id']] = slot_matches[:top_k]
48
+
49
+ return matches
50
+
51
+ def _calculate_geometric_similarity(self, piece_features: dict, slot_features: dict) -> float:
52
+ # Compare area, perimeter, aspect ratio, etc.
53
+ area_diff = abs(piece_features['area'] - slot_features['area']) / max(piece_features['area'], slot_features['area'])
54
+ perimeter_diff = abs(piece_features['perimeter'] - slot_features['perimeter']) / max(piece_features['perimeter'], slot_features['perimeter'])
55
+ return 1.0 - (0.5 * area_diff + 0.5 * perimeter_diff)
56
+
57
+ def _calculate_color_similarity(self, piece_histogram: np.ndarray, slot_histogram: np.ndarray) -> float:
58
+ return cv2.compareHist(piece_histogram, slot_histogram, cv2.HISTCMP_CORREL)
59
+
60
+ def _calculate_edge_similarity(self, piece_features: np.ndarray, slot_features: np.ndarray) -> float:
61
+ return cosine_similarity(piece_features.reshape(1, -1), slot_features.reshape(1, -1))[0, 0]
app/models/segmenter.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ from ultralytics import YOLO
4
+ from segment_anything import SamPredictor, sam_model_registry
5
+ from app.utils.preprocess import preprocess_image
6
+ from app.utils.feature_extraction import extract_piece_features
7
+
8
+ class PuzzlePieceSegmenter:
9
+ def __init__(self):
10
+ # Load models
11
+ self.yolo = YOLO("yolov8n.pt") # Lightweight YOLOv8 model
12
+ self.sam = sam_model_registry["vit_b"](checkpoint="mobile_sam.pth")
13
+ self.predictor = SamPredictor(self.sam)
14
+ self.min_piece_area = 500
15
+ self.max_piece_area = 50000
16
+
17
+ def segment_pieces(self, image: np.ndarray) -> list:
18
+ # Preprocess image
19
+ processed = preprocess_image(image)
20
+
21
+ # YOLOv8 for coarse detection
22
+ results = self.yolo(processed)
23
+ boxes = results[0].boxes.xyxy.cpu().numpy()
24
+
25
+ # SAM for fine segmentation
26
+ self.predictor.set_image(processed)
27
+ pieces = []
28
+ piece_id = 0
29
+
30
+ for box in boxes:
31
+ masks, _, _ = self.predictor.predict(box_coordinates=box, multimask_output=False)
32
+ for mask in masks:
33
+ contours, _ = cv2.findContours(mask.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
34
+ for contour in contours:
35
+ area = cv2.contourArea(contour)
36
+ if self.min_piece_area <= area <= self.max_piece_area:
37
+ # Extract features and image for this piece
38
+ piece_image, features = extract_piece_features(processed, contour)
39
+ if features:
40
+ pieces.append({
41
+ 'id': piece_id,
42
+ 'image': cv2.imencode('.jpg', piece_image)[1].tobytes(),
43
+ 'features': features
44
+ })
45
+ piece_id += 1
46
+
47
+ return pieces
app/routes/count.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Blueprint, request, jsonify
2
+ from app.models.segmenter import PuzzlePieceSegmenter
3
+ import base64
4
+ import numpy as np
5
+ import cv2
6
+
7
+ count_bp = Blueprint('count', __name__)
8
+
9
+ @count_bp.route('/count', methods=['POST'])
10
+ def count_pieces():
11
+ try:
12
+ data = request.get_json()
13
+ image_base64 = data.get('image_base64')
14
+ if not image_base64:
15
+ return jsonify({'error': 'No image provided'}), 400
16
+
17
+ # Decode base64 image
18
+ image_data = base64.b64decode(image_base64)
19
+ nparr = np.frombuffer(image_data, np.uint8)
20
+ image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
21
+
22
+ # Initialize segmenter
23
+ segmenter = PuzzlePieceSegmenter()
24
+
25
+ # Perform segmentation and count
26
+ pieces = segmenter.segment_pieces(image)
27
+ count = len(pieces)
28
+
29
+ return jsonify({'count': count}), 200
30
+ except Exception as e:
31
+ return jsonify({'error': str(e)}), 500
app/routes/feedback.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Blueprint, request, jsonify
2
+ from app.models.feedback_manager import FeedbackManager
3
+
4
+ feedback_bp = Blueprint('feedback', __name__)
5
+
6
+ @feedback_bp.route('/feedback', methods=['POST'])
7
+ def submit_feedback():
8
+ try:
9
+ data = request.get_json()
10
+ piece_id = data.get('pieceId')
11
+ slot_id = data.get('slotId')
12
+ suggestions = data.get('suggestions')
13
+ user_choice = data.get('userChoice')
14
+ is_correct = data.get('isCorrect')
15
+
16
+ if not all([piece_id, slot_id, suggestions, user_choice is not None, is_correct is not None]):
17
+ return jsonify({'error': 'Missing required fields'}), 400
18
+
19
+ # Initialize feedback manager
20
+ feedback_manager = FeedbackManager()
21
+
22
+ # Record feedback
23
+ feedback_manager.record_feedback(piece_id, slot_id, suggestions, user_choice, is_correct)
24
+
25
+ return jsonify({'message': 'Feedback recorded successfully'}), 200
26
+ except Exception as e:
27
+ return jsonify({'error': str(e)}), 500
app/routes/match.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Blueprint, request, jsonify
2
+ from app.models.matcher import PuzzleMatchingEngine
3
+ from app.models.segmenter import PuzzlePieceSegmenter
4
+ import base64
5
+ import numpy as np
6
+ import cv2
7
+
8
+ match_bp = Blueprint('match', __name__)
9
+
10
+ @match_bp.route('/match', methods=['POST'])
11
+ def match_pieces():
12
+ try:
13
+ data = request.get_json()
14
+ image_base64 = data.get('image_base64')
15
+ if not image_base64:
16
+ return jsonify({'error': 'No image provided'}), 400
17
+
18
+ # Decode base64 image
19
+ image_data = base64.b64decode(image_base64)
20
+ nparr = np.frombuffer(image_data, np.uint8)
21
+ image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
22
+
23
+ # Initialize segmenter and matcher
24
+ segmenter = PuzzlePieceSegmenter()
25
+ matcher = PuzzleMatchingEngine()
26
+
27
+ # Segment pieces and slots (assuming slots are from a reference image)
28
+ pieces = segmenter.segment_pieces(image)
29
+ # For demo, assume slots are pre-segmented or same as pieces
30
+ slots = pieces # Replace with actual slot segmentation logic
31
+
32
+ # Perform matching
33
+ matches = matcher.find_matches(pieces, slots, top_k=3)
34
+
35
+ # Serialize matches
36
+ serialized_matches = []
37
+ for slot_id, slot_matches in matches.items():
38
+ for match in slot_matches:
39
+ serialized_matches.append({
40
+ 'piece_id': match['piece_id'],
41
+ 'slot_id': match['slot_id'],
42
+ 'confidence': match['confidence'],
43
+ 'geometric_score': match['geometric_score'],
44
+ 'color_score': match['color_score'],
45
+ 'edge_score': match['edge_score']
46
+ })
47
+
48
+ return jsonify({
49
+ 'matches': serialized_matches,
50
+ 'slots': [{'id': slot['id']} for slot in slots]
51
+ }), 200
52
+ except Exception as e:
53
+ return jsonify({'error': str(e)}), 500
app/routes/segment.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Blueprint, request, jsonify
2
+ from app.models.segmenter import PuzzlePieceSegmenter
3
+ import base64
4
+ import numpy as np
5
+ import cv2
6
+
7
+ segment_bp = Blueprint('segment', __name__)
8
+
9
+ @segment_bp.route('/segment', methods=['POST'])
10
+ def segment_image():
11
+ try:
12
+ data = request.get_json()
13
+ image_base64 = data.get('image_base64')
14
+ if not image_base64:
15
+ return jsonify({'error': 'No image provided'}), 400
16
+
17
+ # Decode base64 image
18
+ image_data = base64.b64decode(image_base64)
19
+ nparr = np.frombuffer(image_data, np.uint8)
20
+ image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
21
+
22
+ # Initialize segmenter
23
+ segmenter = PuzzlePieceSegmenter()
24
+
25
+ # Perform segmentation
26
+ pieces = segmenter.segment_pieces(image)
27
+
28
+ # Serialize pieces
29
+ serialized_pieces = [
30
+ {
31
+ 'id': piece['id'],
32
+ 'image': base64.b64encode(piece['image']).decode('utf-8'),
33
+ 'features': {
34
+ 'geometric': piece['features']['geometric'],
35
+ 'color': piece['features']['color'],
36
+ 'edge': piece['features']['edge']
37
+ }
38
+ }
39
+ for piece in pieces
40
+ ]
41
+
42
+ return jsonify({'pieces': serialized_pieces}), 200
43
+ except Exception as e:
44
+ return jsonify({'error': str(e)}), 500
app/utils/feature_extraction.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+
4
+ def extract_piece_features(image: np.ndarray, contour: np.ndarray) -> tuple:
5
+ # Create mask for the piece
6
+ mask = np.zeros(image.shape[:2], dtype=np.uint8)
7
+ cv2.drawContours(mask, [contour], -1, 255, -1)
8
+
9
+ # Extract piece image
10
+ piece_image = cv2.bitwise_and(image, image, mask=mask)
11
+
12
+ # Geometric features
13
+ area = cv2.contourArea(contour)
14
+ perimeter = cv2.arcLength(contour, True)
15
+ rect = cv2.minAreaRect(contour)
16
+ aspect_ratio = rect[1][0] / rect[1][1] if rect[1][1] != 0 else 1.0
17
+
18
+ # Color histogram
19
+ color_histogram = cv2.calcHist([piece_image], [0, 1, 2], mask, [8, 8, 8], [0, 256, 0, 256, 0, 256])
20
+ color_histogram = cv2.normalize(color_histogram, color_histogram).flatten()
21
+
22
+ # Edge features (simplified)
23
+ gray = cv2.cvtColor(piece_image, cv2.COLOR_BGR2GRAY)
24
+ edges = cv2.Canny(gray, 100, 200)
25
+ edge_features = np.histogram(edges[mask == 255], bins=10, range=(0, 255))[0].astype(np.float32)
26
+ edge_features /= edge_features.sum() + 1e-10
27
+
28
+ features = {
29
+ 'geometric': {'area': float(area), 'perimeter': float(perimeter), 'aspect_ratio': float(aspect_ratio)},
30
+ 'color': color_histogram,
31
+ 'edge': edge_features
32
+ }
33
+
34
+ return piece_image, features
app/utils/preprocess.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+
4
+ def preprocess_image(image: np.ndarray) -> np.ndarray:
5
+ # Convert to LAB color space and apply CLAHE
6
+ lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
7
+ clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
8
+ lab[:, :, 0] = clahe.apply(lab[:, :, 0])
9
+ enhanced = cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)
10
+
11
+ # Apply bilateral filtering
12
+ denoised = cv2.bilateralFilter(enhanced, 9, 75, 75)
13
+
14
+ return denoised
app/utils/visualize.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+
4
+ def visualize_pieces(image: np.ndarray, pieces: list) -> np.ndarray:
5
+ vis_image = image.copy()
6
+ for piece in pieces:
7
+ contour = piece.get('contour') # Assume contour is stored
8
+ if contour is not None:
9
+ cv2.drawContours(vis_image, [contour], -1, (0, 255, 0), 2)
10
+ cv2.putText(
11
+ vis_image,
12
+ f"ID: {piece['id']}",
13
+ (int(contour[:, :, 0].min()), int(contour[:, :, 1].min()) - 10),
14
+ cv2.FONT_HERSHEY_SIMPLEX,
15
+ 0.5,
16
+ (0, 0, 255),
17
+ 1
18
+ )
19
+ return vis_image
deployments/Dockerfile ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ COPY . .
9
+
10
+ ENV FLASK_APP=main.py
11
+ ENV FLASK_RUN_HOST=0.0.0.0
12
+
13
+ EXPOSE 5000
14
+
15
+ CMD ["flask", "run"]
deployments/kubernetes.yaml ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ apiVersion: apps/v1
2
+ kind: Deployment
3
+ metadata:
4
+ name: piecefinder-backend
5
+ spec:
6
+ replicas: 2
7
+ selector:
8
+ matchLabels:
9
+ app: piecefinder-backend
10
+ template:
11
+ metadata:
12
+ labels:
13
+ app: piecefinder-backend
14
+ spec:
15
+ containers:
16
+ - name: piecefinder-backend
17
+ image: piecefinder-backend:latest
18
+ ports:
19
+ - containerPort: 5000
20
+ env:
21
+ - name: DATABASE_URL
22
+ valueFrom:
23
+ secretKeyRef:
24
+ name: db-credentials
25
+ key: database-url
26
+ - name: REDIS_URL
27
+ valueFrom:
28
+ secretKeyRef:
29
+ name: redis-credentials
30
+ key: redis-url
31
+ ---
32
+ apiVersion: v1
33
+ kind: Service
34
+ metadata:
35
+ name: piecefinder-backend
36
+ spec:
37
+ selector:
38
+ app: piecefinder-backend
39
+ ports:
40
+ - protocol: TCP
41
+ port: 80
42
+ targetPort: 5000
43
+ type: ClusterIP
deployments/requirements.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ flask
2
+ opencv-python
3
+ numpy
4
+ ultralytics
5
+ segment-anything
6
+ faiss-cpu
7
+ tensorflow
8
+ scikit-image
9
+ sqlalchemy
10
+ psycopg2-binary
11
+ redis
12
+ flask-cors
main.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ from app import create_app
2
+
3
+ app = create_app()
4
+
5
+ if __name__ == '__main__':
6
+ app.run(debug=True, host='0.0.0.0', port=5000)
models/mobile_sam.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6dbb90523a35330fedd7f1d3dfc66f995213d81b29a5ca8108dbcdd4e37d6c2f
3
+ size 40728226
models/yolov8n.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f59b3d833e2ff32e194b5bb8e08d211dc7c5bdf144b90d2c8412c47ccfc83b36
3
+ size 6549796
scripts/data_augmentation.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ import os
4
+
5
+ def generate_synthetic_puzzle(num_images: int, output_dir: str):
6
+ os.makedirs(output_dir, exist_ok=True)
7
+
8
+ for i in range(num_images):
9
+ # Create blank image
10
+ image = np.ones((1000, 1000, 3), dtype=np.uint8) * 255
11
+
12
+ # Add random puzzle pieces (simplified)
13
+ for _ in range(np.random.randint(10, 50)):
14
+ x = np.random.randint(100, 900)
15
+ y = np.random.randint(100, 900)
16
+ size = np.random.randint(50, 200)
17
+ color = (np.random.randint(0, 256), np.random.randint(0, 256), np.random.randint(0, 256))
18
+ cv2.rectangle(image, (x, y), (x + size, y + size), color, -1)
19
+
20
+ # Save image
21
+ cv2.imwrite(os.path.join(output_dir, f'puzzle_{i}.jpg'), image)
22
+
23
+ if __name__ == '__main__':
24
+ generate_synthetic_puzzle(10, 'synthetic_puzzles')
scripts/train.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app.models.feedback_manager import FeedbackManager
2
+ from app.models.matcher import PuzzleMatchingEngine
3
+
4
+ def retrain_model():
5
+ feedback_manager = FeedbackManager()
6
+ training_data = feedback_manager.get_training_data()
7
+
8
+ # Dummy retraining logic (adjust weights based on feedback)
9
+ matcher = PuzzleMatchingEngine()
10
+ positive_samples = training_data['positive']
11
+ negative_samples = training_data['negative']
12
+
13
+ # Example: Increase weights for correct matches
14
+ if positive_samples:
15
+ matcher.weight_geometric += 0.01
16
+ matcher.weight_color += 0.01
17
+ matcher.weight_edge += 0.01
18
+ total = matcher.weight_geometric + matcher.weight_color + matcher.weight_edge
19
+ matcher.weight_geometric /= total
20
+ matcher.weight_color /= total
21
+ matcher.weight_edge /= total
22
+
23
+ print("Model weights updated:", {
24
+ 'geometric': matcher.weight_geometric,
25
+ 'color': matcher.weight_color,
26
+ 'edge': matcher.weight_edge
27
+ })
28
+
29
+ if __name__ == '__main__':
30
+ retrain_model()
setup.py ADDED
File without changes
tests/test_feedback.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import unittest
2
+ from app.models.feedback_manager import FeedbackManager
3
+
4
+ class TestFeedback(unittest.TestCase):
5
+ def setUp(self):
6
+ self.feedback_manager = FeedbackManager()
7
+
8
+ def test_record_feedback(self):
9
+ self.feedback_manager.record_feedback(
10
+ piece_id=1,
11
+ slot_id=1,
12
+ suggestions={'piece_id': 1, 'confidence': 0.9},
13
+ user_choice=1,
14
+ is_correct=True
15
+ )
16
+ training_data = self.feedback_manager.get_training_data()
17
+ self.assertIn('positive', training_data)
18
+ self.assertGreaterEqual(len(training_data['positive']), 1)
19
+
20
+ if __name__ == '__main__':
21
+ unittest.main()
tests/test_matching.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import unittest
2
+ import numpy as np
3
+ from app.models.matcher import PuzzleMatchingEngine
4
+
5
+ class TestMatching(unittest.TestCase):
6
+ def setUp(self):
7
+ self.matcher = PuzzleMatchingEngine()
8
+ self.dummy_piece = {
9
+ 'id': 0,
10
+ 'features': {
11
+ 'geometric': {'area': 1000, 'perimeter': 400, 'aspect_ratio': 1.0},
12
+ 'color': np.zeros(512, dtype=np.float32),
13
+ 'edge': np.zeros(10, dtype=np.float32)
14
+ }
15
+ }
16
+
17
+ def test_find_matches(self):
18
+ pieces = [self.dummy_piece]
19
+ slots = [self.dummy_piece]
20
+ matches = self.matcher.find_matches(pieces, slots)
21
+ self.assertIsInstance(matches, dict)
22
+ self.assertIn(0, matches)
23
+ self.assertGreaterEqual(len(matches[0]), 1)
24
+
25
+ if __name__ == '__main__':
26
+ unittest.main()
tests/test_segmentation.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import unittest
2
+ import cv2
3
+ import numpy as np
4
+ from app.models.segmenter import PuzzlePieceSegmenter
5
+
6
+ class TestSegmentation(unittest.TestCase):
7
+ def setUp(self):
8
+ self.segmenter = PuzzlePieceSegmenter()
9
+ self.test_image = np.zeros((1000, 1000, 3), dtype=np.uint8) # Dummy image
10
+
11
+ def test_segment_pieces(self):
12
+ pieces = self.segmenter.segment_pieces(self.test_image)
13
+ self.assertIsInstance(pieces, list)
14
+ for piece in pieces:
15
+ self.assertIn('id', piece)
16
+ self.assertIn('image', piece)
17
+ self.assertIn('features', piece)
18
+
19
+ if __name__ == '__main__':
20
+ unittest.main()