import streamlit as st import cv2 import numpy as np from pathlib import Path import tempfile import os import plotly.express as px import pytesseract import re from ultralytics import YOLO # Colors for labels LABEL_COLORS = { "Circuit": (37, 255, 76), # Green "Fuse": (255, 76, 76), # Red "Missing Fuse": (255, 204, 0), # Yellow "Missing Circuit": (0, 128, 255) # Blue } def test_trained_model(model_path, test_image_path, conf_threshold=0.5): model = YOLO(model_path) test_image_path = Path(test_image_path) if not test_image_path.exists(): st.error(f"No image found at {test_image_path}") return None results = model(str(test_image_path), conf=conf_threshold, verbose=False) result = results[0] image = cv2.imread(str(test_image_path)) if result.boxes is not None: boxes = result.boxes.xyxy.cpu().numpy() classes = result.boxes.cls.cpu().numpy().astype(int) for box, cls_id in zip(boxes, classes): x1, y1, x2, y2 = map(int, box) label = model.names[cls_id] color = LABEL_COLORS.get(label, (255, 255, 255)) # fallback white cv2.rectangle(image, (x1, y1), (x2, y2), color, 2) cv2.putText(image, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2) image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) st.image(image_rgb, caption=f"{test_image_path.name} - Detection with Labels", use_column_width=True) # Legend st.markdown("### 🏷 Legend (Bounding Box Colors)") for label, color in LABEL_COLORS.items(): r, g, b = color hex_color = f'#{r:02x}{g:02x}{b:02x}' st.markdown(f" {label}", unsafe_allow_html=True) detections = [model.names[int(cls)] for cls in result.boxes.cls] return detections def count_circuit_components(detections): target_labels = ['Circuit', 'Fuse', 'Missing Fuse', 'Missing Circuit'] component_counts = {} for label in target_labels: count = sum(1 for detection in detections if detection.lower().replace('_', ' ') == label.lower()) component_counts[label] = count return component_counts def display_component_summary(component_counts, image_name): st.write(f"\n{'=' * 50}") st.write(f"COMPONENT SUMMARY FOR: {image_name}") st.write(f"{'=' * 50}") for component, count in component_counts.items(): st.write(f"{component:15}: {count:3d}") st.write(f"{'-' * 25}") def extract_temperatures_from_image(image_path): st.subheader("📋 Temperature Report") image = cv2.imread(image_path) if image is None: st.error("Error loading image for OCR.") return None hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) lower_white = np.array([0, 0, 200]) upper_white = np.array([180, 50, 255]) white_mask = cv2.inRange(hsv_image, lower_white, upper_white) white_text_image = cv2.bitwise_and(image, image, mask=white_mask) white_text_gray = cv2.cvtColor(white_text_image, cv2.COLOR_BGR2GRAY) ocr_text = pytesseract.image_to_string(white_text_gray) ocr_text = re.sub(r'(\d+)\s(\d)°C', r'\1.\2°C', ocr_text) temps_found = re.findall(r'(\d{1,3}\.\d)\s*°C', ocr_text) temps_found = sorted([float(t) for t in temps_found]) if len(temps_found) == 0: st.warning("No temperature values detected.") return None highest_temp = max(temps_found) lowest_temp = min(temps_found) if len(temps_found) >= 3: ambient_temp = temps_found[1] elif len(temps_found) == 2: ambient_temp = sum(temps_found) / 2 else: ambient_temp = temps_found[0] st.write("Detected Temperatures:") st.write(f"- Highest Temp: {highest_temp:.2f} °C") st.write(f"- Lowest Temp: {lowest_temp:.2f} °C") st.write(f"- Ambient Temp: {ambient_temp:.2f} °C") return { "highest": highest_temp, "lowest": lowest_temp, "ambient": ambient_temp, } def plot_hover_temperature(image_path, temp_min=0.0, temp_max=100.0): img = cv2.imread(image_path) img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) red_channel = img_rgb[:, :, 0] def map_temp(val): return round((val / 255.0) * (temp_max - temp_min) + temp_min, 2) temp_map = np.vectorize(map_temp)(red_channel) fig = px.imshow( temp_map, color_continuous_scale="Hot", labels={'x': 'X', 'y': 'Y', 'color': 'Temperature (°C)'}, title=f"Temperature Map (Hover) - Range: {temp_min}°C to {temp_max}°C" ) fig.update_layout(dragmode=False, margin=dict(l=20, r=20, t=40, b=20), height=600) fig.update_traces(hovertemplate="X: %{x}
Y: %{y}
Temp: %{z:.2f} °C") st.plotly_chart(fig, use_container_width=True) def analyze_thermal_circuit_image(model_path, test_image_path, conf_threshold=0.5): st.write("Starting thermal circuit analysis...") detections = test_trained_model(model_path, test_image_path, conf_threshold) if detections is None: return component_counts = count_circuit_components(detections) image_name = Path(test_image_path).name display_component_summary(component_counts, image_name) return component_counts def extract_ambient_temperature(image_path): image = cv2.imread(image_path) hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) white_mask = cv2.inRange(hsv_image, np.array([0, 0, 200]), np.array([180, 50, 255])) text_image = cv2.bitwise_and(image, image, mask=white_mask) gray = cv2.cvtColor(text_image, cv2.COLOR_BGR2GRAY) text = pytesseract.image_to_string(gray) temps = re.findall(r'(\d{1,3}\.\d)\s*°C', text) if not temps: return None temps = sorted([float(t) for t in temps]) return temps[1] if len(temps) >= 3 else sum(temps) / len(temps) def detect_circuit_boxes(model_path, image_path, conf_threshold=0.5): model = YOLO(model_path) results = model(image_path, conf=conf_threshold)[0] boxes = [] if results.boxes is not None: for i, cls in enumerate(results.boxes.cls): label = model.names[int(cls)] if label.lower() == "circuit": box = results.boxes.xyxy[i].cpu().numpy().astype(int) boxes.append(box) return boxes def draw_hotspot_boxes(image_path, boxes, ambient_temp, min_temp=0, max_temp=100): image = cv2.imread(image_path) red_channel = image[:, :, 2] temp_map = (red_channel / 255.0) * (max_temp - min_temp) + min_temp threshold = ambient_temp + 5 mask = np.zeros_like(red_channel, dtype=np.uint8) annotated = image.copy() for box in boxes: x1, y1, x2, y2 = box region_temp = temp_map[y1:y2, x1:x2] if np.mean(region_temp) > threshold: cv2.rectangle(annotated, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.putText(annotated, f"HOT > {threshold:.1f}°C", (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1) mask[y1:y2, x1:x2] = 255 return annotated, mask, temp_map def main(): st.set_page_config(layout="wide") st.title("Thermal Circuit & Hotspot Analysis") uploaded_file = st.file_uploader("Upload Thermal Image (.jpg/.png)", type=["jpg", "jpeg", "png"], key="upload") if uploaded_file: with tempfile.NamedTemporaryFile(delete=False, suffix=".jpg") as tmp_file: tmp_file.write(uploaded_file.getvalue()) tmp_path = tmp_file.name st.image(cv2.cvtColor(cv2.imread(tmp_path), cv2.COLOR_BGR2RGB), caption="Uploaded Thermal Image", use_column_width=True) conf_threshold = st.slider("Detection Confidence Threshold", 0.0, 1.0, 0.5, 0.01, key="conf_thresh") if st.button("Analyze Image", key="analyze"): model_path = "best.pt" analyze_thermal_circuit_image(model_path, tmp_path, conf_threshold) temps = extract_temperatures_from_image(tmp_path) if temps: plot_hover_temperature(tmp_path, temp_min=temps["lowest"], temp_max=temps["highest"]) else: st.info("Using default temperature range (0°C to 100°C) for hover map.") plot_hover_temperature(tmp_path) ambient = extract_ambient_temperature(tmp_path) boxes = detect_circuit_boxes(model_path, tmp_path) if not boxes: st.warning("No circuit components detected.") else: st.success(f"Detected {len(boxes)} Circuit Regions") st.subheader("Mapping Hotspots...") annotated, mask, temp_map = draw_hotspot_boxes(tmp_path, boxes, ambient) st.image(cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB), caption="Annotated Hot Circuits", use_column_width=True) with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as mask_file: cv2.imwrite(mask_file.name, mask) st.download_button("Download Hotspot Mask", data=open(mask_file.name, "rb"), file_name="hotspot_mask.png") os.unlink(tmp_path) if __name__ == "__main__": main()