|
|
import pandas as pd |
|
|
import numpy as np |
|
|
import matplotlib.pyplot as plt |
|
|
import seaborn as sns |
|
|
from prophet import Prophet |
|
|
import gradio as gr |
|
|
import plotly.graph_objects as go |
|
|
from plotly.subplots import make_subplots |
|
|
import warnings |
|
|
warnings.filterwarnings('ignore') |
|
|
from datetime import datetime, timedelta |
|
|
import requests |
|
|
from sklearn.preprocessing import MinMaxScaler |
|
|
from sklearn.metrics import mean_absolute_error, mean_squared_error |
|
|
import tensorflow as tf |
|
|
from tensorflow.keras.models import Sequential |
|
|
from tensorflow.keras.layers import LSTM, Dense, Dropout |
|
|
|
|
|
class SolarPVForecaster: |
|
|
def __init__(self): |
|
|
self.load_data = None |
|
|
self.solar_data = None |
|
|
self.weather_data = None |
|
|
self.prophet_model = None |
|
|
self.lstm_model = None |
|
|
self.scaler = MinMaxScaler() |
|
|
|
|
|
def load_opsd_data(self): |
|
|
"""Load and preprocess data from Open Power System Data""" |
|
|
try: |
|
|
|
|
|
url = 'https://data.open-power-system-data.org/time_series/2020-10-06/time_series_60min_singleindex.csv' |
|
|
print("Loading OPSD data...") |
|
|
data = pd.read_csv(url, parse_dates=['utc_timestamp']) |
|
|
|
|
|
|
|
|
columns_of_interest = [ |
|
|
'utc_timestamp', |
|
|
'DE_load_actual_entsoe_transparency', |
|
|
'DE_solar_generation_actual', |
|
|
'DE_wind_generation_actual', |
|
|
'DE_price_day_ahead' |
|
|
] |
|
|
|
|
|
|
|
|
available_cols = [col for col in columns_of_interest if col in data.columns] |
|
|
df = data[available_cols].copy() |
|
|
|
|
|
|
|
|
df.dropna(subset=['utc_timestamp'], inplace=True) |
|
|
df['utc_timestamp'] = pd.to_datetime(df['utc_timestamp']) |
|
|
df.set_index('utc_timestamp', inplace=True) |
|
|
|
|
|
|
|
|
df = df.interpolate(method='time') |
|
|
|
|
|
print(f"Data loaded successfully! Shape: {df.shape}") |
|
|
print(f"Date range: {df.index.min()} to {df.index.max()}") |
|
|
|
|
|
return df |
|
|
|
|
|
except Exception as e: |
|
|
print(f"Error loading OPSD data: {e}") |
|
|
return self.generate_synthetic_data() |
|
|
|
|
|
def generate_synthetic_data(self): |
|
|
"""Generate synthetic data for demonstration when real data unavailable""" |
|
|
print("Generating synthetic data for demonstration...") |
|
|
dates = pd.date_range(start='2020-01-01', end='2023-12-31', freq='H') |
|
|
|
|
|
|
|
|
hourly_pattern = np.sin(2 * np.pi * dates.hour / 24) + 0.5 |
|
|
daily_pattern = np.sin(2 * np.pi * dates.dayofyear / 365.25) |
|
|
|
|
|
|
|
|
solar_base = 2.5 + 2 * hourly_pattern * (dates.hour >= 6) * (dates.hour <= 18) |
|
|
solar_seasonal = solar_base * (0.7 + 0.3 * np.cos(2 * np.pi * (dates.dayofyear - 172) / 365.25)) |
|
|
|
|
|
|
|
|
weather_impact = 1 - 0.3 * np.random.beta(2, 5, len(dates)) |
|
|
solar_generation = np.maximum(0, solar_seasonal * weather_impact + np.random.normal(0, 0.2, len(dates))) |
|
|
|
|
|
|
|
|
base_load = 32.5 + 7.5 * hourly_pattern + 5 * daily_pattern |
|
|
load_actual = np.maximum(20, base_load + np.random.normal(0, 2, len(dates))) |
|
|
|
|
|
|
|
|
temperature = 20 + 15 * np.sin(2 * np.pi * (dates.dayofyear - 80) / 365.25) + np.random.normal(0, 3, len(dates)) |
|
|
humidity = 50 + 30 * np.sin(2 * np.pi * (dates.dayofyear - 200) / 365.25) + np.random.normal(0, 10, len(dates)) |
|
|
wind_speed = 5 + 3 * np.random.exponential(1, len(dates)) |
|
|
|
|
|
|
|
|
smoke_level = np.random.exponential(0.5, len(dates)) |
|
|
fog_density = np.maximum(0, np.random.normal(0.1, 0.3, len(dates))) |
|
|
dust_concentration = np.random.gamma(2, 0.1, len(dates)) |
|
|
|
|
|
df = pd.DataFrame({ |
|
|
'load_actual': load_actual, |
|
|
'solar_generation': solar_generation, |
|
|
'temperature': temperature, |
|
|
'humidity': humidity, |
|
|
'wind_speed': wind_speed, |
|
|
'smoke_level': smoke_level, |
|
|
'fog_density': fog_density, |
|
|
'dust_concentration': dust_concentration |
|
|
}, index=dates) |
|
|
|
|
|
return df |
|
|
|
|
|
def prepare_lstm_data(self, data, target_col, sequence_length=24): |
|
|
"""Prepare data for LSTM model""" |
|
|
|
|
|
scaled_data = self.scaler.fit_transform(data) |
|
|
|
|
|
X, y = [], [] |
|
|
for i in range(sequence_length, len(scaled_data)): |
|
|
X.append(scaled_data[i-sequence_length:i]) |
|
|
y.append(scaled_data[i, data.columns.get_loc(target_col)]) |
|
|
|
|
|
return np.array(X), np.array(y) |
|
|
|
|
|
def build_lstm_model(self, input_shape): |
|
|
"""Build LSTM model for forecasting""" |
|
|
model = Sequential([ |
|
|
LSTM(50, return_sequences=True, input_shape=input_shape), |
|
|
Dropout(0.2), |
|
|
LSTM(50, return_sequences=True), |
|
|
Dropout(0.2), |
|
|
LSTM(50), |
|
|
Dropout(0.2), |
|
|
Dense(1) |
|
|
]) |
|
|
|
|
|
model.compile(optimizer='adam', loss='mse', metrics=['mae']) |
|
|
return model |
|
|
|
|
|
def train_models(self, df): |
|
|
"""Train both Prophet and LSTM models""" |
|
|
print("Training forecasting models...") |
|
|
|
|
|
|
|
|
prophet_data = df.reset_index()[['utc_timestamp', 'solar_generation']].copy() |
|
|
prophet_data.columns = ['ds', 'y'] |
|
|
prophet_data.dropna(inplace=True) |
|
|
|
|
|
|
|
|
if 'temperature' in df.columns: |
|
|
prophet_data['temperature'] = df['temperature'].values[:len(prophet_data)] |
|
|
if 'humidity' in df.columns: |
|
|
prophet_data['humidity'] = df['humidity'].values[:len(prophet_data)] |
|
|
if 'smoke_level' in df.columns: |
|
|
prophet_data['smoke_level'] = df['smoke_level'].values[:len(prophet_data)] |
|
|
if 'fog_density' in df.columns: |
|
|
prophet_data['fog_density'] = df['fog_density'].values[:len(prophet_data)] |
|
|
|
|
|
|
|
|
self.prophet_model = Prophet( |
|
|
daily_seasonality=True, |
|
|
weekly_seasonality=True, |
|
|
yearly_seasonality=True, |
|
|
changepoint_prior_scale=0.05 |
|
|
) |
|
|
|
|
|
|
|
|
for col in ['temperature', 'humidity', 'smoke_level', 'fog_density']: |
|
|
if col in prophet_data.columns: |
|
|
self.prophet_model.add_regressor(col) |
|
|
|
|
|
self.prophet_model.fit(prophet_data) |
|
|
|
|
|
|
|
|
feature_cols = ['solar_generation', 'load_actual', 'temperature', 'humidity', |
|
|
'smoke_level', 'fog_density', 'dust_concentration'] |
|
|
available_cols = [col for col in feature_cols if col in df.columns] |
|
|
|
|
|
lstm_data = df[available_cols].dropna() |
|
|
X, y = self.prepare_lstm_data(lstm_data, 'solar_generation') |
|
|
|
|
|
|
|
|
train_size = int(0.8 * len(X)) |
|
|
X_train, X_test = X[:train_size], X[train_size:] |
|
|
y_train, y_test = y[:train_size], y[train_size:] |
|
|
|
|
|
|
|
|
self.lstm_model = self.build_lstm_model((X.shape[1], X.shape[2])) |
|
|
|
|
|
print("Training LSTM model...") |
|
|
history = self.lstm_model.fit( |
|
|
X_train, y_train, |
|
|
epochs=50, |
|
|
batch_size=32, |
|
|
validation_data=(X_test, y_test), |
|
|
verbose=0 |
|
|
) |
|
|
|
|
|
print("Models trained successfully!") |
|
|
return history |
|
|
|
|
|
def forecast_solar_generation(self, days=7, include_weather=True): |
|
|
"""Forecast solar generation using Prophet model""" |
|
|
if self.prophet_model is None: |
|
|
raise ValueError("Model not trained yet!") |
|
|
|
|
|
|
|
|
future = self.prophet_model.make_future_dataframe( |
|
|
periods=days * 24, freq='H' |
|
|
) |
|
|
|
|
|
|
|
|
if include_weather: |
|
|
|
|
|
future_weather = self.simulate_future_weather(len(future)) |
|
|
for col, values in future_weather.items(): |
|
|
if col in self.prophet_model.extra_regressors: |
|
|
future[col] = values |
|
|
|
|
|
forecast = self.prophet_model.predict(future) |
|
|
return forecast |
|
|
|
|
|
def simulate_future_weather(self, n_periods): |
|
|
"""Simulate future weather conditions""" |
|
|
future_weather = {} |
|
|
|
|
|
|
|
|
base_temp = 25 |
|
|
base_humidity = 60 |
|
|
|
|
|
future_weather['temperature'] = base_temp + 5 * np.sin(np.linspace(0, 4*np.pi, n_periods)) + np.random.normal(0, 2, n_periods) |
|
|
future_weather['humidity'] = base_humidity + 20 * np.sin(np.linspace(0, 4*np.pi, n_periods)) + np.random.normal(0, 5, n_periods) |
|
|
future_weather['smoke_level'] = np.random.exponential(0.5, n_periods) |
|
|
future_weather['fog_density'] = np.maximum(0, np.random.normal(0.1, 0.2, n_periods)) |
|
|
|
|
|
return future_weather |
|
|
|
|
|
def calculate_auxiliary_savings(self, solar_forecast, days=7): |
|
|
"""Calculate potential savings in auxiliary power consumption""" |
|
|
|
|
|
AUXILIARY_LOAD_OPERATING = 25 |
|
|
AUXILIARY_LOAD_STANDBY = 40 |
|
|
GRID_TARIFF = 0.12 |
|
|
|
|
|
total_solar_generation = solar_forecast['yhat'].sum() |
|
|
|
|
|
|
|
|
hours_in_period = days * 24 |
|
|
|
|
|
|
|
|
avg_auxiliary_load = (AUXILIARY_LOAD_OPERATING + AUXILIARY_LOAD_STANDBY) / 2 |
|
|
|
|
|
|
|
|
solar_contribution = min(total_solar_generation, avg_auxiliary_load * hours_in_period) |
|
|
|
|
|
|
|
|
cost_savings = solar_contribution * 1000 * GRID_TARIFF |
|
|
|
|
|
return { |
|
|
'total_solar_generation_MWh': round(total_solar_generation, 2), |
|
|
'solar_contribution_MWh': round(solar_contribution, 2), |
|
|
'cost_savings_USD': round(cost_savings, 2), |
|
|
'auxiliary_load_reduction_percent': round((solar_contribution / (avg_auxiliary_load * hours_in_period)) * 100, 2) |
|
|
} |
|
|
|
|
|
|
|
|
forecaster = SolarPVForecaster() |
|
|
df_global = None |
|
|
|
|
|
def initialize_system(): |
|
|
"""Initialize the forecasting system""" |
|
|
global df_global, forecaster |
|
|
|
|
|
print("Initializing Solar PV Forecasting System...") |
|
|
df_global = forecaster.load_opsd_data() |
|
|
history = forecaster.train_models(df_global) |
|
|
print("System initialized successfully!") |
|
|
return "β
System Initialized Successfully!" |
|
|
|
|
|
def forecast_and_visualize(days, chart_type, weather_impact): |
|
|
"""Main forecasting function with enhanced visualizations""" |
|
|
global df_global, forecaster |
|
|
|
|
|
if df_global is None or forecaster.prophet_model is None: |
|
|
return "β Please initialize the system first!", None |
|
|
|
|
|
try: |
|
|
|
|
|
forecast = forecaster.forecast_solar_generation(days, include_weather=weather_impact) |
|
|
forecast_future = forecast.tail(days * 24) |
|
|
|
|
|
|
|
|
savings = forecaster.calculate_auxiliary_savings(forecast_future, days) |
|
|
|
|
|
if chart_type == "Solar Generation Forecast": |
|
|
fig = go.Figure() |
|
|
|
|
|
|
|
|
recent_data = df_global.tail(7 * 24) |
|
|
fig.add_trace(go.Scatter( |
|
|
x=recent_data.index, |
|
|
y=recent_data['solar_generation'], |
|
|
name='Historical Solar Generation', |
|
|
line=dict(color='orange') |
|
|
)) |
|
|
|
|
|
|
|
|
fig.add_trace(go.Scatter( |
|
|
x=pd.to_datetime(forecast_future['ds']), |
|
|
y=forecast_future['yhat'], |
|
|
name='Forecasted Solar Generation', |
|
|
line=dict(color='green') |
|
|
)) |
|
|
|
|
|
|
|
|
fig.add_trace(go.Scatter( |
|
|
x=pd.to_datetime(forecast_future['ds']), |
|
|
y=forecast_future['yhat_upper'], |
|
|
fill=None, |
|
|
mode='lines', |
|
|
line_color='rgba(0,100,80,0)', |
|
|
showlegend=False |
|
|
)) |
|
|
|
|
|
fig.add_trace(go.Scatter( |
|
|
x=pd.to_datetime(forecast_future['ds']), |
|
|
y=forecast_future['yhat_lower'], |
|
|
fill='tonexty', |
|
|
mode='lines', |
|
|
line_color='rgba(0,100,80,0)', |
|
|
name='Confidence Interval', |
|
|
fillcolor='rgba(0,100,80,0.2)' |
|
|
)) |
|
|
|
|
|
fig.update_layout( |
|
|
title=f"5 MW Solar PV Generation Forecast ({days} Days)", |
|
|
xaxis_title="Date", |
|
|
yaxis_title="Solar Generation (MW)", |
|
|
template="plotly_white", |
|
|
height=500 |
|
|
) |
|
|
|
|
|
elif chart_type == "Weather Impact Analysis": |
|
|
fig = make_subplots( |
|
|
rows=2, cols=2, |
|
|
subplot_titles=('Temperature Effect', 'Humidity Effect', |
|
|
'Smoke/Fog Impact', 'Generation vs Weather'), |
|
|
specs=[[{"secondary_y": True}, {"secondary_y": True}], |
|
|
[{"secondary_y": True}, {"secondary_y": True}]] |
|
|
) |
|
|
|
|
|
|
|
|
recent_temp = df_global['temperature'].tail(days * 24) |
|
|
recent_solar = df_global['solar_generation'].tail(days * 24) |
|
|
|
|
|
fig.add_trace( |
|
|
go.Scatter(x=recent_temp.index, y=recent_temp, name="Temperature", line=dict(color='red')), |
|
|
row=1, col=1 |
|
|
) |
|
|
fig.add_trace( |
|
|
go.Scatter(x=recent_solar.index, y=recent_solar, name="Solar Gen", line=dict(color='orange')), |
|
|
row=1, col=1, secondary_y=True |
|
|
) |
|
|
|
|
|
|
|
|
fig.update_layout( |
|
|
title="Weather Factors Impact on Solar Generation", |
|
|
height=600, |
|
|
template="plotly_white" |
|
|
) |
|
|
|
|
|
elif chart_type == "Economic Analysis": |
|
|
|
|
|
categories = ['Solar Generation', 'Cost Savings', 'Load Reduction', 'Efficiency'] |
|
|
values = [ |
|
|
savings['total_solar_generation_MWh'], |
|
|
savings['cost_savings_USD'] / 1000, |
|
|
savings['auxiliary_load_reduction_percent'], |
|
|
min(100, savings['auxiliary_load_reduction_percent'] * 1.2) |
|
|
] |
|
|
|
|
|
fig = go.Figure(data=[ |
|
|
go.Bar(x=categories, y=values, |
|
|
marker_color=['green', 'blue', 'orange', 'red']) |
|
|
]) |
|
|
|
|
|
fig.update_layout( |
|
|
title="Economic Impact Analysis - 5 MW Solar Integration", |
|
|
yaxis_title="Value (MWh / k$ / %)", |
|
|
template="plotly_white", |
|
|
height=500 |
|
|
) |
|
|
|
|
|
else: |
|
|
fig = go.Figure() |
|
|
|
|
|
|
|
|
recent_load = df_global['load_actual'].tail(days * 24) |
|
|
|
|
|
fig.add_trace(go.Scatter( |
|
|
x=recent_load.index, |
|
|
y=recent_load, |
|
|
name='Auxiliary Load Demand', |
|
|
line=dict(color='red') |
|
|
)) |
|
|
|
|
|
fig.add_trace(go.Scatter( |
|
|
x=pd.to_datetime(forecast_future['ds']), |
|
|
y=forecast_future['yhat'], |
|
|
name='Solar Generation', |
|
|
line=dict(color='green') |
|
|
)) |
|
|
|
|
|
|
|
|
fig.add_hline(y=25, line_dash="dash", line_color="orange", |
|
|
annotation_text="Operating Load (25 MW)") |
|
|
fig.add_hline(y=40, line_dash="dash", line_color="red", |
|
|
annotation_text="Standby Load (40 MW)") |
|
|
|
|
|
fig.update_layout( |
|
|
title="Solar Generation vs Auxiliary Load Requirements", |
|
|
xaxis_title="Date", |
|
|
yaxis_title="Power (MW)", |
|
|
template="plotly_white", |
|
|
height=500 |
|
|
) |
|
|
|
|
|
|
|
|
report = f""" |
|
|
## π Solar PV Integration Analysis Report |
|
|
|
|
|
**Forecast Period:** {days} days |
|
|
**Weather Impact Included:** {'Yes' if weather_impact else 'No'} |
|
|
|
|
|
### π Generation Summary: |
|
|
- **Total Solar Generation:** {savings['total_solar_generation_MWh']} MWh |
|
|
- **Auxiliary Load Contribution:** {savings['solar_contribution_MWh']} MWh |
|
|
- **Load Reduction:** {savings['auxiliary_load_reduction_percent']}% |
|
|
|
|
|
### π° Economic Benefits: |
|
|
- **Cost Savings:** ${savings['cost_savings_USD']:,} |
|
|
- **Grid Dependency Reduction:** {savings['auxiliary_load_reduction_percent']}% |
|
|
|
|
|
### π± Environmental Impact: |
|
|
- **CO2 Reduction:** ~{savings['solar_contribution_MWh'] * 0.5:.1f} tons CO2eq |
|
|
- **Renewable Energy Share:** Increased by {savings['auxiliary_load_reduction_percent']}% |
|
|
|
|
|
--- |
|
|
*This analysis supports the research objective of integrating 5 MW solar PV |
|
|
with 1180 MW Combined Cycle Power Plant for efficient auxiliary consumption.* |
|
|
""" |
|
|
|
|
|
return report, fig |
|
|
|
|
|
except Exception as e: |
|
|
return f"β Error in forecasting: {str(e)}", None |
|
|
|
|
|
def get_system_status(): |
|
|
"""Get current system status""" |
|
|
global df_global, forecaster |
|
|
|
|
|
if df_global is None: |
|
|
return "β System not initialized" |
|
|
|
|
|
status = f""" |
|
|
## π§ System Status |
|
|
|
|
|
**Data Loaded:** β
{len(df_global):,} records |
|
|
**Date Range:** {df_global.index.min()} to {df_global.index.max()} |
|
|
**Prophet Model:** {'β
Trained' if forecaster.prophet_model else 'β Not trained'} |
|
|
**LSTM Model:** {'β
Trained' if forecaster.lstm_model else 'β Not trained'} |
|
|
|
|
|
**Available Features:** |
|
|
{chr(10).join([f"β’ {col}" for col in df_global.columns])} |
|
|
""" |
|
|
|
|
|
return status |
|
|
|
|
|
|
|
|
def create_interface(): |
|
|
with gr.Blocks( |
|
|
title="Solar PV Integration Forecasting System", |
|
|
theme=gr.themes.Soft(), |
|
|
css=""" |
|
|
.gradio-container { |
|
|
max-width: 1200px !important; |
|
|
} |
|
|
.header { |
|
|
text-align: center; |
|
|
background: linear-gradient(90deg, #1e3c72, #2a5298); |
|
|
color: white; |
|
|
padding: 20px; |
|
|
border-radius: 10px; |
|
|
margin-bottom: 20px; |
|
|
} |
|
|
""" |
|
|
) as iface: |
|
|
|
|
|
gr.Markdown(""" |
|
|
<div class="header"> |
|
|
<h1>π Solar PV Integration Forecasting System</h1> |
|
|
<p>MSc Thesis Research: Integrating 5 MW Solar Project with 1180 MW Combined Cycle Power Plant</p> |
|
|
<p>Student: Muhammad Saddan | Supervisor: Dr Muhammad Asghar Saqib</p> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(scale=1): |
|
|
gr.Markdown("### π System Controls") |
|
|
|
|
|
init_btn = gr.Button("Initialize System", variant="primary", size="lg") |
|
|
status_btn = gr.Button("Check Status", variant="secondary") |
|
|
|
|
|
gr.Markdown("### βοΈ Forecast Parameters") |
|
|
|
|
|
days_input = gr.Radio( |
|
|
choices=[1, 3, 7, 14, 30], |
|
|
value=7, |
|
|
label="Forecast Period (Days)" |
|
|
) |
|
|
|
|
|
chart_type = gr.Dropdown( |
|
|
choices=[ |
|
|
"Solar Generation Forecast", |
|
|
"Weather Impact Analysis", |
|
|
"Economic Analysis", |
|
|
"Load vs Generation Comparison" |
|
|
], |
|
|
value="Solar Generation Forecast", |
|
|
label="Analysis Type" |
|
|
) |
|
|
|
|
|
weather_impact = gr.Checkbox( |
|
|
value=True, |
|
|
label="Include Weather Factors (Smoke, Fog, Temperature)" |
|
|
) |
|
|
|
|
|
forecast_btn = gr.Button("Generate Forecast", variant="primary") |
|
|
|
|
|
with gr.Column(scale=2): |
|
|
gr.Markdown("### π Analysis Results") |
|
|
|
|
|
with gr.Tab("Forecast Chart"): |
|
|
forecast_plot = gr.Plot(label="Forecast Visualization") |
|
|
|
|
|
with gr.Tab("System Status"): |
|
|
status_output = gr.Markdown("Click 'Check Status' to view system information") |
|
|
|
|
|
with gr.Tab("Detailed Report"): |
|
|
report_output = gr.Markdown("Generate a forecast to see detailed analysis") |
|
|
|
|
|
|
|
|
init_btn.click( |
|
|
fn=initialize_system, |
|
|
outputs=status_output |
|
|
) |
|
|
|
|
|
status_btn.click( |
|
|
fn=get_system_status, |
|
|
outputs=status_output |
|
|
) |
|
|
|
|
|
forecast_btn.click( |
|
|
fn=forecast_and_visualize, |
|
|
inputs=[days_input, chart_type, weather_impact], |
|
|
outputs=[report_output, forecast_plot] |
|
|
) |
|
|
|
|
|
gr.Markdown(""" |
|
|
--- |
|
|
### π Research Objectives Addressed: |
|
|
1. β
**Technical Feasibility Assessment** - Solar PV integration with combined cycle power plant |
|
|
2. β
**Advanced Forecasting** - RNN/LSTM and Prophet models for generation prediction |
|
|
3. β
**Weather Impact Analysis** - Smoke, fog, and atmospheric conditions modeling |
|
|
4. β
**Economic Viability** - Cost-benefit analysis and grid dependency reduction |
|
|
5. β
**Grid Synchronization** - Load sharing analysis between solar and conventional sources |
|
|
|
|
|
**Data Source:** Open Power System Data (open-power-system-data.org) |
|
|
**Methodology:** Prophet + LSTM hybrid forecasting with weather regressors |
|
|
""") |
|
|
|
|
|
return iface |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
interface = create_interface() |
|
|
interface.launch( |
|
|
share=True, |
|
|
server_name="0.0.0.0", |
|
|
server_port=7860, |
|
|
debug=True |
|
|
) |