Steph254 commited on
Commit
203a0a4
·
verified ·
1 Parent(s): f0873e9

Upload 12 files

Browse files
Files changed (12) hide show
  1. .env +3 -0
  2. Dockerfile +16 -0
  3. README.md +0 -10
  4. app.py +6 -0
  5. app/__init__.py +20 -0
  6. app/feedback.py +23 -0
  7. app/matcher.py +20 -0
  8. app/routes.py +112 -0
  9. app/segmenter.py +35 -0
  10. app/utils.py +15 -0
  11. main.py +6 -0
  12. requirements.txt +4 -0
.env ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ FLASK_ENV=development
2
+ SQLALCHEMY_DATABASE_URI=sqlite:///feedback.db
3
+ SECRET_KEY=your-secret-key
Dockerfile ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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=app.py
11
+ ENV FLASK_RUN_HOST=0.0.0.0
12
+ ENV FLASK_RUN_PORT=7860 # Hugging Face Spaces default port
13
+
14
+ EXPOSE 7860
15
+
16
+ CMD ["flask", "run"]
README.md CHANGED
@@ -1,10 +0,0 @@
1
- ---
2
- title: Piecefinder
3
- emoji: 👁
4
- colorFrom: yellow
5
- colorTo: red
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
app.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()
app/__init__.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask
2
+ from flask_sqlalchemy import SQLAlchemy
3
+
4
+ db = SQLAlchemy()
5
+
6
+ def create_app():
7
+ app = Flask(__name__)
8
+ app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///feedback.db'
9
+ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
10
+ app.config['SECRET_KEY'] = 'your-secret-key' # Replace with env variable in production
11
+
12
+ db.init_app(app)
13
+
14
+ with app.app_context():
15
+ db.create_all() # Create database tables
16
+
17
+ from app.routes import bp
18
+ app.register_blueprint(bp)
19
+
20
+ return app
app/feedback.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app import db
2
+ from flask_sqlalchemy import SQLAlchemy
3
+ import json
4
+
5
+ class Feedback(db.Model):
6
+ id = db.Column(db.Integer, primary_key=True)
7
+ piece_id = db.Column(db.Integer, nullable=False)
8
+ slot_id = db.Column(db.Integer, nullable=False)
9
+ suggestions = db.Column(db.Text, nullable=False)
10
+ user_choice = db.Column(db.Integer, nullable=False)
11
+ is_correct = db.Column(db.Boolean, nullable=False)
12
+
13
+ class FeedbackManager:
14
+ def record_feedback(self, piece_id: int, slot_id: int, suggestions: dict, user_choice: int, is_correct: bool):
15
+ feedback = Feedback(
16
+ piece_id=piece_id,
17
+ slot_id=slot_id,
18
+ suggestions=json.dumps(suggestions),
19
+ user_choice=user_choice,
20
+ is_correct=is_correct
21
+ )
22
+ db.session.add(feedback)
23
+ db.session.commit()
app/matcher.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+
4
+ class SimpleMatcher:
5
+ def find_matches(self, pieces: list, slots: list, top_k: int = 3) -> list:
6
+ matches = []
7
+ for slot in slots:
8
+ slot_hist = slot['features']['color']
9
+ slot_matches = []
10
+ for piece in pieces:
11
+ piece_hist = piece['features']['color']
12
+ confidence = cv2.compareHist(piece_hist, slot_hist, cv2.HISTCMP_CORREL)
13
+ slot_matches.append({
14
+ 'piece_id': piece['id'],
15
+ 'slot_id': slot['id'],
16
+ 'confidence': float(confidence)
17
+ })
18
+ slot_matches.sort(key=lambda x: x['confidence'], reverse=True)
19
+ matches.extend(slot_matches[:top_k])
20
+ return matches
app/routes.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Blueprint, request, jsonify
2
+ from app.segmenter import SimpleSegmenter
3
+ from app.matcher import SimpleMatcher
4
+ from app.feedback import FeedbackManager
5
+ import base64
6
+ import cv2
7
+ import numpy as np
8
+
9
+ bp = Blueprint('api', __name__)
10
+
11
+ @bp.route('/segment', methods=['POST'])
12
+ def segment_image():
13
+ try:
14
+ data = request.get_json()
15
+ image_base64 = data.get('image_base64')
16
+ if not image_base64:
17
+ return jsonify({'error': 'No image provided'}), 400
18
+
19
+ # Decode base64 image
20
+ image_data = base64.b64decode(image_base64)
21
+ nparr = np.frombuffer(image_data, np.uint8)
22
+ image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
23
+
24
+ # Segment pieces
25
+ segmenter = SimpleSegmenter()
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': piece['features']
34
+ }
35
+ for piece in pieces
36
+ ]
37
+
38
+ return jsonify({'pieces': serialized_pieces}), 200
39
+ except Exception as e:
40
+ return jsonify({'error': str(e)}), 500
41
+
42
+ @bp.route('/count', methods=['POST'])
43
+ def count_pieces():
44
+ try:
45
+ data = request.get_json()
46
+ image_base64 = data.get('image_base64')
47
+ if not image_base64:
48
+ return jsonify({'error': 'No image provided'}), 400
49
+
50
+ image_data = base64.b64decode(image_base64)
51
+ nparr = np.frombuffer(image_data, np.uint8)
52
+ image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
53
+
54
+ segmenter = SimpleSegmenter()
55
+ pieces = segmenter.segment_pieces(image)
56
+ count = len(pieces)
57
+
58
+ return jsonify({'count': count}), 200
59
+ except Exception as e:
60
+ return jsonify({'error': str(e)}), 500
61
+
62
+ @bp.route('/match', methods=['POST'])
63
+ def match_pieces():
64
+ try:
65
+ data = request.get_json()
66
+ image_base64 = data.get('image_base64')
67
+ if not image_base64:
68
+ return jsonify({'error': 'No image provided'}), 400
69
+
70
+ image_data = base64.b64decode(image_base64)
71
+ nparr = np.frombuffer(image_data, np.uint8)
72
+ image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
73
+
74
+ segmenter = SimpleSegmenter()
75
+ matcher = SimpleMatcher()
76
+ pieces = segmenter.segment_pieces(image)
77
+ slots = pieces # Simplified: assume slots are same as pieces
78
+
79
+ matches = matcher.find_matches(pieces, slots)
80
+
81
+ serialized_matches = [
82
+ {
83
+ 'piece_id': match['piece_id'],
84
+ 'slot_id': match['slot_id'],
85
+ 'confidence': match['confidence']
86
+ }
87
+ for match in matches
88
+ ]
89
+
90
+ return jsonify({'matches': serialized_matches, 'slots': [{'id': slot['id']} for slot in slots]}), 200
91
+ except Exception as e:
92
+ return jsonify({'error': str(e)}), 500
93
+
94
+ @bp.route('/feedback', methods=['POST'])
95
+ def submit_feedback():
96
+ try:
97
+ data = request.get_json()
98
+ piece_id = data.get('pieceId')
99
+ slot_id = data.get('slotId')
100
+ suggestions = data.get('suggestions')
101
+ user_choice = data.get('userChoice')
102
+ is_correct = data.get('isCorrect')
103
+
104
+ if not all([piece_id, slot_id, suggestions, user_choice is not None, is_correct is not None]):
105
+ return jsonify({'error': 'Missing required fields'}), 400
106
+
107
+ feedback_manager = FeedbackManager()
108
+ feedback_manager.record_feedback(piece_id, slot_id, suggestions, user_choice, is_correct)
109
+
110
+ return jsonify({'message': 'Feedback recorded successfully'}), 200
111
+ except Exception as e:
112
+ return jsonify({'error': str(e)}), 500
app/segmenter.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ from app.utils import preprocess_image, extract_features
4
+
5
+ class SimpleSegmenter:
6
+ def __init__(self):
7
+ self.min_piece_area = 500
8
+ self.max_piece_area = 50000
9
+
10
+ def segment_pieces(self, image: np.ndarray) -> list:
11
+ # Preprocess image
12
+ processed = preprocess_image(image)
13
+ gray = cv2.cvtColor(processed, cv2.COLOR_BGR2GRAY)
14
+ _, thresh = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY_INV)
15
+
16
+ # Find contours
17
+ contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
18
+ pieces = []
19
+ piece_id = 0
20
+
21
+ for contour in contours:
22
+ area = cv2.contourArea(contour)
23
+ if self.min_piece_area <= area <= self.max_piece_area:
24
+ mask = np.zeros(image.shape[:2], dtype=np.uint8)
25
+ cv2.drawContours(mask, [contour], -1, 255, -1)
26
+ piece_image = cv2.bitwise_and(image, image, mask=mask)
27
+ features = extract_features(piece_image, mask)
28
+ pieces.append({
29
+ 'id': piece_id,
30
+ 'image': cv2.imencode('.jpg', piece_image)[1].tobytes(),
31
+ 'features': features
32
+ })
33
+ piece_id += 1
34
+
35
+ return pieces
app/utils.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+
4
+ def preprocess_image(image: np.ndarray) -> np.ndarray:
5
+ # Simple preprocessing: enhance contrast
6
+ lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
7
+ clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
8
+ lab[:, :, 0] = clahe.apply(lab[:, :, 0])
9
+ return cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)
10
+
11
+ def extract_features(image: np.ndarray, mask: np.ndarray) -> dict:
12
+ # Extract color histogram
13
+ color_histogram = cv2.calcHist([image], [0, 1, 2], mask, [8, 8, 8], [0, 256, 0, 256, 0, 256])
14
+ color_histogram = cv2.normalize(color_histogram, color_histogram).flatten()
15
+ return {'color': color_histogram}
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)
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ flask
2
+ opencv-python
3
+ numpy
4
+ flask-sqlalchemy