Spaces:
Sleeping
Sleeping
| 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"<span style='color:{hex_color}; font-weight:bold;'>■</span> {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}<br>Y: %{y}<br>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() | |