alaka commited on
Commit
9e28fe5
·
1 Parent(s): 0b9b583
Files changed (2) hide show
  1. app.py +325 -12
  2. requirements.txt +1 -0
app.py CHANGED
@@ -3,6 +3,9 @@ import neuralfoil as nf
3
  import numpy as np
4
  import json
5
  from pathlib import Path
 
 
 
6
 
7
  def load_airfoil_from_dat(filepath):
8
  """Load airfoil coordinates from a .dat file"""
@@ -52,14 +55,230 @@ def parse_coordinates(coords_text):
52
 
53
  return np.array(coords)
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  def run_neuralfoil_prediction(coords_text, alpha, reynolds, model_size):
56
- """Run NeuralFoil prediction and return full JSON output"""
57
  try:
58
  # Parse coordinates
59
  coords = parse_coordinates(coords_text)
60
 
61
  if len(coords) < 3:
62
- return json.dumps({"error": "Invalid coordinates. Please provide at least 3 coordinate pairs."}, indent=2)
 
 
 
 
 
63
 
64
  # Run NeuralFoil analysis directly from coordinates
65
  result = nf.get_aero_from_coordinates(
@@ -68,7 +287,7 @@ def run_neuralfoil_prediction(coords_text, alpha, reynolds, model_size):
68
  Re=reynolds,
69
  model_size=model_size
70
  )
71
- print(result)
72
 
73
  # Convert result to a serializable dictionary
74
  output = {}
@@ -100,6 +319,52 @@ def run_neuralfoil_prediction(coords_text, alpha, reynolds, model_size):
100
  elif isinstance(val, np.ndarray):
101
  output[attr] = val.tolist()
102
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  # Add input parameters for reference
104
  output['input_parameters'] = {
105
  'alpha_deg': alpha,
@@ -107,11 +372,36 @@ def run_neuralfoil_prediction(coords_text, alpha, reynolds, model_size):
107
  'model_size': model_size,
108
  'num_coordinates': len(coords)
109
  }
 
 
 
110
 
