File size: 9,280 Bytes
7c8b3aa
68acfb6
 
 
a419469
8ae9faf
 
68acfb6
cf2b6fd
68acfb6
245f5a7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cf2b6fd
fe926b7
4a65ba6
8ae9faf
96ad7d9
 
 
 
 
 
 
 
cf2b6fd
68acfb6
 
8ae9faf
245f5a7
96ad7d9
 
245f5a7
96ad7d9
 
 
245f5a7
96ad7d9
 
245f5a7
 
 
 
 
 
cf2b6fd
63782d6
 
cf2b6fd
8ae9faf
 
 
68acfb6
96ad7d9
 
 
8ae9faf
 
 
 
68acfb6
8ae9faf
 
 
 
cf2b6fd
96ad7d9
 
 
 
 
 
5be1df3
 
e0a018b
5f66e0b
96ad7d9
5f66e0b
 
 
 
 
 
 
68acfb6
8ae9faf
cf2b6fd
96ad7d9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cf2b6fd
96ad7d9
 
 
 
 
 
 
 
 
 
 
f728bc5
cf2b6fd
f728bc5
 
 
 
 
 
 
 
 
cf2b6fd
 
 
 
 
 
 
 
96ad7d9
cf2b6fd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f728bc5
 
245f5a7
 
f728bc5
 
e0a018b
cf2b6fd
7c8b3aa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184

import gradio as gr
from collections import Counter
from transformers import AutoImageProcessor, AutoModelForImageClassification
from PIL import Image
import torch
import math

# ============================== (همان پارامترها و توابع قبلی)
material_params = {
    "brick": {"alpha": 0.3, "eps": 0.9, "I": 1600},
    "stone": {"alpha": 0.25, "eps": 0.92, "I": 2000},
    "polishedstone": {"alpha": 0.2, "eps": 0.9, "I": 2100},
    "concrete": {"alpha": 0.35, "eps": 0.9, "I": 1800},
    "metal": {"alpha": 0.5, "eps": 0.2, "I": 4000},
    "glass": {"alpha": 0.1, "eps": 0.85, "I": 1500},
    "wood": {"alpha": 0.35, "eps": 0.9, "I": 800},
    "tile": {"alpha": 0.4, "eps": 0.9, "I": 1200},
    "ceramic": {"alpha": 0.45, "eps": 0.92, "I": 1300},
    "painted": {"alpha": 0.3, "eps": 0.9, "I": 1000},
    "plastic": {"alpha": 0.1, "eps": 0.95, "I": 800},
    "paper": {"alpha": 0.6, "eps": 0.95, "I": 500},
    "mirror": {"alpha": 0.7, "eps": 0.1, "I": 2000},
    "foliage": {"alpha": 0.25, "eps": 0.98, "I": 900},
    "water": {"alpha": 0.06, "eps": 0.98, "I": 4200},
    
}

material_categories = {
    "facade": {"members": ["brick", "stone", "polishedstone", "concrete", "tile", "ceramic", "painted"], 
               "candidates": ["brick", "stone", "polishedstone", "concrete", "tile", "ceramic", "painted"]},
    "glazing": {"members": ["glass", "mirror"], "candidates": ["glass", "mirror"]},
    "metallic": {"members": ["metal"], "candidates": ["metal"]},
    "coverings": {"members": ["plastic", "paper", "fabric"], "candidates": ["plastic", "paper", "fabric"]},
    "wood_elements": {"members": ["wood"], "candidates": ["wood"]},
    "vegetation": {"members": ["foliage"], "candidates": ["foliage"]},
    "water_bodies": {"members": ["water"], "candidates": ["water"]},
  
}

replacement_text = {
    "facade": {"brick": "آجر روشن یا نمای سرامیکی/تایل روشن با پوشش بازتابی (cool coating)",
               "stone": "سنگ روشن یا سنگ با پوشش بازتابی",
               "polishedstone": "سنگ مات روشن یا سرامیک نما روشن",
               "concrete": "بتن روشن با پوشش بازتابی یا موزاییک نما روشن",
               "tile": "کاشی/سرامیک روشن یا متخلخل",
               "ceramic": "سرامیک روشن با نمای بازتابی",
               "painted": "رنگ بازتابی (cool paint) یا پوشش نانو بازتابی"},
    "glazing": {"glass": "شیشه دو جداره با پوشش Low-E یا شیشه بازتابی کنترل‌شده",
                "mirror": "شیشه مات یا شیشه Low-E با فریم عایق"},
    "metallic": {"metal": "آلومینیوم رنگ روشن یا پوشش پودری با بازتاب بالا"},
    "coverings": {"plastic": "سنگ سبک یا چوب روکش‌دار روشن (بسته به کاربرد)",
                  "paper": "در نما کاربرد معمول ندارد - بررسی بهینه‌سازی طراحی",
                  "fabric": "پارچه با روکش بازتابی یا سایه‌انداز طبیعی"},
    "wood_elements": {"wood": "چوب رنگ روشن یا چوب با روکش بازتابی/محافظ"},
    "vegetation": {"foliage": None},
    "water_bodies": {"water": None},

}

# ============================== (توابع کمکی)
def ET_proxy(T, RH):
    es = 0.6108 * math.exp((17.27 * T) / (T + 237.3))
    return es * (1 - RH / 100.0)

def calc_deltaT(material, T_air, RH=40, u=2, S=700):
    if material not in material_params: return 0.0
    alpha, eps, I = material_params[material]["alpha"], material_params[material]["eps"], material_params[material]["I"]
    A, B, C, D = 1.0, 0.4, 0.8, 0.015
    h_c = 5.8 + 4.1 * u
    if material == "foliage":
        C_m = A * (1 - alpha) - D * ET_proxy(T_air, RH)
    else:
        C_m = A * (1 - alpha) + B * (1 - eps) + (C / math.sqrt(max(I, 1)))
    gamma = S / max(h_c, 1e-6)
    return gamma * C_m / 1000.0

