Upload 24 files
Browse files- .gitattributes +3 -0
- Damage_calculation.py +183 -0
- app.py +141 -0
- fetch_original.py +65 -0
- generator.py +70 -0
- model/Med_seg_test.pt +3 -0
- model/car_parts.pt +3 -0
- model/damage.pt +3 -0
- model/damage_4_class.pt +3 -0
- model/damage_nano_416.pt +3 -0
- pdf_report.py +130 -0
- predict_testing/Car_damages_1065.png +0 -0
- predict_testing/Car_damages_1070.png +0 -0
- predict_testing/Car_damages_1072.png +0 -0
- predict_testing/Car_damages_1185.png +3 -0
- predict_testing/Car_damages_1219.png +3 -0
- predict_testing/Car_damages_251.png +3 -0
- predict_testing/Car_damages_760.png +0 -0
- predict_testing/Car_damages_778.png +0 -0
- predict_testing/Car_damages_788.png +0 -0
- predict_testing/Car_damages_991.png +0 -0
- predict_testing/back.webp +0 -0
- predict_testing/front.webp +0 -0
- predict_testing/left.webp +0 -0
- predict_testing/right.webp +0 -0
.gitattributes
CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
36 |
+
predict_testing/Car_damages_1185.png filter=lfs diff=lfs merge=lfs -text
|
37 |
+
predict_testing/Car_damages_1219.png filter=lfs diff=lfs merge=lfs -text
|
38 |
+
predict_testing/Car_damages_251.png filter=lfs diff=lfs merge=lfs -text
|
Damage_calculation.py
ADDED
@@ -0,0 +1,183 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from shapely.geometry import Polygon
|
3 |
+
|
4 |
+
class DamageCalculator:
|
5 |
+
def __init__(self):
|
6 |
+
self.cost_ranges = {
|
7 |
+
"Scratch": {
|
8 |
+
"Minor": (1000, 3000),
|
9 |
+
"Moderate": (3000, 5000),
|
10 |
+
"Severe": (5000, 10000)
|
11 |
+
},
|
12 |
+
"Dent": {
|
13 |
+
"Minor": (2000, 4000),
|
14 |
+
"Moderate": (4000, 10000),
|
15 |
+
"Severe": (10000, 15000)
|
16 |
+
},
|
17 |
+
"Paint chip": {
|
18 |
+
"Minor": (1000, 2000),
|
19 |
+
"Moderate": (2000, 4000),
|
20 |
+
"Severe": (4000, 10000)
|
21 |
+
},
|
22 |
+
"Broken part": (1000, 7000)
|
23 |
+
}
|
24 |
+
|
25 |
+
def parse_coordinates(self, file_path):
|
26 |
+
polygons = []
|
27 |
+
class_names = []
|
28 |
+
with open(file_path, 'r') as file:
|
29 |
+
for line in file:
|
30 |
+
parts = line.strip().split()
|
31 |
+
class_name = []
|
32 |
+
coordinates = []
|
33 |
+
for part in parts:
|
34 |
+
try:
|
35 |
+
coordinates.append(float(part))
|
36 |
+
except ValueError:
|
37 |
+
class_name.append(part)
|
38 |
+
if class_name and coordinates:
|
39 |
+
class_name = " ".join(class_name)
|
40 |
+
try:
|
41 |
+
if len(coordinates) % 2 != 0:
|
42 |
+
raise ValueError("Coordinates are not in pairs.")
|
43 |
+
polygon = Polygon([(coordinates[i], coordinates[i+1]) for i in range(0, len(coordinates), 2)])
|
44 |
+
polygons.append(polygon)
|
45 |
+
class_names.append(class_name)
|
46 |
+
except ValueError as e:
|
47 |
+
print(f"Skipping line due to error: {e}")
|
48 |
+
else:
|
49 |
+
print(f"Skipping line due to insufficient data: {line.strip()}")
|
50 |
+
return class_names, polygons
|
51 |
+
|
52 |
+
def calculate_severity(self, coverage_percentage):
|
53 |
+
if 3 < coverage_percentage <= 30:
|
54 |
+
return "Minor"
|
55 |
+
elif 30 < coverage_percentage <= 60:
|
56 |
+
return "Moderate"
|
57 |
+
elif coverage_percentage > 60:
|
58 |
+
return "Severe"
|
59 |
+
return "N/A"
|
60 |
+
|
61 |
+
def calculate_cost(self, damage_class, severity):
|
62 |
+
if damage_class == "Broken part":
|
63 |
+
return self.cost_ranges["Broken part"]
|
64 |
+
elif severity in self.cost_ranges[damage_class]:
|
65 |
+
return self.cost_ranges[damage_class][severity]
|
66 |
+
return (0, 0)
|
67 |
+
|
68 |
+
def summarize_damage_by_part(self, damage_polygons, part_polygons):
|
69 |
+
intersection_info = self.calculate_intersection_area(damage_polygons, part_polygons)
|
70 |
+
|
71 |
+
summary = {}
|
72 |
+
for info in intersection_info:
|
73 |
+
part_class = info['part_class']
|
74 |
+
damage_class = info['damage_class']
|
75 |
+
coverage_percentage = info['coverage_percentage']
|
76 |
+
|
77 |
+
if part_class not in summary:
|
78 |
+
summary[part_class] = {'total_coverage': 0, 'damage_types': {}}
|
79 |
+
|
80 |
+
summary[part_class]['total_coverage'] += coverage_percentage
|
81 |
+
|
82 |
+
if damage_class not in summary[part_class]['damage_types']:
|
83 |
+
summary[part_class]['damage_types'][damage_class] = 0
|
84 |
+
|
85 |
+
summary[part_class]['damage_types'][damage_class] += coverage_percentage
|
86 |
+
|
87 |
+
for part_class in summary:
|
88 |
+
total_coverage = summary[part_class]['total_coverage']
|
89 |
+
for damage_class in summary[part_class]['damage_types']:
|
90 |
+
summary[part_class]['damage_types'][damage_class] = (
|
91 |
+
summary[part_class]['damage_types'][damage_class] / total_coverage * 100
|
92 |
+
)
|
93 |
+
|
94 |
+
summary[part_class]['undamaged'] = 100 - total_coverage
|
95 |
+
|
96 |
+
return summary
|
97 |
+
|
98 |
+
def calculate_intersection_area(self, damage_polygons, part_polygons):
|
99 |
+
intersection_info = []
|
100 |
+
front_back_door_detected = any(part_class in ['Front-door', 'Back-door'] for part_class, _ in part_polygons)
|
101 |
+
|
102 |
+
for damage_class, damage_polygon in damage_polygons:
|
103 |
+
for part_class, part_polygon in part_polygons:
|
104 |
+
if damage_polygon.intersects(part_polygon):
|
105 |
+
intersection = damage_polygon.intersection(part_polygon)
|
106 |
+
intersection_area = intersection.area
|
107 |
+
part_area = part_polygon.area
|
108 |
+
coverage_percentage = (intersection_area / part_area) * 100
|
109 |
+
|
110 |
+
# Condition 1: Ignore damage coverage <= 3%
|
111 |
+
if coverage_percentage <= 3:
|
112 |
+
continue
|
113 |
+
|
114 |
+
# Condition 2: Apply weights based on the new conditions
|
115 |
+
if part_class in ['Front-bumper', 'Back-bumper']:
|
116 |
+
if front_back_door_detected:
|
117 |
+
coverage_percentage *= 0.2
|
118 |
+
else:
|
119 |
+
coverage_percentage *= 0.6
|
120 |
+
|
121 |
+
if coverage_percentage <= 3:
|
122 |
+
continue
|
123 |
+
|
124 |
+
# Updated Condition 3: Include Headlight and tail light only with broken part if damage > 50%
|
125 |
+
if part_class in ['Headlight', 'Tail-light']:
|
126 |
+
if damage_class == 'Broken part' and coverage_percentage > 50:
|
127 |
+
intersection_info.append({
|
128 |
+
"damage_class": damage_class,
|
129 |
+
"part_class": part_class,
|
130 |
+
"intersection_area": intersection_area,
|
131 |
+
"part_area": part_area,
|
132 |
+
"coverage_percentage": coverage_percentage
|
133 |
+
})
|
134 |
+
continue
|
135 |
+
|
136 |
+
# Condition 4: Exclude damage with front-wheel, back wheel, and license plate
|
137 |
+
if part_class in ['Front-wheel', 'Back-wheel', 'License-plate']:
|
138 |
+
continue
|
139 |
+
|
140 |
+
intersection_info.append({
|
141 |
+
"damage_class": damage_class,
|
142 |
+
"part_class": part_class,
|
143 |
+
"intersection_area": intersection_area,
|
144 |
+
"part_area": part_area,
|
145 |
+
"coverage_percentage": coverage_percentage
|
146 |
+
})
|
147 |
+
|
148 |
+
# Condition 5: Sum coverage for the same type of defect on the same part
|
149 |
+
summarized_info = {}
|
150 |
+
for info in intersection_info:
|
151 |
+
key = (info['damage_class'], info['part_class'])
|
152 |
+
if key not in summarized_info:
|
153 |
+
summarized_info[key] = {
|
154 |
+
"intersection_area": 0,
|
155 |
+
"part_area": info['part_area'],
|
156 |
+
"coverage_percentage": 0,
|
157 |
+
"count": 0
|
158 |
+
}
|
159 |
+
summarized_info[key]["intersection_area"] += info["intersection_area"]
|
160 |
+
summarized_info[key]["coverage_percentage"] += info["coverage_percentage"]
|
161 |
+
summarized_info[key]["count"] += 1
|
162 |
+
|
163 |
+
final_info = []
|
164 |
+
for (damage_class, part_class), values in summarized_info.items():
|
165 |
+
part_area = values["part_area"]
|
166 |
+
intersection_area = values["intersection_area"]
|
167 |
+
coverage_percentage = values["coverage_percentage"]
|
168 |
+
count = values["count"]
|
169 |
+
severity = self.calculate_severity(coverage_percentage)
|
170 |
+
cost_min, cost_max = self.calculate_cost(damage_class, severity)
|
171 |
+
final_info.append({
|
172 |
+
"damage_class": damage_class,
|
173 |
+
"part_class": part_class,
|
174 |
+
"intersection_area": intersection_area,
|
175 |
+
"part_area": part_area,
|
176 |
+
"coverage_percentage": coverage_percentage,
|
177 |
+
"severity": severity,
|
178 |
+
"cost_min": cost_min,
|
179 |
+
"cost_max": cost_max,
|
180 |
+
"count": count
|
181 |
+
})
|
182 |
+
|
183 |
+
return final_info
|
app.py
ADDED
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
from PIL import Image
|
3 |
+
import os
|
4 |
+
import shutil
|
5 |
+
from ultralytics import YOLO
|
6 |
+
import tempfile
|
7 |
+
import pandas as pd
|
8 |
+
import plotly.express as px
|
9 |
+
from fetch_original import FileProcessor
|
10 |
+
from Damage_calculation import DamageCalculator
|
11 |
+
from generator import CSVGenerator
|
12 |
+
from pdf_report import PDFReportGenerator
|
13 |
+
|
14 |
+
class SegmentationModel:
|
15 |
+
def __init__(self, model_path):
|
16 |
+
self.model = YOLO(model_path)
|
17 |
+
|
18 |
+
def predict(self, input_path):
|
19 |
+
result = self.model.predict(source=input_path, save=True, show_conf=False, conf=0.70, save_txt=True)
|
20 |
+
img_path = result[0].save_dir
|
21 |
+
return img_path
|
22 |
+
|
23 |
+
class SegmentationApp:
|
24 |
+
def __init__(self, car_parts_model_path, damage_model_path, output_folder):
|
25 |
+
self.car_parts_model = SegmentationModel(car_parts_model_path)
|
26 |
+
self.damage_model = SegmentationModel(damage_model_path)
|
27 |
+
self.output_folder = output_folder
|
28 |
+
|
29 |
+
def copy_folder(self, src_folder, dest_folder):
|
30 |
+
if not os.path.exists(dest_folder):
|
31 |
+
os.makedirs(dest_folder)
|
32 |
+
for item in os.listdir(src_folder):
|
33 |
+
s = os.path.join(src_folder, item)
|
34 |
+
d = os.path.join(dest_folder, item)
|
35 |
+
if os.path.isdir(s):
|
36 |
+
shutil.copytree(s, d, dirs_exist_ok=True)
|
37 |
+
else:
|
38 |
+
shutil.copy2(s, d)
|
39 |
+
|
40 |
+
def clean_output_folder(self):
|
41 |
+
parts_output_folder = os.path.join(self.output_folder, 'parts')
|
42 |
+
damage_output_folder = os.path.join(self.output_folder, 'damage')
|
43 |
+
if os.path.exists(parts_output_folder):
|
44 |
+
shutil.rmtree(parts_output_folder)
|
45 |
+
if os.path.exists(damage_output_folder):
|
46 |
+
shutil.rmtree(damage_output_folder)
|
47 |
+
|
48 |
+
def run(self):
|
49 |
+
st.title("Car Damage Analyses And Cost Estimation")
|
50 |
+
st.markdown("### Important Guidelines for Using the App")
|
51 |
+
st.write("""
|
52 |
+
<div style="background-color: pink; color: black; padding: 10px; border-radius: 5px;">
|
53 |
+
<ul>
|
54 |
+
<li>Provide 4 images of the car from the following views: front, back, left, and right.</li>
|
55 |
+
<li>Ensure the images are taken at proper angles, standing parallel to the car, and that they are clear.</li>
|
56 |
+
<li>Make sure there are no obstacles between the car and the camera.</li>
|
57 |
+
<li>The car should not be behind any objects in the images.</li>
|
58 |
+
</ul>
|
59 |
+
</div>
|
60 |
+
""", unsafe_allow_html=True)
|
61 |
+
|
62 |
+
uploaded_files = st.file_uploader("Choose up to 4 images...", type=["jpg", "jpeg", "png", "webp"], accept_multiple_files=True)
|
63 |
+
|
64 |
+
if uploaded_files and len(uploaded_files) == 4:
|
65 |
+
st.write("Running segmentation models on uploaded images...")
|
66 |
+
car_parts_results_paths = []
|
67 |
+
damage_results_paths = []
|
68 |
+
|
69 |
+
self.clean_output_folder()
|
70 |
+
|
71 |
+
with st.spinner("Analyzing total damage..."):
|
72 |
+
for i, uploaded_file in enumerate(uploaded_files):
|
73 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix='.jpg') as temp_file:
|
74 |
+
temp_file.write(uploaded_file.getvalue())
|
75 |
+
temp_image_path = temp_file.name
|
76 |
+
|
77 |
+
car_parts_results = self.car_parts_model.predict(temp_image_path)
|
78 |
+
car_parts_results_paths.append(car_parts_results)
|
79 |
+
|
80 |
+
damage_results = self.damage_model.predict(temp_image_path)
|
81 |
+
damage_results_paths.append(damage_results)
|
82 |
+
|
83 |
+
self.car_parts_results_paths = car_parts_results_paths
|
84 |
+
self.damage_results_paths = damage_results_paths
|
85 |
+
|
86 |
+
for car_parts_results in car_parts_results_paths:
|
87 |
+
parts_output_folder = os.path.join(self.output_folder, 'parts')
|
88 |
+
self.copy_folder(car_parts_results, parts_output_folder)
|
89 |
+
|
90 |
+
for damage_results in damage_results_paths:
|
91 |
+
damage_output_folder = os.path.join(self.output_folder, 'damage')
|
92 |
+
self.copy_folder(damage_results, damage_output_folder)
|
93 |
+
|
94 |
+
file_processor = FileProcessor()
|
95 |
+
file_processor.process_output_folder(self.output_folder)
|
96 |
+
|
97 |
+
csv_generator = CSVGenerator(self.output_folder)
|
98 |
+
damage_data = csv_generator.process_files()
|
99 |
+
|
100 |
+
df = pd.read_csv(csv_generator.output_csv)
|
101 |
+
st.write("Damage Estimation Report")
|
102 |
+
st.dataframe(df)
|
103 |
+
|
104 |
+
# Generate PDF report
|
105 |
+
pdf_report = PDFReportGenerator(csv_generator.output_csv, self.output_folder, 'damage_estimation_report.pdf')
|
106 |
+
pdf_report.generate_report()
|
107 |
+
|
108 |
+
# # Add download button for the CSV file
|
109 |
+
# csv_data = df.to_csv(index=False).encode('utf-8')
|
110 |
+
# st.download_button(
|
111 |
+
# label="Download CSV",
|
112 |
+
# data=csv_data,
|
113 |
+
# file_name='damage_estimation.csv',
|
114 |
+
# mime='text/csv',
|
115 |
+
# )
|
116 |
+
|
117 |
+
# Add download button for the PDF report
|
118 |
+
with open(pdf_report.pdf_path, 'rb') as f:
|
119 |
+
pdf_data = f.read()
|
120 |
+
st.download_button(
|
121 |
+
label="Download PDF Report",
|
122 |
+
data=pdf_data,
|
123 |
+
file_name='damage_estimation_report.pdf',
|
124 |
+
mime='application/pdf',
|
125 |
+
)
|
126 |
+
|
127 |
+
damage_df = pd.DataFrame(damage_data)
|
128 |
+
fig = px.pie(damage_df, values='coverage_percentage', names='damage_class', title='Total Damage Percentages by Type')
|
129 |
+
st.plotly_chart(fig)
|
130 |
+
|
131 |
+
st.success("Analysis Completed.")
|
132 |
+
|
133 |
+
elif uploaded_files:
|
134 |
+
st.error("Please upload exactly four images.")
|
135 |
+
|
136 |
+
if __name__ == "__main__":
|
137 |
+
CAR_PARTS_MODEL_PATH = 'model/car_parts.pt'
|
138 |
+
DAMAGE_MODEL_PATH = 'model/damage_4_class.pt'
|
139 |
+
OUTPUT_FOLDER = 'Output'
|
140 |
+
app = SegmentationApp(CAR_PARTS_MODEL_PATH, DAMAGE_MODEL_PATH, OUTPUT_FOLDER)
|
141 |
+
app.run()
|
fetch_original.py
ADDED
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from PIL import Image
|
3 |
+
|
4 |
+
class FileProcessor:
|
5 |
+
PARTS_NAMES = [
|
6 |
+
"Quarter-panel", "Front-wheel", "Back-window", "Trunk", "Front-door", "Rocker-panel",
|
7 |
+
"Grille", "Windshield", "Front-window", "Back-door", "Headlight", "Back-wheel",
|
8 |
+
"Back-windshield", "Hood", "Fender", "Tail-light", "License-plate", "Front-bumper",
|
9 |
+
"Back-bumper", "Mirror", "Roof"
|
10 |
+
]
|
11 |
+
|
12 |
+
DAMAGE_NAMES = [
|
13 |
+
"Broken part", "Scratch", "Dent", "Paint chip"
|
14 |
+
]
|
15 |
+
|
16 |
+
@staticmethod
|
17 |
+
def convert_normalized_to_original(coords, width, height):
|
18 |
+
original_coords = []
|
19 |
+
for i in range(0, len(coords), 2):
|
20 |
+
x = coords[i] * width
|
21 |
+
y = coords[i + 1] * height
|
22 |
+
original_coords.extend([x, y])
|
23 |
+
return original_coords
|
24 |
+
|
25 |
+
def process_file(self, file_path, class_names, image_size):
|
26 |
+
width, height = image_size
|
27 |
+
with open(file_path, "r") as f:
|
28 |
+
lines = f.readlines()
|
29 |
+
|
30 |
+
new_lines = []
|
31 |
+
for line in lines:
|
32 |
+
parts = line.strip().split()
|
33 |
+
class_index = int(parts[0])
|
34 |
+
coords = list(map(float, parts[1:]))
|
35 |
+
original_coords = self.convert_normalized_to_original(coords, width, height)
|
36 |
+
class_name = class_names[class_index]
|
37 |
+
new_line = f"{class_name} " + " ".join(map(str, original_coords)) + "\n"
|
38 |
+
new_lines.append(new_line)
|
39 |
+
|
40 |
+
with open(file_path, "w") as f:
|
41 |
+
f.writelines(new_lines)
|
42 |
+
|
43 |
+
def process_folder(self, folder_path, class_names):
|
44 |
+
labels_folder = os.path.join(folder_path, "labels")
|
45 |
+
for file in os.listdir(folder_path):
|
46 |
+
if file.endswith(".jpg"):
|
47 |
+
image_path = os.path.join(folder_path, file)
|
48 |
+
if os.path.exists(image_path):
|
49 |
+
image = Image.open(image_path)
|
50 |
+
image_size = image.size
|
51 |
+
txt_file = file.replace(".jpg", ".txt")
|
52 |
+
txt_file_path = os.path.join(labels_folder, txt_file)
|
53 |
+
if os.path.exists(txt_file_path):
|
54 |
+
self.process_file(txt_file_path, class_names, image_size)
|
55 |
+
else:
|
56 |
+
print(f"Text file {txt_file_path} not found.")
|
57 |
+
else:
|
58 |
+
print(f"Image {image_path} not found.")
|
59 |
+
|
60 |
+
def process_output_folder(self, output_folder):
|
61 |
+
parts_folder = os.path.join(output_folder, "parts")
|
62 |
+
damage_folder = os.path.join(output_folder, "damage")
|
63 |
+
|
64 |
+
self.process_folder(parts_folder, self.PARTS_NAMES)
|
65 |
+
self.process_folder(damage_folder, self.DAMAGE_NAMES)
|
generator.py
ADDED
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import csv
|
3 |
+
from Damage_calculation import DamageCalculator
|
4 |
+
|
5 |
+
class CSVGenerator:
|
6 |
+
def __init__(self, output_folder):
|
7 |
+
self.damage_folder = os.path.join(output_folder, 'damage/labels')
|
8 |
+
self.parts_folder = os.path.join(output_folder, 'parts/labels')
|
9 |
+
self.output_csv = os.path.join(output_folder, 'damage_estimation.csv')
|
10 |
+
self.calculator = DamageCalculator()
|
11 |
+
|
12 |
+
def process_files(self):
|
13 |
+
damage_files = sorted(os.listdir(self.damage_folder))
|
14 |
+
parts_files = sorted(os.listdir(self.parts_folder))
|
15 |
+
|
16 |
+
total_cost_min = 0
|
17 |
+
total_cost_max = 0
|
18 |
+
damage_data = []
|
19 |
+
|
20 |
+
with open(self.output_csv, 'w', newline='') as csvfile:
|
21 |
+
fieldnames = ['Car Parts', 'Damage Type', 'Damage Count', 'Severity', 'Estimated Cost Range']
|
22 |
+
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
|
23 |
+
writer.writeheader()
|
24 |
+
|
25 |
+
for damage_file, parts_file in zip(damage_files, parts_files):
|
26 |
+
damage_path = os.path.join(self.damage_folder, damage_file)
|
27 |
+
parts_path = os.path.join(self.parts_folder, parts_file)
|
28 |
+
|
29 |
+
damage_classes, damage_polygons = self.calculator.parse_coordinates(damage_path)
|
30 |
+
part_classes, part_polygons = self.calculator.parse_coordinates(parts_path)
|
31 |
+
|
32 |
+
damage_polygons = list(zip(damage_classes, damage_polygons))
|
33 |
+
part_polygons = list(zip(part_classes, part_polygons))
|
34 |
+
|
35 |
+
intersection_info = self.calculator.calculate_intersection_area(damage_polygons, part_polygons)
|
36 |
+
|
37 |
+
for info in intersection_info:
|
38 |
+
writer.writerow({
|
39 |
+
'Car Parts': info['part_class'],
|
40 |
+
'Damage Type': info['damage_class'],
|
41 |
+
'Damage Count': info['count'],
|
42 |
+
'Severity': info['severity'],
|
43 |
+
'Estimated Cost Range': f"Rs. {info['cost_min']}-{info['cost_max']}"
|
44 |
+
})
|
45 |
+
total_cost_min += info['cost_min']
|
46 |
+
total_cost_max += info['cost_max']
|
47 |
+
|
48 |
+
damage_data.append({
|
49 |
+
'damage_class': info['damage_class'],
|
50 |
+
'part_class': info['part_class'],
|
51 |
+
'coverage_percentage': info['coverage_percentage']
|
52 |
+
})
|
53 |
+
|
54 |
+
writer.writerow({
|
55 |
+
'Car Parts': 'Total',
|
56 |
+
'Damage Type': '',
|
57 |
+
'Damage Count': '',
|
58 |
+
'Severity': '',
|
59 |
+
'Estimated Cost Range': f"Rs. {total_cost_min}-{total_cost_max}"
|
60 |
+
})
|
61 |
+
|
62 |
+
return damage_data
|
63 |
+
|
64 |
+
def main(output_folder):
|
65 |
+
generator = CSVGenerator(output_folder)
|
66 |
+
generator.process_files()
|
67 |
+
|
68 |
+
if __name__ == "__main__":
|
69 |
+
output_folder = 'Output'
|
70 |
+
main(output_folder)
|
model/Med_seg_test.pt
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:075c2d330e905ef4a8a6f32807a0cbbc1b58176f157a199820f2052000f57ed1
|
3 |
+
size 54825429
|
model/car_parts.pt
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:9602172fa683e2d684d34bbd6fe413f56b8ff400b0c0f10e242d5dd479bb44d2
|
3 |
+
size 47568240
|
model/damage.pt
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:00682f355ee0875a0b88837019c355f2a778d575ef977cfd47f3b142d4b5b386
|
3 |
+
size 6797549
|
model/damage_4_class.pt
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:b9804361bf6d835fae7fcf3f06f83aa4bcadd40d7203c8397a987f6bae64f67d
|
3 |
+
size 23857261
|
model/damage_nano_416.pt
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:ee47af79719278456796069f6ec8a3d38091a17936645bd0f1542e5075c2aac4
|
3 |
+
size 6751149
|
pdf_report.py
ADDED
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import pandas as pd
|
3 |
+
from reportlab.pdfgen import canvas
|
4 |
+
from reportlab.lib.pagesizes import letter
|
5 |
+
import tempfile
|
6 |
+
import matplotlib.pyplot as plt
|
7 |
+
from reportlab.lib import colors
|
8 |
+
from Damage_calculation import DamageCalculator
|
9 |
+
from reportlab.platypus import Table, TableStyle
|
10 |
+
|
11 |
+
class PDFReportGenerator:
|
12 |
+
def __init__(self, output_csv, output_folder, pdf_path):
|
13 |
+
self.output_csv = output_csv
|
14 |
+
self.damage_folder = os.path.join(output_folder, 'damage/labels')
|
15 |
+
self.parts_folder = os.path.join(output_folder, 'parts/labels')
|
16 |
+
self.pdf_path = pdf_path
|
17 |
+
self.calculator = DamageCalculator()
|
18 |
+
|
19 |
+
def create_pie_chart(self, part_class, damage_summary):
|
20 |
+
part_damage = damage_summary.get(part_class, {})
|
21 |
+
if not part_damage:
|
22 |
+
return None
|
23 |
+
|
24 |
+
damage_labels = list(part_damage['damage_types'].keys()) + ['Undamaged']
|
25 |
+
damage_values = list(part_damage['damage_types'].values()) + [part_damage['undamaged']]
|
26 |
+
|
27 |
+
fig, ax = plt.subplots(figsize=(6, 6)) # Adjust the figure size to ensure the pie is circular
|
28 |
+
ax.pie(damage_values, labels=damage_labels, autopct='%1.1f%%', startangle=140, textprops={'fontsize': 14}) # Increase font size
|
29 |
+
ax.axis('equal')
|
30 |
+
|
31 |
+
img_temp_path = tempfile.mktemp(suffix=".png")
|
32 |
+
plt.savefig(img_temp_path, bbox_inches='tight') # Ensure the pie chart is properly sized
|
33 |
+
plt.close(fig)
|
34 |
+
return img_temp_path
|
35 |
+
|
36 |
+
def add_table_to_pdf(self, pdf, dataframe, x_position, y_position):
|
37 |
+
# Replace NaN values with '-'
|
38 |
+
dataframe = dataframe.fillna('-')
|
39 |
+
|
40 |
+
data = [dataframe.columns.to_list()] + dataframe.values.tolist()
|
41 |
+
table = Table(data)
|
42 |
+
|
43 |
+
# Define table style
|
44 |
+
style = TableStyle([
|
45 |
+
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
|
46 |
+
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
|
47 |
+
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
|
48 |
+
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
49 |
+
('FONTSIZE', (0, 0), (-1, 0), 12),
|
50 |
+
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
51 |
+
('BACKGROUND', (0, 1), (-1, -1), colors.beige),
|
52 |
+
('GRID', (0, 0), (-1, -1), 1, colors.black)
|
53 |
+
])
|
54 |
+
|
55 |
+
table.setStyle(style)
|
56 |
+
|
57 |
+
# Convert the table to a PDF flowable and draw it on the canvas
|
58 |
+
table.wrapOn(pdf, x_position, y_position)
|
59 |
+
table.drawOn(pdf, x_position, y_position)
|
60 |
+
|
61 |
+
|
62 |
+
def generate_report(self):
|
63 |
+
df = pd.read_csv(self.output_csv)
|
64 |
+
car_parts = df['Car Parts'].unique()
|
65 |
+
|
66 |
+
# Initialize damage summary dictionary
|
67 |
+
damage_summary = {}
|
68 |
+
|
69 |
+
# Populate damage and parts file paths
|
70 |
+
damage_files = sorted(os.listdir(self.damage_folder))
|
71 |
+
parts_files = sorted(os.listdir(self.parts_folder))
|
72 |
+
|
73 |
+
# Iterate over the damage and parts files
|
74 |
+
for damage_file, parts_file in zip(damage_files, parts_files):
|
75 |
+
damage_path = os.path.join(self.damage_folder, damage_file)
|
76 |
+
parts_path = os.path.join(self.parts_folder, parts_file)
|
77 |
+
|
78 |
+
damage_classes, damage_polygons = self.calculator.parse_coordinates(damage_path)
|
79 |
+
part_classes, part_polygons = self.calculator.parse_coordinates(parts_path)
|
80 |
+
|
81 |
+
damage_polygons = list(zip(damage_classes, damage_polygons))
|
82 |
+
part_polygons = list(zip(part_classes, part_polygons))
|
83 |
+
|
84 |
+
part_summary = self.calculator.summarize_damage_by_part(damage_polygons, part_polygons)
|
85 |
+
for part, summary in part_summary.items():
|
86 |
+
if part not in damage_summary:
|
87 |
+
damage_summary[part] = summary
|
88 |
+
else:
|
89 |
+
for damage_type, percentage in summary['damage_types'].items():
|
90 |
+
if damage_type not in damage_summary[part]['damage_types']:
|
91 |
+
damage_summary[part]['damage_types'][damage_type] = 0
|
92 |
+
damage_summary[part]['damage_types'][damage_type] += percentage
|
93 |
+
damage_summary[part]['undamaged'] += summary['undamaged']
|
94 |
+
|
95 |
+
pdf = canvas.Canvas(self.pdf_path, pagesize=letter)
|
96 |
+
pdf.setTitle("Car Damage Estimation Report")
|
97 |
+
|
98 |
+
width, height = letter
|
99 |
+
y_position = height - 40
|
100 |
+
|
101 |
+
for part_class in car_parts:
|
102 |
+
if part_class == 'Total':
|
103 |
+
continue
|
104 |
+
|
105 |
+
pdf.setFont("Helvetica-Bold", 12)
|
106 |
+
pdf.drawString(30, y_position, f'Damage Report for {part_class}')
|
107 |
+
y_position -= 20
|
108 |
+
|
109 |
+
pie_chart_path = self.create_pie_chart(part_class, damage_summary)
|
110 |
+
if pie_chart_path:
|
111 |
+
pdf.drawImage(pie_chart_path, 50, y_position - 300, width=275, height=275) # Adjust size to maintain aspect ratio
|
112 |
+
y_position -= 320
|
113 |
+
|
114 |
+
# Clean up the temporary file
|
115 |
+
os.remove(pie_chart_path)
|
116 |
+
|
117 |
+
if y_position < 100:
|
118 |
+
pdf.showPage()
|
119 |
+
y_position = height - 40
|
120 |
+
|
121 |
+
# Add title above the table
|
122 |
+
y_position -= 40
|
123 |
+
pdf.setFont("Helvetica-Bold", 14)
|
124 |
+
pdf.drawString(50, y_position, "Cost Breakdown Table")
|
125 |
+
y_position -= 200
|
126 |
+
|
127 |
+
# Add table from CSV to the PDF
|
128 |
+
self.add_table_to_pdf(pdf, df, 30, y_position-100)
|
129 |
+
|
130 |
+
pdf.save()
|
predict_testing/Car_damages_1065.png
ADDED
![]() |
predict_testing/Car_damages_1070.png
ADDED
![]() |
predict_testing/Car_damages_1072.png
ADDED
![]() |
predict_testing/Car_damages_1185.png
ADDED
![]() |
Git LFS Details
|
predict_testing/Car_damages_1219.png
ADDED
![]() |
Git LFS Details
|
predict_testing/Car_damages_251.png
ADDED
![]() |
Git LFS Details
|
predict_testing/Car_damages_760.png
ADDED
![]() |
predict_testing/Car_damages_778.png
ADDED
![]() |
predict_testing/Car_damages_788.png
ADDED
![]() |
predict_testing/Car_damages_991.png
ADDED
![]() |
predict_testing/back.webp
ADDED
![]() |
predict_testing/front.webp
ADDED
![]() |
predict_testing/left.webp
ADDED
![]() |
predict_testing/right.webp
ADDED
![]() |