SakibAhmed commited on
Commit
7616805
·
verified ·
1 Parent(s): 6f18220

Upload 8 files

Browse files
.env ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # --- Model Configuration ---
2
+ PARTS_MODEL_NAME=best_parts_EP336.pt
3
+ DAMAGE_MODEL_NAME=best_new_EP382.pt
Dockerfile ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Python runtime as a parent image
2
+ FROM python:3.10-slim
3
+
4
+ # Set the working directory in the container
5
+ WORKDIR /app
6
+
7
+ # Install system dependencies for OpenCV and other packages
8
+ RUN apt-get update && apt-get install -y \
9
+ libgl1-mesa-glx \
10
+ libglib2.0-0 \
11
+ libsm6 \
12
+ libxext6 \
13
+ libxrender-dev \
14
+ libgomp1 \
15
+ && rm -rf /var/lib/apt/lists/*
16
+
17
+ # Copy the requirements file
18
+ COPY requirements.txt requirements.txt
19
+
20
+ # Install Python packages
21
+ RUN pip install --no-cache-dir -r requirements.txt
22
+
23
+ # Copy application code
24
+ COPY . /app
25
+
26
+ # Create a non-root user
27
+ RUN useradd -m -u 1000 user
28
+
29
+ # Change ownership
30
+ RUN chown -R user:user /app
31
+
32
+ # Switch to the non-root user
33
+ USER user
34
+
35
+ # Expose the port Gunicorn will run on (Using 7860 as in CMD)
36
+ EXPOSE 7860
37
+
38
+ # Command to run the app
39
+ CMD ["python", "app.py", "--host", "0.0.0.0", "--port", "7860"]
app.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+ from flask import Flask, request, jsonify, render_template, Response
4
+ from flask_cors import CORS
5
+ from werkzeug.utils import secure_filename
6
+ from ultralytics import YOLO
7
+ from dotenv import load_dotenv
8
+ import time
9
+ import json
10
+ import traceback
11
+
12
+ # Import the processing logic
13
+ from processing import process_images
14
+
15
+ # Load environment variables from .env file
16
+ load_dotenv()
17
+
18
+ app = Flask(__name__)
19
+
20
+ # Enable CORS for all routes
21
+ CORS(app)
22
+
23
+ # --- Configuration ---
24
+ UPLOAD_FOLDER = 'static/uploads'
25
+ MODELS_FOLDER = 'models'
26
+ ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg'}
27
+
28
+ # --- Load model names from .env file ---
29
+ PARTS_MODEL_NAME = os.getenv('PARTS_MODEL_NAME', 'best_parts_EP336.pt')
30
+ DAMAGE_MODEL_NAME = os.getenv('DAMAGE_MODEL_NAME', 'best_new_EP382.pt')
31
+
32
+ # --- Model Paths ---
33
+ PARTS_MODEL_PATH = os.path.join(MODELS_FOLDER, PARTS_MODEL_NAME)
34
+ DAMAGE_MODEL_PATH = os.path.join(MODELS_FOLDER, DAMAGE_MODEL_NAME)
35
+
36
+ app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
37
+ os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
38
+ os.makedirs(MODELS_FOLDER, exist_ok=True)
39
+ os.makedirs('templates', exist_ok=True)
40
+
41
+ # --- Determine Device ---
42
+ device = "cuda" if torch.cuda.is_available() else "cpu"
43
+ print(f"Using device: {device}")
44
+
45
+ # --- Load YOLO Models ---
46
+ parts_model, damage_model = None, None
47
+
48
+ # Load Parts Model
49
+ try:
50
+ if not os.path.exists(PARTS_MODEL_PATH):
51
+ print(f"Warning: Parts model file not found at {PARTS_MODEL_PATH}")
52
+ else:
53
+ parts_model = YOLO(PARTS_MODEL_PATH)
54
+ parts_model.to(device)
55
+ print(f"Successfully loaded parts model '{PARTS_MODEL_NAME}' on {device}.")
56
+ except Exception as e:
57
+ print(f"Error loading Parts Model ({PARTS_MODEL_NAME}): {e}")
58
+
59
+ # Load Damage Model
60
+ try:
61
+ if not os.path.exists(DAMAGE_MODEL_PATH):
62
+ print(f"Warning: Damage model file not found at {DAMAGE_MODEL_PATH}")
63
+ else:
64
+ damage_model = YOLO(DAMAGE_MODEL_PATH)
65
+ damage_model.to(device)
66
+ print(f"Successfully loaded damage model '{DAMAGE_MODEL_NAME}' on {device}.")
67
+ except Exception as e:
68
+ print(f"Error loading Damage Model ({DAMAGE_MODEL_NAME}): {e}")
69
+
70
+
71
+ def allowed_file(filename):
72
+ """Checks if a file's extension is in the ALLOWED_EXTENSIONS set."""
73
+ return '.' in filename and \
74
+ filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
75
+
76
+ @app.route('/')
77
+ def home():
78
+ """Serve the main HTML page."""
79
+ return render_template('index.html')
80
+
81
+ @app.route('/predict', methods=['POST'])
82
+ def predict():
83
+ """
84
+ Endpoint to receive one or more images, process them immediately,
85
+ and return the prediction results.
86
+ """
87
+ # 1. --- Get Session Key and Validate ---
88
+ # Session key can be used for logging or grouping, but doesn't control logic.
89
+ session_key = request.form.get('session_key')
90
+ if not session_key:
91
+ return jsonify({"error": "No session_key provided in the payload"}), 400
92
+
93
+ # 2. --- File Validation ---
94
+ if 'file' not in request.files:
95
+ return jsonify({"error": "No file part in the request"}), 400
96
+
97
+ files = request.files.getlist('file')
98
+ if not files or all(f.filename == '' for f in files):
99
+ return jsonify({"error": "No selected files"}), 400
100
+
101
+ # 3. --- Save Files and Prepare for Processing ---
102
+ saved_filepaths = []
103
+ for file in files:
104
+ if file and allowed_file(file.filename):
105
+ # Create a unique filename to prevent overwrites
106
+ unique_filename = f"{session_key}_{int(time.time()*1000)}_{secure_filename(file.filename)}"
107
+ filepath = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)
108
+ file.save(filepath)
109
+ saved_filepaths.append(filepath)
110
+ else:
111
+ print(f"Skipped invalid file: {file.filename}")
112
+
113
+ if not saved_filepaths:
114
+ return jsonify({"error": "No valid files were uploaded. Allowed types: png, jpg, jpeg"}), 400
115
+
116
+ # 4. --- Run Prediction ---
117
+ try:
118
+ print(f"Processing {len(saved_filepaths)} file(s) for session '{session_key}'...")
119
+
120
+ # This function processes the images and returns the prediction results.
121
+ results = process_images(parts_model, damage_model, saved_filepaths)
122
+
123
+ print(f"Processing complete for session '{session_key}'.")
124
+
125
+ # Return the results as a JSON response
126
+ return Response(json.dumps(results), mimetype='application/json')
127
+
128
+ except Exception as e:
129
+ print(f"An error occurred during processing for session {session_key}: {e}")
130
+ traceback.print_exc()
131
+ return jsonify({"error": f"An error occurred during processing: {str(e)}"}), 500
132
+ finally:
133
+ # 5. --- Clean up the saved files ---
134
+ for filepath in saved_filepaths:
135
+ try:
136
+ if os.path.exists(filepath):
137
+ os.remove(filepath)
138
+ except Exception as e:
139
+ print(f"Error cleaning up file {filepath}: {e}")
140
+
141
+
142
+ if __name__ == '__main__':
143
+ app.run(host='0.0.0.0', port=7860, debug=True)
models/best_new_EP382.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9a7d17e7205ca2134f2e416dcf8b072291971fa38e4ef361ad7cc5bd3a34bc0c
3
+ size 68668535
models/best_parts_EP336.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c31287038cb7d88d40500083a58ca13a35d7f6b9f3e4bfb29a6f9905c2d2f402
3
+ size 137014581
models/model_summary.txt ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ best_new_EP382.pt
2
+
3
+ {0: 'correct', 1: 'incorrect'}
4
+
5
+
6
+ best_parts_EP336.pt
7
+
8
+ {0: 'alloys',
9
+ 1: 'dashboard',
10
+ 2: 'driver_front_side',
11
+ 3: 'driver_rear_side',
12
+ 4: 'interior_front',
13
+ 5: 'passenger_front_side',
14
+ 6: 'passenger_rear_side',
15
+ 7: 'service_history',
16
+ 8: 'tyres'}
processing.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # processing.py
2
+
3
+ import os
4
+ from ultralytics import YOLO
5
+
6
+ # --- Configuration ---
7
+ # These are the specific parts that require a subsequent damage check.
8
+ DAMAGE_CHECK_PARTS = {
9
+ 'driver_front_side',
10
+ 'driver_rear_side',
11
+ 'passenger_front_side',
12
+ 'passenger_rear_side',
13
+ }
14
+
15
+ def run_single_inference(model, filepath):
16
+ """
17
+ Helper function to run inference for a single model and format the result.
18
+ """
19
+ if model is None:
20
+ return None # Return None if the model isn't loaded
21
+
22
+ results = model(filepath, verbose=False) # verbose=False to keep logs clean
23
+ result = results[0]
24
+
25
+ # Check if it's a classification model with probabilities
26
+ if result.probs is not None:
27
+ probs = result.probs
28
+ top1_index = probs.top1
29
+ top1_confidence = float(probs.top1conf)
30
+ class_name = model.names[top1_index]
31
+ else: # Fallback for detection models or if probs are not available
32
+ # Assuming the top prediction is what we need
33
+ top1_index = result.boxes.cls[0].int() if len(result.boxes) > 0 else 0
34
+ top1_confidence = float(result.boxes.conf[0]) if len(result.boxes) > 0 else 0.0
35
+ class_name = model.names[top1_index] if len(result.boxes) > 0 else "unknown"
36
+
37
+ return {
38
+ "class": class_name,
39
+ "confidence": round(top1_confidence, 4)
40
+ }
41
+
42
+ def process_images(parts_model, damage_model, image_paths):
43
+ """
44
+ Processes a list of images.
45
+ 1. Runs the 'parts_model' on every image.
46
+ 2. If the detected part is in DAMAGE_CHECK_PARTS, it then runs the 'damage_model'.
47
+ 3. Otherwise, the damage status defaults to 'correct'.
48
+ """
49
+ if parts_model is None or damage_model is None:
50
+ raise RuntimeError("One or more models are not loaded. Check server logs.")
51
+
52
+ final_results = []
53
+
54
+ for filepath in image_paths:
55
+ filename = os.path.basename(filepath)
56
+ print(f"Processing {filename}...")
57
+
58
+ # 1. First, predict the part
59
+ part_prediction = run_single_inference(parts_model, filepath)
60
+ predicted_part = part_prediction.get("class") if part_prediction else "unknown"
61
+
62
+ damage_prediction = None
63
+ # 2. Conditionally predict the damage
64
+ if predicted_part in DAMAGE_CHECK_PARTS:
65
+ print(f" -> Part '{predicted_part}' requires damage check. Running damage model...")
66
+ damage_prediction = run_single_inference(damage_model, filepath)
67
+ else:
68
+ print(f" -> Part '{predicted_part}' does not require damage check. Defaulting to 'correct'.")
69
+ # 3. For other parts, default to 'correct'
70
+ damage_prediction = {
71
+ "class": "correct",
72
+ "confidence": 1.0,
73
+ "note": "Result by default, not by model inference."
74
+ }
75
+
76
+ # Assemble the final result for this image
77
+ final_results.append({
78
+ "filename": filename,
79
+ "part_prediction": part_prediction,
80
+ "damage_prediction": damage_prediction
81
+ })
82
+
83
+ return final_results
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ Flask==3.1.1
2
+ flask_cors==5.0.1
3
+ python-dotenv==1.1.0
4
+ torch
5
+ ultralytics==8.3.151
6
+ Werkzeug==3.1.3
7
+ opencv-python-headless==4.10.0.84
8
+ psycopg2-binary==2.9.10