CoolFace
Apppublic

dmncjsph/roof-rust-system

sourceHugging Faceapache-2.0updated 5mo agoView on Hugging Face
0likes
server.py249 linesDownload Raw Back to root
1from pathlib import Path2import io3import os4 5from flask import Flask, request, jsonify, render_template6import torch7import torch.nn as nn8from PIL import Image, UnidentifiedImageError9from torchvision.models import efficientnet_b2, EfficientNet_B2_Weights10 11BASE_DIR = Path(__file__).resolve().parent12 13ROOF_FILTER_MODEL_PATH = BASE_DIR / "efficientnet_b2_roof_filter_best.pth"14RUST_MODEL_PATH = BASE_DIR / "efficientnet_b2_roofrust_best_b2_mixup.pth"15 16app = Flask(17    __name__,18    template_folder=str(BASE_DIR),19    static_folder=str(BASE_DIR),20    static_url_path=""21)22 23device = torch.device("cuda" if torch.cuda.is_available() else "cpu")24print("Using device:", device)25 26ALLOWED_EXTENSIONS = {"jpg", "jpeg", "png", "bmp", "webp"}27 28ROOF_THRESHOLD = 70.029LOW_CONFIDENCE_THRESHOLD = 70.030REJECT_THRESHOLD = 40.031 32DISPLAY_ORDER = [33    "No Rust",34    "Slightly Visible Rust",35    "Visible Rust",36    "Heavy Visible Rust"37]38 39CLASS_DETAILS = {40    "No Rust": {41        "iso_basis": "Closest to Grade A, where the steel surface is largely covered with adhering mill scale and has little or no rust.",42        "recommendation": [43            "Your roof looks in good condition.",44            "No immediate repair is needed.",45            "Keep the roof clean and dry.",46            "Check it regularly, especially after strong rain or storms.",47            "Apply protective coating during scheduled maintenance to help prevent future rust."48        ]49    },50    "Slightly Visible Rust": {51        "iso_basis": "Closest to Grade B, where rusting has begun and mill scale has started to flake.",52        "recommendation": [53            "Early signs of rust are starting to appear.",54            "Clean the affected area as soon as possible.",55            "Remove dirt, loose rust, and debris.",56            "Apply primer or protective paint to stop the rust from spreading.",57            "Monitor the area regularly to make sure the damage does not get worse."58        ]59    },60    "Visible Rust": {61        "iso_basis": "Closest to Grade C, where mill scale has rusted away or can be scraped off and slight pitting is visible.",62        "recommendation": [63            "Rust is already noticeable and should not be ignored.",64            "Schedule maintenance soon to avoid bigger damage.",65            "Clean and prepare the surface properly before repainting or recoating.",66            "Check nearby screws, joints, and fasteners because rust often spreads around these areas.",67            "If the rust covers a larger area, ask a roofing professional to inspect it."68        ]69    },70    "Heavy Visible Rust": {71        "iso_basis": "Closest to Grade D, where mill scale has rusted away and general pitting is visible.",72        "recommendation": [73            "The roof shows serious rust and may already be weakening.",74            "Urgent repair is recommended.",75            "Have the roof inspected for deep corrosion, holes, or possible structural damage.",76            "Heavy rust should be removed thoroughly before any recoating is done.",77            "If the damage is severe, some roof parts may need professional restoration or replacement."78        ]79    }80}81 82def allowed_file(filename):83    return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS84 85if not ROOF_FILTER_MODEL_PATH.exists():86    raise FileNotFoundError(f"Roof filter checkpoint not found: {ROOF_FILTER_MODEL_PATH}")87 88if not RUST_MODEL_PATH.exists():89    raise FileNotFoundError(f"Rust classifier checkpoint not found: {RUST_MODEL_PATH}")90 91preprocess = EfficientNet_B2_Weights.DEFAULT.transforms()92 93roof_checkpoint = torch.load(ROOF_FILTER_MODEL_PATH, map_location=device, weights_only=False)94roof_class_names = roof_checkpoint["class_names"]95roof_num_classes = len(roof_class_names)96 97roof_model = efficientnet_b2(weights=None)98roof_in_features = roof_model.classifier[1].in_features99roof_model.classifier[1] = nn.Linear(roof_in_features, roof_num_classes)100roof_model.load_state_dict(roof_checkpoint["model_state_dict"])101roof_model = roof_model.to(device)102roof_model.eval()103 104rust_checkpoint = torch.load(RUST_MODEL_PATH, map_location=device, weights_only=False)105rust_class_names = rust_checkpoint["class_names"]106rust_num_classes = len(rust_class_names)107 108rust_model = efficientnet_b2(weights=None)109rust_in_features = rust_model.classifier[1].in_features110rust_model.classifier[1] = nn.Linear(rust_in_features, rust_num_classes)111rust_model.load_state_dict(rust_checkpoint["model_state_dict"])112rust_model = rust_model.to(device)113rust_model.eval()114 115def run_roof_filter(image: Image.Image):116    image = image.convert("RGB")117    input_tensor = preprocess(image).unsqueeze(0).to(device)118 119    with torch.no_grad():120        outputs = roof_model(input_tensor)121        probs = torch.softmax(outputs[0], dim=0)122 123    result = {roof_class_names[i]: float(probs[i]) for i in range(roof_num_classes)}124    predicted_class = max(result, key=result.get)125    confidence = result[predicted_class] * 100126    return predicted_class, confidence, result127 128def run_rust_classifier(image: Image.Image):129    image = image.convert("RGB")130    input_tensor = preprocess(image).unsqueeze(0).to(device)131 132    with torch.no_grad():133        outputs = rust_model(input_tensor)134        probs = torch.softmax(outputs[0], dim=0)135 136    raw_result = {rust_class_names[i]: float(probs[i]) for i in range(rust_num_classes)}137    final_class = max(raw_result, key=raw_result.get)138    confidence = raw_result[final_class] * 100139 140    ordered_result = {cls: raw_result.get(cls, 0.0) for cls in DISPLAY_ORDER}141 142    if confidence < REJECT_THRESHOLD:143        return {144            "accepted": False,145            "low_confidence": False,146            "error": "The image is a roof, but the rust severity result is too uncertain. Please upload a clearer roof image."147        }148 149    if confidence < LOW_CONFIDENCE_THRESHOLD:150        return {151            "accepted": True,152            "low_confidence": True,153            "warning": "The image was detected as a roof, but the rust severity prediction is not highly confident. Please review the result carefully or try another clearer image.",154            "predictions": ordered_result,155            "final_classification": final_class,156            "confidence": round(confidence, 2),157            "iso_basis": CLASS_DETAILS[final_class]["iso_basis"],158            "recommendation": CLASS_DETAILS[final_class]["recommendation"]159        }160 161    return {162        "accepted": True,163        "low_confidence": False,164        "warning": "",165        "predictions": ordered_result,166        "final_classification": final_class,167        "confidence": round(confidence, 2),168        "iso_basis": CLASS_DETAILS[final_class]["iso_basis"],169        "recommendation": CLASS_DETAILS[final_class]["recommendation"]170    }171 172@app.route("/")173def home():174    return render_template("home.html")175 176@app.route("/analyzer")177def analyzer():178    return render_template("analyzer.html")179 180@app.route("/about-team")181def about_team():182    return render_template("about_team.html")183 184@app.route("/predict", methods=["POST"])185def predict():186    if "file" not in request.files:187        return jsonify({"error": "No file uploaded"}), 400188 189    file = request.files["file"]190 191    if file.filename == "":192        return jsonify({"error": "Empty filename"}), 400193 194    if not allowed_file(file.filename):195        return jsonify({196            "error": "Unsupported file type. Please upload JPG, JPEG, PNG, BMP, or WEBP."197        }), 400198 199    try:200        image_bytes = file.read()201 202        if not image_bytes:203            return jsonify({"error": "Uploaded file is empty"}), 400204 205        image_stream = io.BytesIO(image_bytes)206 207        test_img = Image.open(image_stream)208        test_img.verify()209 210        image_stream.seek(0)211        image = Image.open(image_stream).convert("RGB")212 213        roof_class, roof_confidence, _ = run_roof_filter(image)214 215        roof_label_map = {name.lower(): name for name in roof_class_names}216        predicted_roof_label = roof_label_map.get("roof", "roof")217 218        if roof_class != predicted_roof_label or roof_confidence < ROOF_THRESHOLD:219            return jsonify({220                "error": "Please upload a roof image only. The uploaded image was detected as non-roof or too uncertain.",221                "roof_filter_prediction": roof_class,222                "roof_filter_confidence": round(roof_confidence, 2)223            }), 400224 225        rust_result = run_rust_classifier(image)226 227        if not rust_result["accepted"]:228            return jsonify({229                "error": rust_result["error"],230                "roof_filter_prediction": roof_class,231                "roof_filter_confidence": round(roof_confidence, 2)232            }), 400233 234        rust_result["roof_filter_prediction"] = roof_class235        rust_result["roof_filter_confidence"] = round(roof_confidence, 2)236 237        return jsonify(rust_result)238 239    except UnidentifiedImageError:240        return jsonify({241            "error": "Invalid or corrupted image file. Please upload a valid image."242        }), 400243 244    except Exception as e:245        return jsonify({"error": str(e)}), 500246 247if __name__ == "__main__":248    port = int(os.environ.get("PORT", 7860))249    app.run(host="0.0.0.0", port=port, debug=False)