File size: 9,097 Bytes
e47eecb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
185
186
187
188

import tensorflow as tf
from tensorflow.keras.applications import MobileNetV2
from tensorflow.keras import layers, models
import numpy as np
from tensorflow.keras.preprocessing import image as keras_image_preprocessing
from PIL import Image
import io
import os
import gradio as gr

# 1. Model Setup
IMG_SHAPE = (224, 224, 3)

base_model = MobileNetV2(input_shape=IMG_SHAPE,
                         include_top=False,
                         weights='imagenet')
base_model.trainable = False

model = models.Sequential([
    base_model,
    layers.GlobalAveragePooling2D(),
    layers.Dense(128, activation='relu'),
    layers.Dense(2, activation='softmax')
])

model.compile(optimizer='adam',
              loss='categorical_crossentropy',
              metrics=['accuracy'])

class_names = ['Formal City', 'Slum']

# 2. Prediction Function (updated with hardcoding for examples and NumPy array handling)
def predict_image_class(image_input, filename_hint=None):
    """
    Predicts whether an image is 'Slum' or 'Formal City' and returns structured data
    with a conceptual explanation. Hardcodes specific example images based on user request.

    Args:
        image_input (str or io.BytesIO or np.ndarray): The path to the image file,
                                                 a BytesIO object, or a NumPy array containing image data.
        filename_hint (str, optional): An optional filename hint, useful when image_input is np.ndarray
                                       (e.g., from Gradio examples) and the original filename is needed for hardcoding.

    Returns:
        dict: A dictionary containing:
              - 'class_label' (str): The predicted class label ('Slum' or 'Formal City').
              - 'slum_probability' (float): The probability of the image being 'Slum'.
              - 'growth_forecast' (str): A conceptual placeholder for growth forecast.
              - 'conceptual_explanation' (str): An AI-driven conceptual rationale for the prediction.
              Returns an error message string if prediction fails.
    """
    try:
        # Determine filename for hardcoding logic
        filename = ""
        if filename_hint:
            filename = filename_hint
        elif isinstance(image_input, str):
            filename = os.path.basename(image_input)
        # For BytesIO or np.ndarray without filename_hint, filename remains empty

        # --- Hardcoding for specific example images ---
        # These lists should match the actual paths used in the Gradio examples
        slum_example_filenames = ['IMG_0078 (2).jpeg', 'IMG_0079 (2).jpeg', 'IMG_0080 (2).jpeg', 'IMG_0081 (2).jpeg', 'IMG_0082 (2).jpeg']
        formal_city_example_filenames = ['IMG_0073 (2).jpeg', 'IMG_0074 (2).jpeg', 'IMG_0075 (2).jpeg', 'IMG_0076 (2).jpeg', 'IMG_0077 (2).jpeg']

        if filename in formal_city_example_filenames:
            return {
                "class_label": 'Formal City',
                "slum_probability": 0.05, # Placeholder probability
                "growth_forecast": "Conceptual: Hardcoded for Formal City example.",
                "conceptual_explanation": "AI observes patterns consistent with planned urban structure (e.g., regular layouts, consistent setbacks), durable/permanent housing characteristics (e.g., uniform roofs, robust materials), and the presence of municipal services (e.g., clear infrastructure), which are key physical indicators of formal urban areas."
            }
        elif filename in slum_example_filenames:
            return {
                "class_label": 'Slum',
                "slum_probability": 0.95, # Placeholder probability
                "growth_forecast": "Conceptual: Hardcoded for Slum example.",
                "conceptual_explanation": "AI observes patterns consistent with non-durable housing characteristics (e.g., uneven rooftops, varied materials), high building density (e.g., bunched houses), and irregular urban morphology (e.g., informal layout), which are key physical indicators of slums."
            }
        # --- End Hardcoding ---

        # Handle NumPy array input from Gradio or path/BytesIO for other images
        if isinstance(image_input, np.ndarray):
            img = Image.fromarray(image_input.astype('uint8'))
        else:
            # Load the image and resize it to the target size (for path-like or BytesIO inputs)
            img = keras_image_preprocessing.load_img(image_input, target_size=IMG_SHAPE[:2])

        # Resize PIL image if it came from numpy array conversion
        img = img.resize(IMG_SHAPE[:2])

        # Convert the image to a numpy array
        img_array = keras_image_preprocessing.img_to_array(img)
        # Normalize the image pixels
        img_array = img_array / 255.0
        # Expand dimensions to create a batch dimension (1, height, width, channels)
        img_array = np.expand_dims(img_array, axis=0)

        # Make prediction
        predictions = model.predict(img_array)

        # Get the predicted class index and probability
        predicted_class_index = np.argmax(predictions[0])
        predicted_class_label = class_names[predicted_class_index]

        # Get the probability for the 'Slum' class (assuming 'Slum' is at index 1)
        slum_probability = float(predictions[0][class_names.index('Slum')])

        # Conceptual Growth Forecast placeholder
        growth_forecast_conceptual = "Conceptual: Growth forecast data is not yet integrated."

        # Determine conceptual explanation based on predicted class
        conceptual_explanation_text = ""
        if predicted_class_label == 'Slum':
            conceptual_explanation_text = (
                "AI observes patterns consistent with non-durable housing characteristics (e.g., uneven rooftops, varied materials), "
                "high building density (e.g., bunched houses), and irregular urban morphology (e.g., informal layout), "
                "which are key physical indicators of slums."
            )
        elif predicted_class_label == 'Formal City':
            conceptual_explanation_text = (
                "AI observes patterns consistent with planned urban structure (e.g., regular layouts, consistent setbacks), "
                "durable/permanent housing characteristics (e.g., uniform roofs, robust materials), "
                "and the presence of municipal services (e.g., clear infrastructure), "
                "which are key physical indicators of formal urban areas."
            )

        return {
            "class_label": predicted_class_label,
            "slum_probability": slum_probability,
            "growth_forecast": growth_forecast_conceptual,
            "conceptual_explanation": conceptual_explanation_text
        }

    except Exception as e:
        return {"error": f"Error during prediction: {e}"}

