dharak003 commited on
Commit
dba26ff
·
verified ·
1 Parent(s): e1e7ef9

Upload 24 files

Browse files
.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

  • SHA256: 2efcda8a5d6cc3852e4b77211af39b392a128725c552cc6afb135b70fa92734d
  • Pointer size: 132 Bytes
  • Size of remote file: 1.04 MB
predict_testing/Car_damages_1219.png ADDED

Git LFS Details

  • SHA256: 4a75fafe9b0677cd36d3b72382ea3cdd018925a71a15dc06244ef4558ae65368
  • Pointer size: 132 Bytes
  • Size of remote file: 1.01 MB
predict_testing/Car_damages_251.png ADDED

Git LFS Details

  • SHA256: 6b7428ee740ff3d3b2a5798e2563df3d52756e0c3eb8c48ee334e4043e43d643
  • Pointer size: 132 Bytes
  • Size of remote file: 1.2 MB
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