nithishbasireddy commited on
Commit
63fcffc
·
verified ·
1 Parent(s): 8c5546c

Upload src/pipeline/visualization.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. src/pipeline/visualization.py +270 -0
src/pipeline/visualization.py ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Visualization module for EL defect analysis.
3
+
4
+ Creates overlay images with color-coded defect masks:
5
+ - Crack → Blue (visible against bright cell regions)
6
+ - Dark → Red (contrast with bright areas)
7
+ - Cross → Cyan (distinguishable from regular cracks)
8
+ - Busbar → Green (feature, not defect)
9
+
10
+ All overlays use alpha blending so original image detail remains visible.
11
+ Handles resize alignment to prevent mask/image size mismatches.
12
+ """
13
+
14
+ import cv2
15
+ import numpy as np
16
+ from typing import Dict, List, Tuple, Optional
17
+
18
+
19
+ # Color scheme (BGR for OpenCV)
20
+ DEFECT_COLORS_BGR = {
21
+ "background": (0, 0, 0), # Black (not drawn)
22
+ "dark": (0, 0, 255), # Red
23
+ "crack": (255, 0, 0), # Blue
24
+ "cross": (255, 255, 0), # Cyan
25
+ "busbar": (0, 255, 0), # Green
26
+ }
27
+
28
+ # RGB for matplotlib/PIL/Streamlit
29
+ DEFECT_COLORS_RGB = {
30
+ "background": (0, 0, 0),
31
+ "dark": (255, 0, 0), # Red
32
+ "crack": (0, 0, 255), # Blue
33
+ "cross": (0, 255, 255), # Cyan
34
+ "busbar": (0, 255, 0), # Green
35
+ }
36
+
37
+ CLASS_NAMES = ["background", "dark", "crack", "cross", "busbar"]
38
+
39
+
40
+ def create_overlay(
41
+ image: np.ndarray,
42
+ mask: np.ndarray,
43
+ alpha: float = 0.4,
44
+ show_background: bool = False,
45
+ ) -> np.ndarray:
46
+ """
47
+ Create colored overlay of segmentation mask on image.
48
+
49
+ Args:
50
+ image: Grayscale or BGR image (any size)
51
+ mask: Class index mask (any size, will be resized to match image)
52
+ alpha: Overlay transparency (0 = fully transparent, 1 = fully opaque)
53
+ show_background: If True, also color background class
54
+
55
+ Returns:
56
+ BGR image with colored overlay
57
+ """
58
+ # Ensure image is BGR
59
+ if image.ndim == 2:
60
+ vis = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
61
+ else:
62
+ vis = image.copy()
63
+
64
+ # Ensure uint8
65
+ if vis.dtype != np.uint8:
66
+ if vis.max() <= 1.0:
67
+ vis = (vis * 255).astype(np.uint8)
68
+ else:
69
+ vis = vis.astype(np.uint8)
70
+
71
+ h, w = vis.shape[:2]
72
+
73
+ # Resize mask to match image (CRITICAL: use NEAREST to preserve labels)
74
+ if mask.shape[:2] != (h, w):
75
+ mask = cv2.resize(mask, (w, h), interpolation=cv2.INTER_NEAREST)
76
+
77
+ # Create color overlay
78
+ overlay = vis.copy()
79
+
80
+ for class_idx, class_name in enumerate(CLASS_NAMES):
81
+ if class_idx == 0 and not show_background:
82
+ continue
83
+
84
+ color = DEFECT_COLORS_BGR[class_name]
85
+ class_mask = mask == class_idx
86
+
87
+ if class_mask.any():
88
+ overlay[class_mask] = color
89
+
90
+ # Alpha blend
91
+ result = cv2.addWeighted(vis, 1 - alpha, overlay, alpha, 0)
92
+
93
+ return result
94
+
95
+
96
+ def create_class_overlay(
97
+ image: np.ndarray,
98
+ mask: np.ndarray,
99
+ class_name: str,
100
+ alpha: float = 0.5,
101
+ color: Optional[Tuple[int, int, int]] = None,
102
+ ) -> np.ndarray:
103
+ """
104
+ Create overlay for a single class.
105
+
106
+ Args:
107
+ image: Grayscale or BGR image
108
+ mask: Binary mask for one class
109
+ class_name: For color lookup
110
+ alpha: Overlay transparency
111
+ color: Override color (BGR)
112
+ """
113
+ if image.ndim == 2:
114
+ vis = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
115
+ else:
116
+ vis = image.copy()
117
+
118
+ if vis.dtype != np.uint8:
119
+ vis = (vis * 255).astype(np.uint8) if vis.max() <= 1 else vis.astype(np.uint8)
120
+
121
+ h, w = vis.shape[:2]
122
+ if mask.shape[:2] != (h, w):
123
+ mask = cv2.resize(mask, (w, h), interpolation=cv2.INTER_NEAREST)
124
+
125
+ if color is None:
126
+ color = DEFECT_COLORS_BGR.get(class_name, (255, 255, 255))
127
+
128
+ overlay = vis.copy()
129
+ overlay[mask > 0] = color
130
+
131
+ return cv2.addWeighted(vis, 1 - alpha, overlay, alpha, 0)
132
+
133
+
134
+ def create_color_mask(
135
+ mask: np.ndarray,
136
+ include_background: bool = False,
137
+ ) -> np.ndarray:
138
+ """
139
+ Convert class index mask to RGB color visualization.
140
+
141
+ Returns:
142
+ (H, W, 3) uint8 RGB image
143
+ """
144
+ h, w = mask.shape[:2]
145
+ color_img = np.zeros((h, w, 3), dtype=np.uint8)
146
+
147
+ for class_idx, class_name in enumerate(CLASS_NAMES):
148
+ if class_idx == 0 and not include_background:
149
+ continue
150
+
151
+ color = DEFECT_COLORS_RGB[class_name]
152
+ color_img[mask == class_idx] = color
153
+
154
+ return color_img
155
+
156
+
157
+ def draw_cell_results(
158
+ image: np.ndarray,
159
+ cell_results: List[dict],
160
+ cells: list,
161
+ ) -> np.ndarray:
162
+ """
163
+ Draw cell analysis results on module image.
164
+
165
+ Shows per-cell:
166
+ - Bounding box (green = PASS, red = FAIL)
167
+ - Cell ID
168
+ - Defect score
169
+ """
170
+ if image.ndim == 2:
171
+ vis = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
172
+ else:
173
+ vis = image.copy()
174
+
175
+ if vis.dtype != np.uint8:
176
+ vis = (vis * 255).astype(np.uint8) if vis.max() <= 1 else vis.astype(np.uint8)
177
+
178
+ for cell_info, result in zip(cells, cell_results):
179
+ y1, x1, y2, x2 = cell_info.bbox
180
+ score = result.get("defect_score", 0)
181
+
182
+ # Color: green for good, yellow for moderate, red for bad
183
+ if score < 25:
184
+ color = (0, 255, 0) # Green
185
+ elif score < 50:
186
+ color = (0, 255, 255) # Yellow
187
+ else:
188
+ color = (0, 0, 255) # Red
189
+
190
+ cv2.rectangle(vis, (x1, y1), (x2, y2), color, 2)
191
+
192
+ # Label
193
+ label = f"C{cell_info.cell_id}: {score:.0f}"
194
+ cv2.putText(
195
+ vis, label, (x1 + 2, y1 + 15),
196
+ cv2.FONT_HERSHEY_SIMPLEX, 0.4, color, 1
197
+ )
198
+
199
+ return vis
200
+
201
+
202
+ def create_summary_image(
203
+ original: np.ndarray,
204
+ overlay: np.ndarray,
205
+ mask_color: np.ndarray,
206
+ decision: str,
207
+ score: float,
208
+ ) -> np.ndarray:
209
+ """
210
+ Create a summary image with original, overlay, and color mask side by side.
211
+
212
+ Returns:
213
+ (H, W*3, 3) BGR image with all three panels
214
+ """
215
+ # Ensure all are BGR
216
+ panels = []
217
+ for img in [original, overlay, mask_color]:
218
+ if img.ndim == 2:
219
+ img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
220
+ if img.dtype != np.uint8:
221
+ img = (img * 255).astype(np.uint8) if img.max() <= 1 else img.astype(np.uint8)
222
+ panels.append(img)
223
+
224
+ # Resize to same height
225
+ target_h = 400
226
+ resized = []
227
+ for p in panels:
228
+ scale = target_h / p.shape[0]
229
+ new_w = int(p.shape[1] * scale)
230
+ resized.append(cv2.resize(p, (new_w, target_h)))
231
+
232
+ # Concatenate horizontally
233
+ summary = np.hstack(resized)
234
+
235
+ # Add decision text
236
+ color = (0, 255, 0) if decision == "PASS" else (0, 0, 255)
237
+ text = f"{decision} (Score: {score:.1f})"
238
+ cv2.putText(
239
+ summary, text, (10, 30),
240
+ cv2.FONT_HERSHEY_SIMPLEX, 1.0, color, 2
241
+ )
242
+
243
+ return summary
244
+
245
+
246
+ def create_legend(height: int = 400, width: int = 200) -> np.ndarray:
247
+ """Create a color legend for defect classes."""
248
+ legend = np.ones((height, width, 3), dtype=np.uint8) * 255
249
+
250
+ y_offset = 30
251
+ for class_name in CLASS_NAMES[1:]: # Skip background
252
+ color = DEFECT_COLORS_BGR[class_name]
253
+
254
+ # Color swatch
255
+ cv2.rectangle(
256
+ legend, (10, y_offset), (40, y_offset + 20), color, -1
257
+ )
258
+ cv2.rectangle(
259
+ legend, (10, y_offset), (40, y_offset + 20), (0, 0, 0), 1
260
+ )
261
+
262
+ # Label
263
+ cv2.putText(
264
+ legend, class_name.capitalize(), (50, y_offset + 15),
265
+ cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1
266
+ )
267
+
268
+ y_offset += 35
269
+
270
+ return legend