# ============================== (بارگذاری مدل)
model_id = "prithivMLmods/Minc-Materials-23"
processor = AutoImageProcessor.from_pretrained(model_id)
model = AutoModelForImageClassification.from_pretrained(model_id)

patch_size = 224
def get_patches(image, size=224, stride=100):
    patches = []
    w, h = image.size
    for scale in [1.0, 0.75, 0.5]:
        scaled_w, scaled_h = int(w * scale), int(h * scale)
        if min(scaled_w, scaled_h) < size: continue
        scaled_img = image.resize((scaled_w, scaled_h), Image.Resampling.LANCZOS)
        for i in range(0, scaled_w, stride):
            for j in range(0, scaled_h, stride):
                box = (i, j, min(i+size, scaled_w), min(j+size, scaled_h))
                patch = scaled_img.crop(box)
                if patch.size[0] >= size and patch.size[1] >= size:
                    patches.append(patch)
    return patches

# ============================== (تابع اصلی Gradio)
def analyze_image(image, T_air=32.0, RH=40, u=2.0, S=700):
    patches = get_patches(image, size=patch_size)
    all_predictions = []
    for patch in patches:
        inputs = processor(images=patch, return_tensors="pt")
        with torch.no_grad():
            outputs = model(**inputs)
            probs = torch.nn.functional.softmax(outputs.logits, dim=-1)
        top1 = torch.argmax(probs[0]).item()
        label = model.config.id2label[top1]
        all_predictions.append(label)
    
    counter = Counter(all_predictions)
    total_patches = len(patches)
    MIN_COUNT = 3
    ignore_classes = ["food", "skin", "other", "wallpaper", "carpet","sky"]
    materials_found = {label for label, count in counter.items() if count >= MIN_COUNT and label not in ignore_classes}
    
    if len(materials_found) == 0:
        return "هیچ مصالح معتبرِ کافی در تصویر شناسایی نشد (حداقل تکرار MIN_COUNT رعایت نمی‌شود)."
    
    material_info = {}
    for label in sorted(materials_found):
        count = counter[label]
        share = count / total_patches
        dT = calc_deltaT(label, T_air, RH, u, S)
        material_info[label] = {"count": count, "share": share, "deltaT": dT}
    
    # مقایسه درون‌دسته‌ای و توصیه
    IMPROVEMENT_THRESHOLD = 0.02
    SHARE_IMPORTANCE_THRESHOLD = 0.03
    recommendations = []
    candidate_delta_cache = {}
    for cat, info in material_categories.items():
        for candidate in info["candidates"]:
            if candidate not in candidate_delta_cache:
                candidate_delta_cache[candidate] = calc_deltaT(candidate, T_air, RH, u, S)
    
    for label, info in material_info.items():
        found_category = None
        for cat, cinfo in material_categories.items():
            if label in cinfo["members"]:
                found_category = cat
                break
        if found_category is None:
            recommendations.append(f"{label}: در دسته‌های پیش‌تعریف قرار ندارد.")
            continue
        candidates = material_categories[found_category]["candidates"]
        cand_list = [(c, candidate_delta_cache.get(c, calc_deltaT(c, T_air, RH, u, S))) for c in candidates]
        cand_list.sort(key=lambda x: x[1])
        current_dT = info["deltaT"]
        best_candidate, best_dT = cand_list[0]
        improvement = current_dT - best_dT
        share_pct = info["share"] * 100
        if improvement >= IMPROVEMENT_THRESHOLD and best_candidate != label:
            importance = "High" if info["share"] >= SHARE_IMPORTANCE_THRESHOLD else "Optional"
            suggestion_text = replacement_text.get(found_category, {}).get(best_candidate, f"Consider replacing with {best_candidate}")
            recommendations.append(
                f"{label} ({found_category}): ΔT={current_dT:+.2f}°C → جایگزین: {best_candidate} (ΔT={best_dT:+.2f}°C) | بهبود: {improvement:+.2f}°C | اهمیت: {importance} | پیشنهاد: {suggestion_text}"
            )
        else:
            recommendations.append(f"{label}: ΔT={current_dT:+.2f}°C → نیازی به جایگزینی ندارد.")
    
    scene_deltaT = sum([info["share"] * info["deltaT"] for info in material_info.values()])
    recommendations.append(f"ΔT میانگین وزنی کل صحنه: {scene_deltaT:+.2f}°C")
    recommendations.append(f"دمای مؤثر سطح: {T_air + scene_deltaT:.2f}°C")
    
    return "\n".join(recommendations)

# ============================== (راه‌اندازی رابط Gradio)
iface = gr.Interface(
    fn=analyze_image,
    inputs=[
        gr.Image(type="pil", label="آپلود تصویر"),
        gr.Number(value=32.0, label="دمای هوا T_air (°C)"),
        gr.Number(value=40, label="رطوبت نسبی RH (%)"),
        gr.Number(value=2.0, label="سرعت باد u (m/s)"),
        gr.Number(value=700, label="تابش خورشیدی S (W/m²)")
    ],
    outputs=gr.Textbox(label="خروجی ΔT و توصیه‌ها"),
    title="تحلیل مصالح و ΔT سطحی",
    description="آپلود تصویر ساختمان/محیط → نمایش ΔT مصالح و توصیه جایگزینی منطقی."
)

iface.launch()