FranciscoLozDataScience commited on
Commit
e0d8ab4
·
1 Parent(s): 60fa23b

publish app code

Browse files
README.md CHANGED
@@ -1,8 +1,8 @@
1
  ---
2
- title: Uhi Resnet Model
3
- emoji: 📚
4
- colorFrom: red
5
- colorTo: green
6
  sdk: gradio
7
  sdk_version: 5.21.0
8
  app_file: app.py
 
1
  ---
2
+ title: Play with an Urban Heat Island ResNet Model
3
+ emoji: 🔥
4
+ colorFrom: gray
5
+ colorTo: red
6
  sdk: gradio
7
  sdk_version: 5.21.0
8
  app_file: app.py
app.py ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import shap
3
+ from model import UhiModel
4
+ from explainer import UhiExplainer
5
+ import numpy as np
6
+ import pandas as pd
7
+ import plotly.graph_objects as go
8
+
9
+ MODEL = UhiModel("mixed_buffers_ResNet_model.keras","mixed_buffers_standard_scaler.pkl")
10
+
11
+ def filter_map(uhi, longitude, latitude):
12
+ '''
13
+ This function generates a map based on uhi prediction
14
+ '''
15
+ #set up custom data
16
+ data = [uhi, longitude, latitude]
17
+
18
+ # Create the plot
19
+ fig = go.Figure(go.Scattermapbox(
20
+ lat=latitude,
21
+ lon=longitude,
22
+ mode='markers',
23
+ marker=go.scattermapbox.Marker(
24
+ size=6
25
+ ),
26
+ hoverinfo="text",
27
+ hovertemplate='<b>UHI Index</b>: %{customdata[0]}<br><b>long</b>: %{customdata[1]}<br><b>lat</b>: %{customdata[2]}<br>',
28
+ customdata=data
29
+ ))
30
+
31
+ fig.update_layout(
32
+ mapbox_style="open-street-map",
33
+ hovermode='closest',
34
+ mapbox=dict(
35
+ bearing=0,
36
+ center=go.layout.mapbox.Center(
37
+ lat=40.7128,
38
+ lon=-74.0060 # Default to New York City for initial view
39
+ ),
40
+ pitch=0,
41
+ zoom=10
42
+ ),
43
+ )
44
+
45
+ return fig
46
+
47
+ def predict(
48
+ longitude, latitude, m50_NPCRI, m100_Ground_Elevation, avg_wind_speed,
49
+ wind_direction, traffic_volume, m150_Ground_Elevation,
50
+ relative_humidity, m150_NDVI, m150_NDBI,
51
+ m300_SI, m300_NPCRI, m300_Coastal_Aerosol,
52
+ m300_Total_Building_Area_m2, m300_Building_Construction_Year, m300_Ground_Elevation,
53
+ m300_Building_Height, m300_Building_Count, m300_NDVI,
54
+ m300_NDBI, m300_Building_Density, solar_flux
55
+ ):
56
+ '''
57
+ Predict the UHI index for the data inputed, Longitude and Latitude are used to generate a map
58
+ and do not affect the UHI index prediction.
59
+ '''
60
+
61
+ # Create a dictionary with input data and dataset var names
62
+ input_data = {
63
+ "50m_1NPCRI": m50_NPCRI,
64
+ "100m_Ground_Elevation": m100_Ground_Elevation,
65
+ "Avg_Wind_Speed": avg_wind_speed,
66
+ "Wind_Direction": wind_direction,
67
+ "Traffic_Volume": traffic_volume,
68
+ "150m_Ground_Elevation": m150_Ground_Elevation,
69
+ "Relative_Humidity": relative_humidity,
70
+ "150m_NDVI": m150_NDVI,
71
+ "150m_NDBI": m150_NDBI,
72
+ "300m_SI": m300_SI,
73
+ "300m_NPCRI": m300_NPCRI,
74
+ "300m_Coastal_Aerosol": m300_Coastal_Aerosol,
75
+ "300m_Total_Building_Area_m2": m300_Total_Building_Area_m2,
76
+ "300m_Building_Construction_Year": m300_Building_Construction_Year,
77
+ "300m_Ground_Elevation": m300_Ground_Elevation,
78
+ "300m_Building_Height": m300_Building_Height,
79
+ "300m_Building_Count": m300_Building_Count,
80
+ "300m_NDVI": m300_NDVI,
81
+ "300m_NDBI": m300_NDBI,
82
+ "300m_Building_Density": m300_Building_Density,
83
+ "Solar_Flux": solar_flux
84
+ }
85
+
86
+ # Convert to DataFrame
87
+ input_df = pd.DataFrame(input_data, index=[0])
88
+
89
+ #predict
90
+ uhi_index = MODEL.predict(input_df)
91
+
92
+ # explain the prediction
93
+ explainer = UhiExplainer(
94
+ model=MODEL.model,
95
+ explainer_type=shap.DeepExplainer,
96
+ X=input_df,
97
+ feature_names=input_df.columns,
98
+ ref_data=input_df,
99
+ shap_values=None # Compute SHAP values on the fly
100
+ )
101
+ reason = explainer.reasoning(index=0, location=(longitude, latitude))
102
+
103
+ # generate map
104
+ plot = filter_map(uhi_index, longitude, latitude)
105
+
106
+ return uhi_index, reason["uhi_status"], reason["feature_contributions"], plot
107
+
108
+ def load_examples(csv_file):
109
+ '''
110
+ Load examples from csv file
111
+ '''
112
+ # Read examples from CSV file
113
+ df = pd.read_csv(csv_file)
114
+
115
+ # Convert DataFrame to a list of lists
116
+ examples = df.values.tolist()
117
+
118
+ return examples
119
+
120
+ def load_interface():
121
+ '''
122
+ Configure Gradio interface
123
+ '''
124
+
125
+ #set blocks
126
+ info_page = gr.Blocks()
127
+
128
+ with info_page:
129
+ # set title and description
130
+ gr.Markdown(
131
+ """
132
+ # ResNet model for Predicting Urban Heat Island (UHI) Index
133
+
134
+ **Contributors**: Francisco Lozano, Dalton Knapp, Adam Zizi\n
135
+ **University**: Depaul University\n
136
+
137
+ ## Overview
138
+ Our project focused on creating a micro-scale machine learning model that predicts the locations and severity of the UHI effect.
139
+ The model used various datasets, including near-surface air temperatures, building footprint data, weather data, and
140
+ satellite data, to identify key drivers of UHI. This model provides insights into urban areas that are most affected by UHI,
141
+ enabling urban planners and policymakers to take effective mitigation actions.
142
+ >NOTE: The longitude and latitude inputs are used to identify the location of the prediction, but they do not affect the UHI index prediction.\n
143
+
144
+ ## Repository
145
+ The code for this project is available on GitHub. It includes the model training, evaluation, and prediction scripts, as well as
146
+ the datasets used for training and testing. The repository also contains Jupyter notebooks that provide detailed explanations of the model's
147
+ architecture, training process, and evaluation metrics. The notebooks include visualizations of the model's performance and feature importance analysis.\n
148
+ [Project Repo](https://github.com/FranciscoLozCoding/cooling_with_code)
149
+ """
150
+ )
151
+
152
+ # set inputs and outputs for the model
153
+ longitude = gr.Number(label="Longitude", precision=5, info="The Longitude of the location")
154
+ latitude = gr.Number(label="Latitude", precision=5, info="The Latitude of the location")
155
+ m50_NPCRI = gr.Number(label="50m NPCRI", precision=5, info="The average Normalized Difference Vegetation Index in a 50m Buffer Zone")
156
+ m100_Ground_Elevation = gr.Number(label="100m Ground Elevation", precision=5, info="The average Ground Elevation in a 100m Buffer Zone")
157
+ avg_wind_speed = gr.Number(label="Avg Wind Speed [m/s]", precision=5, info="The average Wind Speed at the location")
158
+ wind_direction = gr.Number(label="Wind Direction [degrees]", precision=5, info="The average Wind Direction at the location")
159
+ traffic_volume = gr.Number(label="Traffic Volume", precision=5, info="The Traffic Volume at the location")
160
+ m150_Ground_Elevation = gr.Number(label="150m Ground Elevation", precision=5, info="The average Ground Elevation in a 150m Buffer Zone")
161
+ relative_humidity = gr.Number(label="Relative Humidity [percent]", precision=5, info="The average Relative Humidity at the location")
162
+ m150_NDVI = gr.Number(label="150m NDVI", precision=5, info="The average Normalized Difference Vegetation Index in a 150m Buffer Zone")
163
+ m150_NDBI = gr.Number(label="150m NDBI", precision=5, info="The average Normalized Difference Built-up Index in a 150m Buffer Zone")
164
+ m300_SI = gr.Number(label="300m SI", precision=5, info="The average Shadow Index in a 300m Buffer Zone")
165
+ m300_NPCRI = gr.Number(label="300m NPCRI", precision=5, info="The average Normalized Pigment Chlorophyll Ratio Index in a 300m Buffer Zone")
166
+ m300_Coastal_Aerosol = gr.Number(label="300m Coastal Aerosol", precision=5, info="The average Coastal Aerosol in a 300m Buffer Zone")
167
+ m300_Total_Building_Area_m2 = gr.Number(label="300m Total Building Area(m2)", precision=5, info="The Total Building Area in a 300m Buffer Zone")
168
+ m300_Building_Construction_Year = gr.Number(label="300m Building Construction Year", precision=5, info="The average Building Construction Year in a 300m Buffer Zone")
169
+ m300_Ground_Elevation = gr.Number(label="300m Ground Elevation", precision=5, info="The average Ground Elevation in a 300m Buffer Zone")
170
+ m300_Building_Height = gr.Number(label="300m Building Height", precision=5, info="The average Building Height in a 300m Buffer Zone")
171
+ m300_Building_Count = gr.Number(label="300m Building Count", precision=5, info="The average Building Count in a 300m Buffer Zone")
172
+ m300_NDVI = gr.Number(label="300m NDVI", precision=5, info="The average Normalized Difference Vegetation Index in a 300m Buffer Zone")
173
+ m300_NDBI = gr.Number(label="300m NDBI", precision=5, info="The average Normalized Difference Built-up Index in a 300m Buffer Zone")
174
+ m300_Building_Density = gr.Number(label="300m Building Density", precision=5, info="The average Building Density in a 300m Buffer Zone")
175
+ solar_flux = gr.Number(label="Solar Flux [W/m^2]", precision=5, info="The average Solar Flux at the location")
176
+ inputs = [longitude, latitude, m50_NPCRI, m100_Ground_Elevation, avg_wind_speed, wind_direction,
177
+ traffic_volume, m150_Ground_Elevation, relative_humidity, m150_NDVI,
178
+ m150_NDBI, m300_SI, m300_NPCRI, m300_Coastal_Aerosol, m300_Total_Building_Area_m2,
179
+ m300_Building_Construction_Year, m300_Ground_Elevation, m300_Building_Height, m300_Building_Count,
180
+ m300_NDVI, m300_NDBI, m300_Building_Density, solar_flux]
181
+ uhi = gr.number(label="Predicted UHI Index", precision=5)
182
+
183
+ # set model explainer outputs
184
+ uhi_label = gr.Label(label="Predicted Status based on UHI Index")
185
+ feature_contributions = gr.JSON(label="Feature Contributions", info="The contributions of each feature to the UHI index prediction")
186
+
187
+ # Urban Location
188
+ plot = gr.Plot(label="Urban Location", info="A plot showing the location of the prediction based on the longitude and latitude inputs")
189
+
190
+ model_page = gr.Interface(
191
+ predict,
192
+ inputs=inputs,
193
+ outputs=[uhi, uhi_label, feature_contributions, plot],
194
+ live=True,
195
+ examples=load_examples("examples.csv"),
196
+ title="Interact with The ResNet UHI Model",
197
+ description="This model predicts the Urban Heat Island (UHI) index based on various environmental and urban factors. Adjust the inputs to see how they affect the UHI index prediction.",
198
+ )
199
+
200
+ iface = gr.TabbedInterface(
201
+ [info_page, model_page],
202
+ ["Information", "UHI Model"]
203
+ )
204
+
205
+ iface.launch(server_name="0.0.0.0", server_port=7860, allowed_paths=["/"])
206
+
207
+ if __name__ == "__main__":
208
+ load_interface()
examples.csv ADDED
The diff for this file is too large to render. See raw diff
 