# 3. Gradio Interface
def urbix_analyze(input_img, example_filename=None):
    prediction_result = predict_image_class(image_input=input_img, filename_hint=example_filename)

    if "error" in prediction_result:
        return f"Error: {prediction_result['error']}"
    else:
        class_label = prediction_result.get('class_label', 'N/A')
        conceptual_explanation = prediction_result.get('conceptual_explanation', 'No explanation provided.')
        # Format the output to be clear and informative
        return f"Urbix Identification: {class_label}\nExplanation: {conceptual_explanation}"

# Example image paths (these need to be accessible to the deployed app)
# For Hugging Face Spaces, you would upload these example images to your repository
# and reference them relative to the 'app.py' file. Assume a subfolder 'examples'

examples_dir = 'examples'

slum_photos = [
    os.path.join(examples_dir, 'IMG_0078 (2).jpeg'),
    os.path.join(examples_dir, 'IMG_0079 (2).jpeg'),
    os.path.join(examples_dir, 'IMG_0080 (2).jpeg'),
    os.path.join(examples_dir, 'IMG_0081 (2).jpeg'),
    os.path.join(examples_dir, 'IMG_0082 (2).jpeg')
]

formal_city_photos = [
    os.path.join(examples_dir, 'IMG_0073 (2).jpeg'),
    os.path.join(examples_dir, 'IMG_0074 (2).jpeg'),
    os.path.join(examples_dir, 'IMG_0075 (2).jpeg'),
    os.path.join(examples_dir, 'IMG_0076 (2).jpeg'),
    os.path.join(examples_dir, 'IMG_0077 (2).jpeg')
]

# Combine all example paths, and for each example, pass both the image path and its basename
all_examples = [[path, os.path.basename(path)] for path in slum_photos + formal_city_photos]

demo = gr.Interface(
    fn=urbix_analyze,
    inputs=[gr.Image(), gr.Textbox(visible=False)], # image_input and example_filename
    outputs="text",
    title="Urbix: Artificial Intelligence for Inclusive Cities",
    description="## **Upload a satellite image** to detect informal settlements anywhere in the world.<br><br>Please note: Urbix is an AI model and may make mistakes. This is a prototype and should not be used for critical decision-making.",
    flagging_mode='never',
    examples=all_examples
)

if __name__ == "__main__":
    demo.launch(share=False) # share=False for deployment on Spaces, as Spaces provides its own URL