111
- return json.dumps(output, indent=2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
  except Exception as e:
114
- return json.dumps({"error": str(e)}, indent=2)
 
 
 
 
115
 
116
  def load_example(example_name):
117
  """Load an example airfoil"""
@@ -127,8 +417,8 @@ def load_example(example_name):
127
  return format_coordinates(coords)
128
  return ""
129
 
130
- # Load default airfoil (NACA 4412)
131
- default_coords = format_coordinates(load_airfoil_from_dat("examples/naca4412.dat"))
132
 
133
  # Create Gradio interface
134
  with gr.Blocks(title="NeuralFoil Airfoil Predictor") as demo:
@@ -156,7 +446,7 @@ with gr.Blocks(title="NeuralFoil Airfoil Predictor") as demo:
156
  example_buttons = gr.Radio(
157
  choices=["NACA 4412", "Clark Y", "RAE 2822"],
158
  label="Example Airfoils",
159
- value="NACA 4412"
160
  )
161
 
162
  load_btn = gr.Button("Load Example")
@@ -184,11 +474,14 @@ with gr.Blocks(title="NeuralFoil Airfoil Predictor") as demo:
184
  predict_btn = gr.Button("Run Prediction", variant="primary")
185
 
186
  with gr.Column():
 
 
 
187
  gr.Markdown("### Full NeuralFoil Output (JSON)")
188
  output_json = gr.Textbox(
189
  label="Prediction Results",
190
- lines=25,
191
- max_lines=40,
192
  placeholder="Results will appear here..."
193
  )
194
 
@@ -202,7 +495,7 @@ with gr.Blocks(title="NeuralFoil Airfoil Predictor") as demo:
202
  predict_btn.click(
203
  fn=run_neuralfoil_prediction,
204
  inputs=[coords_input, alpha_input, reynolds_input, model_size_input],
205
- outputs=[output_json]
206
  )
207
 
208
  gr.Markdown("""
@@ -214,5 +507,25 @@ with gr.Blocks(title="NeuralFoil Airfoil Predictor") as demo:
214
  **Citation**: If you use NeuralFoil, please cite the [GitHub repository](https://github.com/peterdsharpe/NeuralFoil) and Peter Sharpe's PhD thesis.
215
  """)
216
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
  if __name__ == "__main__":
218
- demo.launch()
 
3
  import numpy as np
4
  import json
5
  from pathlib import Path
6
+ import matplotlib.pyplot as plt
7
+ import matplotlib
8
+ matplotlib.use('Agg') # Use non-interactive backend
9
 
10
  def load_airfoil_from_dat(filepath):
11
  """Load airfoil coordinates from a .dat file"""
 
55
 
56
  return np.array(coords)
57
 
58
+ def create_pressure_plot(coords, result, alpha, reynolds):
59
+ """Create a plot of the airfoil with pressure distribution"""
60
+ try:
61
+ fig, ax = plt.subplots(figsize=(12, 6))
62
+
63
+ # Plot airfoil shape
64
+ ax.plot(coords[:, 0], coords[:, 1], 'k-', linewidth=2, label='Airfoil')
65
+ ax.fill(coords[:, 0], coords[:, 1], color='lightgray', alpha=0.3)
66
+
67
+ # Calculate pressure coefficient from edge velocity
68
+ # Cp = 1 - (ue/vinf)^2
69
+ upper_ue = np.array([result[f'upper_bl_ue/vinf_{i}'][0] for i in range(32) if f'upper_bl_ue/vinf_{i}' in result])
70
+ lower_ue = np.array([result[f'lower_bl_ue/vinf_{i}'][0] for i in range(32) if f'lower_bl_ue/vinf_{i}' in result])
71
+
72
+ upper_cp_32 = 1 - upper_ue**2
73
+ lower_cp_32 = 1 - lower_ue**2
74
+
75
+ # Create x positions for 32 points (from leading edge to trailing edge)
76
+ x_bl_upper = np.linspace(0, 1, len(upper_cp_32))
77
+ x_bl_lower = np.linspace(0, 1, len(lower_cp_32))
78
+
79
+ # Find upper and lower surface points
80
+ # Typically airfoil coords go: TE (top) -> LE -> TE (bottom)
81
+ le_idx = np.argmin(coords[:, 0]) # Leading edge is minimum x
82
+
83
+ upper_surface = coords[:le_idx+1] # From TE to LE (top)
84
+ lower_surface = coords[le_idx:] # From LE to TE (bottom)
85
+
86
+ # Get high-resolution x-coordinates and interpolate Cp
87
+ if len(upper_surface) > 1:
88
+ x_upper_hires = upper_surface[::-1, 0] # Reverse to go LE to TE
89
+ y_upper_hires = upper_surface[::-1, 1]
90
+ upper_cp_hires = np.interp(x_upper_hires, x_bl_upper, upper_cp_32)
91
+ else:
92
+ x_upper_hires = np.array([])
93
+ y_upper_hires = np.array([])
94
+ upper_cp_hires = np.array([])
95
+
96
+ if len(lower_surface) > 1:
97
+ x_lower_hires = lower_surface[:, 0] # Already LE to TE
98
+ y_lower_hires = lower_surface[:, 1]
99
+ lower_cp_hires = np.interp(x_lower_hires, x_bl_lower, lower_cp_32)
100
+ else:
101
+ x_lower_hires = np.array([])
102
+ y_lower_hires = np.array([])
103
+ lower_cp_hires = np.array([])
104
+
105
+ # Also get y-coordinates for 32-point data
106
+ if len(upper_surface) > 1:
107
+ y_upper_32 = np.interp(x_bl_upper, upper_surface[::-1, 0], upper_surface[::-1, 1])
108
+ else:
109
+ y_upper_32 = np.zeros_like(x_bl_upper)
110
+
111
+ if len(lower_surface) > 1:
112
+ y_lower_32 = np.interp(x_bl_lower, lower_surface[:, 0], lower_surface[:, 1])
113
+ else:
114
+ y_lower_32 = np.zeros_like(x_bl_lower)
115
+
116
+ # Scale factor for pressure lines
117
+ scale = 0.15 * np.max(coords[:, 1] - np.min(coords[:, 1]))
118
+
119
+ # Calculate pressure line coordinates for high-resolution data
120
+ y_upper_pressure_hires = y_upper_hires - upper_cp_hires * scale
121
+ y_lower_pressure_hires = y_lower_hires + lower_cp_hires * scale
122
+
123
+ # Calculate pressure line coordinates for 32-point data
124
+ y_upper_pressure_32 = y_upper_32 - upper_cp_32 * scale
125
+ y_lower_pressure_32 = y_lower_32 + lower_cp_32 * scale
126
+
127
+ # Plot high-resolution pressure distribution
128
+ ax.plot(x_upper_hires, y_upper_pressure_hires, 'b-', linewidth=2, label='Upper Surface Cp (interpolated)', alpha=0.8)
129
+ ax.plot(x_lower_hires, y_lower_pressure_hires, 'r-', linewidth=2, label='Lower Surface Cp (interpolated)', alpha=0.8)
130
+
131
+ # Plot 32-point data with markers
132
+ ax.plot(x_bl_upper, y_upper_pressure_32, 'bo', markersize=4, label='Upper Surface Cp (32 pts)', alpha=0.6)
133
+ ax.plot(x_bl_lower, y_lower_pressure_32, 'ro', markersize=4, label='Lower Surface Cp (32 pts)', alpha=0.6)
134
+
135
+ # Fill the area between airfoil surface and pressure line
136
+ ax.fill_between(x_upper_hires, y_upper_hires, y_upper_pressure_hires, color='blue', alpha=0.2)
137
+ ax.fill_between(x_lower_hires, y_lower_hires, y_lower_pressure_hires, color='red', alpha=0.2)
138
+
139
+ ax.legend(loc='upper right')
140
+
141
+ ax.set_xlabel('x/c', fontsize=12)
142
+ ax.set_ylabel('y/c', fontsize=12)
143
+ ax.set_title(f'Airfoil with Pressure Distribution (α={alpha}°, Re={reynolds:.1e})', fontsize=14)
144
+ ax.grid(True, alpha=0.3)
145
+ ax.set_aspect('equal')
146
+ ax.axhline(y=0, color='k', linestyle='--', alpha=0.3, linewidth=0.5)
147
+
148
+ plt.tight_layout()
149
+ return fig
150
+
151
+ except Exception as e:
152
+ # Return a simple error plot
153
+ fig, ax = plt.subplots(figsize=(12, 6))
154
+ ax.text(0.5, 0.5, f'Error creating plot: {str(e)}',
155
+ ha='center', va='center', fontsize=12)
156
+ ax.set_xlim(0, 1)
157
+ ax.set_ylim(0, 1)
158
+ return fig
159
+
160
+ def run_neuralfoil_prediction_api(coordinates, alpha, reynolds, model_size):
161
+ """Run NeuralFoil prediction from numpy array/list - for API usage"""
162
+ try:
163
+ # Convert to numpy array if it's a list
164
+ if isinstance(coordinates, list):
165
+ coords = np.array(coordinates)
166
+ else:
167
+ coords = coordinates
168
+
169
+ if len(coords) < 3:
170
+ return {"error": "Invalid coordinates. Please provide at least 3 coordinate pairs."}
171
+
172
+ # Run NeuralFoil analysis directly from coordinates
173
+ result = nf.get_aero_from_coordinates(
174
+ coordinates=coords,
175
+ alpha=alpha,
176
+ Re=reynolds,
177
+ model_size=model_size
178
+ )
179
+
180
+ # Convert result to a serializable dictionary
181
+ output = {}
182
+
183
+ # Standard outputs
184
+ if hasattr(result, 'CL'):
185
+ output['CL'] = float(result.CL) if not np.isnan(result.CL) else None
186
+ if hasattr(result, 'CD'):
187
+ output['CD'] = float(result.CD) if not np.isnan(result.CD) else None
188
+ if hasattr(result, 'CM'):
189
+ output['CM'] = float(result.CM) if not np.isnan(result.CM) else None
190
+
191
+ # Transition locations (if available)
192
+ if hasattr(result, 'Top_Xtr'):
193
+ output['Top_Xtr'] = float(result.Top_Xtr) if not np.isnan(result.Top_Xtr) else None
194
+ if hasattr(result, 'Bot_Xtr'):
195
+ output['Bot_Xtr'] = float(result.Bot_Xtr) if not np.isnan(result.Bot_Xtr) else None
196
+
197
+ # Confidence metric
198
+ if hasattr(result, 'analysis_confidence'):
199
+ output['analysis_confidence'] = float(result.analysis_confidence) if not np.isnan(result.analysis_confidence) else None
200
+
201
+ # Include all other attributes
202
+ for attr in dir(result):
203
+ if not attr.startswith('_') and attr not in output:
204
+ val = getattr(result, attr)
205
+ if isinstance(val, (int, float, str, bool)):
206
+ output[attr] = val
207
+ elif isinstance(val, np.ndarray):
208
+ output[attr] = val.tolist()
209
+
210
+ # Calculate pressure coefficients from edge velocity
211
+ # Cp = 1 - (ue/vinf)^2
212
+ upper_ue = np.array([result[f'upper_bl_ue/vinf_{i}'][0] for i in range(32) if f'upper_bl_ue/vinf_{i}' in result])
213
+ lower_ue = np.array([result[f'lower_bl_ue/vinf_{i}'][0] for i in range(32) if f'lower_bl_ue/vinf_{i}' in result])
214
+
215
+ upper_cp_32 = 1 - upper_ue**2
216
+ lower_cp_32 = 1 - lower_ue**2
217
+
218
+ # Create x positions for the 32 boundary layer stations
219
+ x_bl_upper = np.linspace(0, 1, len(upper_cp_32))
220
+ x_bl_lower = np.linspace(0, 1, len(lower_cp_32))
221
+
222
+ # Split airfoil coordinates into upper and lower surfaces
223
+ le_idx = np.argmin(coords[:, 0]) # Leading edge is minimum x
224
+ upper_surface = coords[:le_idx+1] # From TE to LE (top)
225
+ lower_surface = coords[le_idx:] # From LE to TE (bottom)
226
+
227
+ # Get x-coordinates for upper and lower surfaces (in 0-1 range)
228
+ if len(upper_surface) > 1:
229
+ x_upper_coords = upper_surface[::-1, 0] # Reverse to go LE to TE
230
+ # Interpolate Cp from 32 points to airfoil coordinate resolution
231
+ upper_cp_interp = np.interp(x_upper_coords, x_bl_upper, upper_cp_32)
232
+ else:
233
+ x_upper_coords = np.array([])
234
+ upper_cp_interp = np.array([])
235
+
236
+ if len(lower_surface) > 1:
237
+ x_lower_coords = lower_surface[:, 0] # Already LE to TE
238
+ # Interpolate Cp from 32 points to airfoil coordinate resolution
239
+ lower_cp_interp = np.interp(x_lower_coords, x_bl_lower, lower_cp_32)
240
+ else:
241
+ x_lower_coords = np.array([])
242
+ lower_cp_interp = np.array([])
243
+
244
+ # Add pressure coefficient arrays (both 32-point and interpolated)
245
+ output['pressure_coefficients'] = {
246
+ 'upper_surface_cp': upper_cp_interp.tolist(),
247
+ 'lower_surface_cp': lower_cp_interp.tolist(),
248
+ 'x_upper': x_upper_coords.tolist(),
249
+ 'x_lower': x_lower_coords.tolist(),
250
+ 'upper_surface_cp_32': upper_cp_32.tolist(),
251
+ 'lower_surface_cp_32': lower_cp_32.tolist(),
252
+ 'x_upper_32': x_bl_upper.tolist(),
253
+ 'x_lower_32': x_bl_lower.tolist()
254
+ }
255
+
256
+ # Add input parameters for reference
257
+ output['input_parameters'] = {
258
+ 'alpha_deg': alpha,
259
+ 'reynolds_number': reynolds,
260
+ 'model_size': model_size,
261
+ 'num_coordinates': len(coords)
262
+ }
263
+
264
+ return output
265
+
266
+ except Exception as e:
267
+ return {"error": str(e)}
268
+
269
  def run_neuralfoil_prediction(coords_text, alpha, reynolds, model_size):
270
+ """Run NeuralFoil prediction and return full JSON output and plot - for UI usage"""
271
  try:
272
  # Parse coordinates
273
  coords = parse_coordinates(coords_text)
274
 
275
  if len(coords) < 3:
276
+ error_fig, ax = plt.subplots(figsize=(12, 6))
277
+ ax.text(0.5, 0.5, 'Invalid coordinates. Please provide at least 3 coordinate pairs.',
278
+ ha='center', va='center', fontsize=12)
279
+ ax.set_xlim(0, 1)
280
+ ax.set_ylim(0, 1)
281
+ return json.dumps({"error": "Invalid coordinates. Please provide at least 3 coordinate pairs."}, indent=2), error_fig
282
 
283
  # Run NeuralFoil analysis directly from coordinates
284
  result = nf.get_aero_from_coordinates(
 
287
  Re=reynolds,
288
  model_size=model_size
289
  )
290
+ # print(result)
291
 
292
  # Convert result to a serializable dictionary
293
  output = {}
 
319
  elif isinstance(val, np.ndarray):
320
  output[attr] = val.tolist()
321
 
322
+ # Calculate pressure coefficients from edge velocity
323
+ # Cp = 1 - (ue/vinf)^2
324
+ upper_ue = np.array([result[f'upper_bl_ue/vinf_{i}'][0] for i in range(32) if f'upper_bl_ue/vinf_{i}' in result])
325
+ lower_ue = np.array([result[f'lower_bl_ue/vinf_{i}'][0] for i in range(32) if f'lower_bl_ue/vinf_{i}' in result])
326
+
327
+ upper_cp_32 = 1 - upper_ue**2
328
+ lower_cp_32 = 1 - lower_ue**2
329
+
330
+ # Create x positions for the 32 boundary layer stations
331
+ x_bl_upper = np.linspace(0, 1, len(upper_cp_32))
332
+ x_bl_lower = np.linspace(0, 1, len(lower_cp_32))
333
+
334
+ # Split airfoil coordinates into upper and lower surfaces
335
+ le_idx = np.argmin(coords[:, 0]) # Leading edge is minimum x
336
+ upper_surface = coords[:le_idx+1] # From TE to LE (top)
337
+ lower_surface = coords[le_idx:] # From LE to TE (bottom)
338
+
339
+ # Get x-coordinates for upper and lower surfaces (in 0-1 range)
340
+ if len(upper_surface) > 1:
341
+ x_upper_coords = upper_surface[::-1, 0] # Reverse to go LE to TE
342
+ # Interpolate Cp from 32 points to airfoil coordinate resolution
343
+ upper_cp_interp = np.interp(x_upper_coords, x_bl_upper, upper_cp_32)
344
+ else:
345
+ x_upper_coords = np.array([])
346
+ upper_cp_interp = np.array([])
347
+
348
+ if len(lower_surface) > 1:
349
+ x_lower_coords = lower_surface[:, 0] # Already LE to TE
350
+ # Interpolate Cp from 32 points to airfoil coordinate resolution
351
+ lower_cp_interp = np.interp(x_lower_coords, x_bl_lower, lower_cp_32)
352
+ else:
353
+ x_lower_coords = np.array([])
354
+ lower_cp_interp = np.array([])
355
+
356
+ # Add pressure coefficient arrays (both 32-point and interpolated)
357
+ output['pressure_coefficients'] = {
358
+ 'upper_surface_cp': upper_cp_interp.tolist(),
359
+ 'lower_surface_cp': lower_cp_interp.tolist(),
360
+ 'x_upper': x_upper_coords.tolist(),
361
+ 'x_lower': x_lower_coords.tolist(),
362
+ 'upper_surface_cp_32': upper_cp_32.tolist(),
363
+ 'lower_surface_cp_32': lower_cp_32.tolist(),
364
+ 'x_upper_32': x_bl_upper.tolist(),
365
+ 'x_lower_32': x_bl_lower.tolist()
366
+ }
367
+
368
  # Add input parameters for reference
369
  output['input_parameters'] = {
370
  'alpha_deg': alpha,
 
372
  'model_size': model_size,
373
  'num_coordinates': len(coords)
374
  }
375
+
376
+ # Use the API function to get results
377
+ output = run_neuralfoil_prediction_api(coords, alpha, reynolds, model_size)
378
 
379
+ if "error" in output:
380
+ error_fig, ax = plt.subplots(figsize=(12, 6))
381
+ ax.text(0.5, 0.5, output["error"], ha='center', va='center', fontsize=12)
382
+ ax.set_xlim(0, 1)
383
+ ax.set_ylim(0, 1)
384
+ return json.dumps(output, indent=2), error_fig
385
+
386
+ # Get result back for plotting
387
+ result = nf.get_aero_from_coordinates(
388
+ coordinates=coords,
389
+ alpha=alpha,
390
+ Re=reynolds,
391
+ model_size=model_size
392
+ )
393
+
394
+ # Create the pressure plot
395
+ fig = create_pressure_plot(coords, result, alpha, reynolds)
396
+
397
+ return json.dumps(output, indent=2), fig
398
 
399
  except Exception as e:
400
+ error_fig, ax = plt.subplots(figsize=(12, 6))
401
+ ax.text(0.5, 0.5, f'Error: {str(e)}', ha='center', va='center', fontsize=12)
402
+ ax.set_xlim(0, 1)
403
+ ax.set_ylim(0, 1)
404
+ return json.dumps({"error": str(e)}, indent=2), error_fig
405
 
406
  def load_example(example_name):
407
  """Load an example airfoil"""
 
417
  return format_coordinates(coords)
418
  return ""
419
 
420
+ # Load default airfoil (RAE 2822)
421
+ default_coords = format_coordinates(load_airfoil_from_dat("examples/rae2822.dat"))
422
 
423
  # Create Gradio interface
424
  with gr.Blocks(title="NeuralFoil Airfoil Predictor") as demo:
 
446
  example_buttons = gr.Radio(
447
  choices=["NACA 4412", "Clark Y", "RAE 2822"],
448
  label="Example Airfoils",
449
+ value="RAE 2822"
450
  )
451
 
452
  load_btn = gr.Button("Load Example")
 
474
  predict_btn = gr.Button("Run Prediction", variant="primary")
475
 
476
  with gr.Column():
477
+ gr.Markdown("### Airfoil with Pressure Distribution")
478
+ output_plot = gr.Plot(label="Pressure Distribution")
479
+
480
  gr.Markdown("### Full NeuralFoil Output (JSON)")
481
  output_json = gr.Textbox(
482
  label="Prediction Results",
483
+ lines=20,
484
+ max_lines=30,
485
  placeholder="Results will appear here..."
486
  )
487
 
 
495
  predict_btn.click(
496
  fn=run_neuralfoil_prediction,
497
  inputs=[coords_input, alpha_input, reynolds_input, model_size_input],
498
+ outputs=[output_json, output_plot]
499
  )
500
 
501
  gr.Markdown("""
 
507
  **Citation**: If you use NeuralFoil, please cite the [GitHub repository](https://github.com/peterdsharpe/NeuralFoil) and Peter Sharpe's PhD thesis.
508
  """)
509
 
510
+ # Create API endpoint that accepts numpy arrays
511
+ api = gr.Interface(
512
+ fn=run_neuralfoil_prediction_api,
513
+ inputs=[
514
+ gr.JSON(label="Coordinates (2D array: [[x1,y1], [x2,y2], ...])"),
515
+ gr.Number(label="Angle of Attack α [deg]"),
516
+ gr.Number(label="Reynolds Number Re [-]"),
517
+ gr.Dropdown(choices=["xxsmall", "xsmall", "small", "medium", "large", "xlarge", "xxlarge", "xxxlarge"], label="Model Size")
518
+ ],
519
+ outputs=gr.JSON(label="Prediction Results"),
520
+ title="NeuralFoil API",
521
+ description="API endpoint for programmatic access. Input coordinates as a 2D array."
522
+ )
523
+
524
+ # Combine both interfaces in tabs
525
+ app = gr.TabbedInterface(
526
+ [demo, api],
527
+ ["Interactive UI", "API"]
528
+ )
529
+
530
  if __name__ == "__main__":
531
+ app.launch()
requirements.txt CHANGED
@@ -3,3 +3,4 @@ gradio>=4.0.0,<4.45.0
3
  neuralfoil>=0.2.0
4
  aerosandbox>=4.0.0
5
  numpy>=1.24.0
 
 
3
  neuralfoil>=0.2.0
4
  aerosandbox>=4.0.0
5
  numpy>=1.24.0
6
+ matplotlib>=3.5.0