explainer.py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """This module provides an explainer for the model."""
2
+
3
+ import shap
4
+ import matplotlib.pyplot as plt
5
+ import pandas as pd
6
+ import numpy as np
7
+
8
+ class UhiExplainer:
9
+ """
10
+ A class for SHAP-based model explanation.
11
+
12
+ Attributes:
13
+ - model: Trained model (e.g., RandomForestRegressor, XGBRegressor).
14
+ - explainer_type: SHAP explainer class (e.g., shap.TreeExplainer, shap.KernelExplainer).
15
+ - X: Data (Pandas DataFrame) used to compute SHAP values.
16
+ - feature_names: List of feature names.
17
+ - explainer: SHAP explainer instance.
18
+ - shap_values: Computed SHAP values.
19
+
20
+ Methods:
21
+ - apply_shap(): Computes SHAP values.
22
+ - summary_plot(): Generates a SHAP summary plot.
23
+ - bar_plot(): Generates a bar chart of feature importance.
24
+ - dependence_plot(): Generates a dependence plot for a feature.
25
+ - force_plot(): Generates a force plot for an individual prediction.
26
+ - init_js(): Initializes SHAP for Jupyter Notebook.
27
+ - reasoning(): Provides insights on why a record received a high or low UHI index.
28
+ """
29
+
30
+ def __init__(self, model, explainer_type, X, feature_names, ref_data=None, shap_values=None):
31
+ """
32
+ Initializes the Explainer with a trained model, explainer type, and dataset.
33
+
34
+ Parameters:
35
+ - model: Trained model (e.g., RandomForestRegressor, XGBRegressor).
36
+ - explainer_type: SHAP explainer class (e.g., shap.TreeExplainer, shap.KernelExplainer).
37
+ - X: Data (Pandas DataFrame) used to compute SHAP values.
38
+ - feature_names: List of feature names.
39
+ - ref_data (optional): The reference dataset (background dataset) is used by SHAP to estimate the expected output of the model
40
+ - shap_values (optional): Precomputed SHAP values
41
+ """
42
+ self.model = model
43
+ self.explainer_type = explainer_type
44
+ self.X = np.array(X) if isinstance(X, pd.DataFrame) else X # Ensure NumPy format
45
+ if ref_data is not None:
46
+ ref_data = np.array(ref_data) if isinstance(ref_data, pd.DataFrame) else ref_data # Ensure NumPy format
47
+ self.feature_names = feature_names
48
+ self.explainer = explainer_type(model, ref_data) # Initialize explainer
49
+ # Compute SHAP values
50
+ if shap_values is not None:
51
+ self.shap_values = shap_values
52
+ else:
53
+ self.shap_values = self.explainer.shap_values(self.X, check_additivity=False) if self.explainer_type == shap.DeepExplainer else self.explainer.shap_values(self.X)
54
+ # Apply squeeze only if the array has three dimensions and the last dimension is 1
55
+ if self.shap_values.ndim == 3 and self.shap_values.shape[-1] == 1:
56
+ self.shap_values = np.squeeze(self.shap_values)
57
+
58
+ def reasoning(self, index=0, location=(None, None)):
59
+ """
60
+ Provides insights on why the record received a high or low UHI index.
61
+
62
+ Parameters:
63
+ index (int): The index of the observation of interest.
64
+ location (tuple) (optional): The location of the record (long, lat).
65
+
66
+ Returns:
67
+ dict: The insights for the selected record.
68
+ """
69
+
70
+ # Ensure expected_value is a single value (not tensor)
71
+ if self.explainer_type == shap.DeepExplainer:
72
+ expected_value = np.array(self.explainer.expected_value)
73
+ else:
74
+ expected_value = self.explainer.expected_value
75
+
76
+ # Extract single value if expected_value is an array
77
+ if isinstance(expected_value, np.ndarray):
78
+ expected_value = expected_value[0]
79
+
80
+ # Validate record index
81
+ if index >= len(self.shap_values) or index < 0:
82
+ return {"error": "Invalid record index"}
83
+
84
+ # Extract SHAP values for the specified record
85
+ record_shap_values = self.shap_values[index]
86
+
87
+ # Compute SHAP-based final prediction
88
+ shap_final_prediction = expected_value + sum(record_shap_values)
89
+
90
+ # Structure feature contributions
91
+ feature_contributions = [
92
+ {
93
+ "feature": feature,
94
+ "shap_value": value,
95
+ "impact": "increase" if value > 0 else "decrease"
96
+ }
97
+ for feature, value in zip(self.feature_names, record_shap_values)
98
+ ]
99
+
100
+ # Create JSON structure
101
+ shap_json = {
102
+ "record_index": index,
103
+ "longitude": location[0],
104
+ "latitude": location[1],
105
+ "base_value": expected_value,
106
+ "shap_final_prediction": shap_final_prediction, # SHAP-based predicted value
107
+ "uhi_status": "Urban Heat Island" if shap_final_prediction > 1 else "Cooler Region",
108
+ "feature_contributions": feature_contributions,
109
+ }
110
+
111
+ return shap_json
mixed_buffers_ResNet_model.keras ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a5420fdce9a338369c6c88bb33fcdfdc5bfb0ed8e674fdd64afa52453fcddbf1
3
+ size 43663843
mixed_buffers_standard_scaler.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d3961d88dc1644f33012bcab972d6e44aec2a0028502412a009bbc58905f347d
3
+ size 1605
model.py ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import pandas as pd
3
+ from tensorflow.keras.models import load_model
4
+ import pickle
5
+
6
+ class UhiModel:
7
+ """
8
+ Urban Heat Island Model Class that can predict new instances
9
+
10
+ INPUTS
11
+ ---
12
+ model_path: the path to the model file
13
+ scaler_path: the path to the standard scaler file
14
+ """
15
+ def __init__(self, model_path, scaler_path):
16
+ self.model = load_model(model_path)
17
+ with open(scaler_path, 'rb') as f:
18
+ self.scaler = pickle.load(f)
19
+
20
+ def preprocess(self, df: pd.DataFrame) -> pd.DataFrame:
21
+ """
22
+ Preprocess the input DataFrame to create new features for the model.
23
+
24
+ INPUT
25
+ -----
26
+ df: pd.DataFrame
27
+ The input DataFrame containing the features.
28
+
29
+ OUTPUT
30
+ ------
31
+ pd.DataFrame
32
+ The preprocessed DataFrame with additional features.
33
+ """
34
+ Wind_X = np.sin(df["Wind_Direction"])
35
+ Wind_Y = np.cos(df["Wind_Direction"])
36
+
37
+ m100_Elevation_Wind_X = df["100m_Ground_Elevation"] * df["Avg_Wind_Speed"] * Wind_X
38
+ m150_Elevation_Wind_Y = df["150m_Ground_Elevation"] * df["Avg_Wind_Speed"] * Wind_Y
39
+ m150_Humidity_NDVI = df["Relative_Humidity"] * df["150m_NDVI"]
40
+ m150_Traffic_NDBI = df["Traffic_Volume"] * df["150m_NDBI"]
41
+ m300_Building_Wind_X = df["300m_Building_Height"] * df["Avg_Wind_Speed"] * Wind_X
42
+ m300_Building_Wind_Y = df["300m_Building_Height"] * df["Avg_Wind_Speed"] * Wind_Y
43
+ m300_Elevation_Wind_Y = df["300m_Ground_Elevation"] * df["Avg_Wind_Speed"] * Wind_Y
44
+ m300_BldgHeight_Count = df["300m_Building_Height"] * df["300m_Building_Count"]
45
+ m300_TotalBuildingArea_NDVI = df["300m_Total_Building_Area_m2"] * df["300m_NDVI"]
46
+ m300_Traffic_NDVI = df["Traffic_Volume"] * df["300m_NDVI"]
47
+ m300_Traffic_NDBI = df["Traffic_Volume"] * df["300m_NDBI"]
48
+ m300_Building_Aspect_Ratio = df["300m_Building_Height"] / np.sqrt(df["300m_Total_Building_Area_m2"] + 1e-6)
49
+ m300_Sky_View_Factor = 1 - df["300m_Building_Density"]
50
+ m300_Canopy_Cover_Ratio = df["300m_NDVI"] / (df["300m_Building_Density"] + 1e-6)
51
+ m300_GHG_Proxy = df["300m_Building_Count"] * df["Traffic_Volume"] * df["Solar_Flux"]
52
+
53
+ output = {
54
+ "50m_1NPCRI": df["50m_1NPCRI"],
55
+ "100m_Elevation_Wind_X": m100_Elevation_Wind_X,
56
+ "150m_Traffic_Volume": df["Traffic_Volume"],
57
+ "150m_Elevation_Wind_Y": m150_Elevation_Wind_Y,
58
+ "150m_Humidity_NDVI": m150_Humidity_NDVI,
59
+ "150m_Traffic_NDBI": m150_Traffic_NDBI,
60
+ "300m_SI": df["300m_SI"],
61
+ "300m_NPCRI": df["300m_NPCRI"],
62
+ "300m_Coastal_Aerosol": df["300m_Coastal_Aerosol"],
63
+ "300m_Total_Building_Area_m2": df["300m_Total_Building_Area_m2"],
64
+ "300m_Building_Construction_Year": df["300m_Building_Construction_Year"],
65
+ "300m_Ground_Elevation": df["300m_Ground_Elevation"],
66
+ "300m_Building_Wind_X": m300_Building_Wind_X,
67
+ "300m_Building_Wind_Y": m300_Building_Wind_Y,
68
+ "300m_Elevation_Wind_Y": m300_Elevation_Wind_Y,
69
+ "300m_BldgHeight_Count": m300_BldgHeight_Count,
70
+ "300m_TotalBuildingArea_NDVI": m300_TotalBuildingArea_NDVI,
71
+ "300m_Traffic_NDVI": m300_Traffic_NDVI,
72
+ "300m_Traffic_NDBI": m300_Traffic_NDBI,
73
+ "300m_Building_Aspect_Ratio": m300_Building_Aspect_Ratio,
74
+ "300m_Sky_View_Factor": m300_Sky_View_Factor,
75
+ "300m_Canopy_Cover_Ratio": m300_Canopy_Cover_Ratio,
76
+ "300m_GHG_Proxy": m300_GHG_Proxy
77
+ }
78
+
79
+ return output
80
+
81
+ def scale(self, X):
82
+ """
83
+ Apply the scaler used to train the model to the new data
84
+
85
+ INPUT
86
+ -----
87
+ X: the data to be scaled
88
+
89
+ OUTPUT
90
+ ------
91
+ returns the scaled data
92
+ """
93
+
94
+ new_data_scaled = self.scaler.transform(X)
95
+
96
+ return new_data_scaled
97
+
98
+ def predict(self, X: pd.DataFrame) -> float:
99
+ """
100
+ Make a prediction on one sample using the loaded model.
101
+
102
+ INPUT
103
+ -----
104
+ X: pd.DataFrame
105
+ The data to predict a UHI index for. Must contain only one sample.
106
+
107
+ OUTPUT
108
+ ------
109
+ str:
110
+ Predicted UHI index.
111
+ """
112
+
113
+ # Check that input contains only one sample
114
+ if X.shape[0] != 1:
115
+ raise ValueError(f"Input array must contain only one sample, but {X.shape[0]} samples were found")
116
+
117
+ # Preprocess the input data to create new features
118
+ X_processed = self.preprocess(X)
119
+
120
+ # Scale the input data
121
+ X_scaled = self.scale(X_processed)
122
+
123
+ # Ensure the scaled data is 2D
124
+ X_scaled = X_scaled.reshape(1, -1)
125
+
126
+ # Make prediction
127
+ y_pred = self.model.predict(X_scaled)
128
+
129
+ # Extract the predicted UHI index (assuming it's a single value)
130
+ uhi = y_pred[0][0] if y_pred.ndim == 2 else y_pred[0]
131
+
132
+ # Return UHI
133
+ return uhi
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ gradio==5.14.0
2
+ shap==0.46.0
3
+ tensorflow[and-cuda]==2.18.0
4
+ plotly==6.0.*