Vishwas1 commited on
Commit
bcdf3c7
·
verified ·
1 Parent(s): 1453284

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +89 -128
app.py CHANGED
@@ -1,32 +1,20 @@
1
- import math
2
- from typing import Dict, List, Optional
3
-
4
  import gradio as gr
5
  import pandas as pd
6
  import numpy as np
7
  import matplotlib.pyplot as plt
 
8
  from periodictable import elements
9
 
10
- # -----------------------------
11
- # Helpers
12
- # -----------------------------
13
  def to_float(x):
14
- """Coerce periodictable numeric (incl. uncertainties) to plain float, else NaN."""
15
  if x is None:
16
  return np.nan
 
17
  try:
18
- # uncertainties.UFloat has .nominal_value
19
- v = getattr(x, "nominal_value", x)
20
  return float(v)
21
  except Exception:
22
- try:
23
- return float(x)
24
- except Exception:
25
- return np.nan
26
 
27
- # -----------------------------
28
- # Data
29
- # -----------------------------
30
  NUMERIC_PROPS = [
31
  ("mass", "Atomic mass (u)"),
32
  ("density", "Density (g/cm^3)"),
@@ -38,42 +26,40 @@ NUMERIC_PROPS = [
38
  ]
39
 
40
  CURATED_FACTS: Dict[str, List[str]] = {
41
- "H": ["Lightest element; ~74% of the visible universe by mass is hydrogen in stars."],
42
- "He": ["Inert, used in cryogenics and balloons; second lightest element."],
43
- "Li": ["Batteries MVP: lithium-ion cells power phones and EVs."],
44
- "C": ["Backbone of life; diamond and graphite are pure carbon with wildly different properties."],
45
- "N": ["~78% of Earth's atmosphere is nitrogen (mostly N₂)."],
46
- "O": ["Essential for respiration; ~21% of Earth's atmosphere."],
47
- "Na": ["Sodium metal reacts violently with water—handle only under oil or inert gas."],
48
- "Mg": ["Burns with a bright white flame; used in flares and fireworks."],
49
- "Al": ["Light and strong; forms a protective oxide layer that resists corrosion."],
50
- "Si": ["Silicon is the basis of modern electronics—hello, semiconductors."],
51
- "Cl": ["Powerful disinfectant; elemental chlorine is toxic, compounds are widely useful."],
52
- "Ar": ["Argon is used to provide inert atmospheres for welding and 3D printing."],
53
- "Fe": ["Core of steel; iron is essential in hemoglobin for oxygen transport."],
54
- "Cu": ["Excellent electrical conductor; iconic blue-green patina (verdigris)."],
55
- "Ag": ["Highest electrical conductivity of all metals; historically used as currency."],
56
- "Au": ["Very unreactive ('noble'); prized for electronics and jewelry."],
57
- "Hg": ["Only metal that's liquid at room temperature; toxic—use with care."],
58
- "Pb": ["Dense and malleable; toxicity led to phase-out from gasoline and paints."],
59
- "U": ["Radioactive; used as nuclear reactor fuel (U-235)."],
60
- "Pu": ["Man-made in quantity; key in certain nuclear technologies."],
61
- "F": ["Most electronegative element; extremely reactive."],
62
- "Ne": ["Neon glows striking red-orange in discharge tubes—classic signs."],
63
- "Xe": ["Xenon makes bright camera flashes and high-intensity lamps."],
64
  }
65
 
66
  GROUP_FACTS = {
67
- "alkali": "Alkali metal: very reactive soft metal; forms +1 cations and reacts with water.",
68
- "alkaline-earth": "Alkaline earth metal: reactive (less than Group 1); forms +2 cations.",
69
- "transition": "Transition metal: often good catalysts, colorful compounds, multiple oxidation states.",
70
- "post-transition": "Post-transition metal: softer metals with lower melting points than transition metals.",
71
- "metalloid": "Metalloid: properties between metals and nonmetals; often semiconductors.",
72
- "nonmetal": "Nonmetal: tends to form covalent compounds; wide range of roles in biology and materials.",
73
- "halogen": "Halogen: very reactive nonmetals; form salts with metals and −1 oxidation state.",
74
- "noble-gas": "Noble gas: chemically inert under most conditions; monatomic gases.",
75
- "lanthanide": "Lanthanide: f-block rare earths; notable for magnets, lasers, and phosphors.",
76
- "actinide": "Actinide: radioactive f-block; includes nuclear fuel materials.",
77
  }
78
 
79
  def classify_category(el) -> str:
@@ -82,12 +68,12 @@ def classify_category(el) -> str:
82
  return "alkali"
83
  if el.block == "s" and el.group == 2:
84
  return "alkaline-earth"
 
 
85
  if el.block == "p" and el.group == 17:
86
  return "halogen"
87
  if el.block == "p" and el.group == 18:
88
  return "noble-gas"
89
- if el.block == "d":
90
- return "transition"
91
  if el.block == "f" and 57 <= el.number <= 71:
92
  return "lanthanide"
93
  if el.block == "f" and 89 <= el.number <= 103:
@@ -106,12 +92,12 @@ def build_elements_df() -> pd.DataFrame:
106
  el = elements[Z]
107
  if el is None:
108
  continue
109
- data = {
110
  "Z": el.number,
111
  "symbol": el.symbol,
112
  "name": el.name.title(),
113
  "period": getattr(el, "period", None),
114
- "group": getattr(el, "group", None), # may be None for many
115
  "block": getattr(el, "block", None),
116
  "mass": to_float(getattr(el, "mass", None)),
117
  "density": to_float(getattr(el, "density", None)),
@@ -122,54 +108,35 @@ def build_elements_df() -> pd.DataFrame:
122
  "covalent_radius": to_float(getattr(el, "covalent_radius", None)),
123
  "category": classify_category(el),
124
  "is_radioactive": bool(getattr(el, "radioactive", False)),
125
- }
126
- rows.append(data)
127
  return pd.DataFrame(rows).sort_values("Z").reset_index(drop=True)
128
 
129
  DF = build_elements_df()
130
 
131
- # -----------------------------
132
- # Build a robust grid (no reliance on group from the lib)
133
- # Rules: s->groups 1-2, d->3..12, p->13..18; period 1 special (H at 1, He at 18)
134
- # f-block shown separately.
135
- # -----------------------------
136
- MAX_GROUP = 18
137
- MAX_PERIOD = 7
138
- GRID: List[List[Optional[int]]] = [[None for _ in range(MAX_GROUP)] for _ in range(MAX_PERIOD)]
139
-
140
- for period in range(1, MAX_PERIOD + 1):
141
- rows = DF[DF["period"] == period].sort_values("Z")
142
- s = rows[rows["block"] == "s"]["Z"].tolist()
143
- d = rows[rows["block"] == "d"]["Z"].tolist()
144
- p = rows[rows["block"] == "p"]["Z"].tolist()
145
- # Period 1 special case
146
- if period == 1:
147
- # Expect H then He
148
- if len(s) >= 1:
149
- GRID[0][0] = int(s[0]) # H -> group 1
150
- if len(p) >= 1:
151
- GRID[0][17] = int(p[-1]) # He -> group 18
152
- continue
153
- # s-block (usually 2)
154
- if len(s) >= 1:
155
- GRID[period - 1][0] = int(s[0]) # group 1
156
- if len(s) >= 2:
157
- GRID[period - 1][1] = int(s[1]) # group 2
158
- # d-block (10 wide), only in periods >= 4
159
- for i, z in enumerate(d):
160
- if i < 10:
161
- GRID[period - 1][2 + i] = int(z) # groups 3..12
162
- # p-block (6 wide)
163
- for i, z in enumerate(p[-6:]): # last 6 p-block in order
164
- GRID[period - 1][12 + i] = int(z) # groups 13..18
165
 
166
- # f-block lists (lanthanides/actinides)
167
- LAN = [int(z) for z in DF["Z"] if 57 <= int(z) <= 71]
168
- ACT = [int(z) for z in DF["Z"] if 89 <= int(z) <= 103]
169
 
170
- # -----------------------------
171
- # Plotting (Matplotlib -> gr.Plot)
172
- # -----------------------------
173
  def plot_trend(trend_df: pd.DataFrame, prop_key: str, Z: int, symbol: str):
174
  fig, ax = plt.subplots()
175
  ax.scatter(trend_df["Z"], trend_df[prop_key])
@@ -185,9 +152,10 @@ def plot_trend(trend_df: pd.DataFrame, prop_key: str, Z: int, symbol: str):
185
 
186
  def plot_heatmap(property_key: str):
187
  prop_label = dict(NUMERIC_PROPS)[property_key]
188
- grid_vals = np.full((MAX_PERIOD, MAX_GROUP), np.nan, dtype=float)
189
- for r in range(MAX_PERIOD):
190
- for c in range(MAX_GROUP):
 
191
  z = GRID[r][c]
192
  if z is None:
193
  continue
@@ -196,10 +164,10 @@ def plot_heatmap(property_key: str):
196
  grid_vals[r, c] = float(val)
197
  fig, ax = plt.subplots()
198
  im = ax.imshow(grid_vals, origin="upper", aspect="auto")
199
- ax.set_xticks(range(MAX_GROUP))
200
- ax.set_xticklabels([str(i) for i in range(1, MAX_GROUP + 1)])
201
- ax.set_yticks(range(MAX_PERIOD))
202
- ax.set_yticklabels([str(i) for i in range(1, MAX_PERIOD + 1)])
203
  ax.set_xlabel("Group")
204
  ax.set_ylabel("Period")
205
  ax.set_title(f"Periodic heatmap: {prop_label}")
@@ -207,9 +175,7 @@ def plot_heatmap(property_key: str):
207
  fig.tight_layout()
208
  return fig
209
 
210
- # -----------------------------
211
- # Callbacks
212
- # -----------------------------
213
  def element_info(z_or_symbol: str):
214
  try:
215
  if z_or_symbol.isdigit():
@@ -229,18 +195,19 @@ def element_info(z_or_symbol: str):
229
  facts.append(GROUP_FACTS.get(row["category"], None))
230
  facts = [f for f in facts if f]
231
 
 
 
 
232
  props_lines = [
233
  f"{row['name']} ({symbol}), Z = {Z}",
234
  f"Period {int(row['period']) if not pd.isna(row['period']) else '—'}, "
235
  f"Group {row['group'] if row['group'] is not None else '—'}, "
236
  f"Block {row['block']} | Category: {row['category'].replace('-', ' ').title()}",
237
- f"Atomic mass: {row['mass'] if not pd.isna(row['mass']) else '—'} u",
238
- f"Density: {row['density'] if not pd.isna(row['density']) else '—'} g/cm³",
239
- f"Electronegativity: {row['electronegativity'] if not pd.isna(row['electronegativity']) else '—'} (Pauling)",
240
- f"Melting point: {row['melting_point'] if not pd.isna(row['melting_point']) else '—'} K | "
241
- f"Boiling point: {row['boiling_point'] if not pd.isna(row['boiling_point']) else '—'} K",
242
- f"vdW radius: {row['vdw_radius'] if not pd.isna(row['vdw_radius']) else '—'} pm | "
243
- f"Covalent radius: {row['covalent_radius'] if not pd.isna(row['covalent_radius']) else '—'} pm",
244
  f"Radioactive: {'Yes' if row['is_radioactive'] else 'No'}",
245
  ]
246
  info_text = "\n".join(props_lines)
@@ -249,7 +216,6 @@ def element_info(z_or_symbol: str):
249
  prop_key = "electronegativity" if not pd.isna(row["electronegativity"]) else "mass"
250
  trend_df = DF[["Z", "symbol", prop_key]].dropna()
251
  fig = plot_trend(trend_df, prop_key, Z, symbol)
252
-
253
  return info_text, facts_text, fig
254
 
255
  def handle_button_click(z: int):
@@ -261,14 +227,12 @@ def search_element(query: str):
261
  return gr.update(), gr.update(), gr.update()
262
  return element_info(query)
263
 
264
- # -----------------------------
265
- # UI (Gradio 4.29.0 compatible)
266
- # -----------------------------
267
  with gr.Blocks(title="Interactive Periodic Table") as demo:
268
- gr.Markdown("# 🧪 Interactive Periodic Table\nClick an element or search by symbol/name/atomic number.")
269
 
270
  with gr.Row():
271
- # Inspector first so buttons can target these outputs
272
  with gr.Column(scale=1):
273
  gr.Markdown("### Inspector")
274
  search = gr.Textbox(label="Search (symbol/name/Z)", placeholder="e.g., C, Iron, 79")
@@ -283,16 +247,15 @@ with gr.Blocks(title="Interactive Periodic Table") as demo:
283
  prop.change(lambda k: plot_heatmap(k), inputs=[prop], outputs=[heat])
284
  demo.load(lambda: plot_heatmap("electronegativity"), outputs=[heat])
285
 
 
286
  with gr.Column(scale=2):
287
  gr.Markdown("### Main Table")
288
- # Group headers (1..18)
289
  with gr.Row():
290
  for g in range(1, 19):
291
  gr.Markdown(f"**{g}**")
292
- # Grid of element buttons
293
- for r in range(MAX_PERIOD):
294
  with gr.Row():
295
- for c in range(MAX_GROUP):
296
  z = GRID[r][c]
297
  if z is None:
298
  gr.Button("", interactive=False)
@@ -306,15 +269,13 @@ with gr.Blocks(title="Interactive Periodic Table") as demo:
306
  with gr.Row():
307
  for z in LAN:
308
  sym = DF.loc[DF["Z"] == z, "symbol"].values[0]
309
- btn = gr.Button(sym)
310
- btn.click(handle_button_click, inputs=[gr.Number(z, visible=False)],
311
- outputs=[info, facts, trend])
312
  with gr.Row():
313
  for z in ACT:
314
  sym = DF.loc[DF["Z"] == z, "symbol"].values[0]
315
- btn = gr.Button(sym)
316
- btn.click(handle_button_click, inputs=[gr.Number(z, visible=False)],
317
- outputs=[info, facts, trend])
318
 
319
  if __name__ == "__main__":
320
  demo.launch()
 
 
 
 
1
  import gradio as gr
2
  import pandas as pd
3
  import numpy as np
4
  import matplotlib.pyplot as plt
5
+ from typing import Dict, List, Optional
6
  from periodictable import elements
7
 
8
+ # ---------- helpers ----------
 
 
9
  def to_float(x):
 
10
  if x is None:
11
  return np.nan
12
+ v = getattr(x, "nominal_value", x) # handles uncertainties.UFloat
13
  try:
 
 
14
  return float(v)
15
  except Exception:
16
+ return np.nan
 
 
 
17
 
 
 
 
18
  NUMERIC_PROPS = [
19
  ("mass", "Atomic mass (u)"),
20
  ("density", "Density (g/cm^3)"),
 
26
  ]
27
 
28
  CURATED_FACTS: Dict[str, List[str]] = {
29
+ "H": ["Lightest element; ~74% of visible matter is H in stars."],
30
+ "He": ["Inert and super light; cryogenics & balloons."],
31
+ "Li": ["Lithium-ion batteries power phones & EVs."],
32
+ "C": ["Diamond vs graphite = same element, different structure."],
33
+ "N": ["~78% of Earth's atmosphere is N₂."],
34
+ "O": ["~21% of air; essential for respiration."],
35
+ "Na": ["Reacts violently with water."],
36
+ "Mg": ["Bright white flame in flares."],
37
+ "Si": ["Semiconductor backbone."],
38
+ "Cl": ["Disinfectant; elemental Cl₂ is toxic."],
39
+ "Fe": ["Steel core; oxygen transport in blood (heme)."],
40
+ "Cu": ["Great conductor; forms green patina."],
41
+ "Ag": ["Highest electrical conductivity."],
42
+ "Au": ["Very unreactive; great for electronics/jewelry."],
43
+ "Hg": ["Liquid metal at room temp; toxic."],
44
+ "Pb": ["Dense, malleable; toxic—phase-out in fuels/paints."],
45
+ "U": ["Reactor fuel (U-235)."],
46
+ "Pu": ["Man-made in quantity; nuclear uses."],
47
+ "F": ["Most electronegative; extremely reactive."],
48
+ "Ne": ["Classic red-orange neon glow."],
49
+ "Xe": ["Used in bright flashes/HID lamps."],
 
 
50
  }
51
 
52
  GROUP_FACTS = {
53
+ "alkali": "Alkali metal: very reactive; forms +1 cations; reacts with water.",
54
+ "alkaline-earth": "Alkaline earth metal: reactive; forms +2 cations.",
55
+ "transition": "Transition metal: catalysts, colorful compounds, multiple oxidation states.",
56
+ "post-transition": "Post-transition metal: softer, lower melting than transition metals.",
57
+ "metalloid": "Metalloid: between metals and nonmetals; often semiconductors.",
58
+ "nonmetal": "Nonmetal: forms covalent compounds; huge biological roles.",
59
+ "halogen": "Halogen: very reactive nonmetals; −1 state; forms salts.",
60
+ "noble-gas": "Noble gas: inert, monatomic gases.",
61
+ "lanthanide": "Lanthanide: rare earths; magnets, lasers, phosphors.",
62
+ "actinide": "Actinide: radioactive; nuclear materials.",
63
  }
64
 
65
  def classify_category(el) -> str:
 
68
  return "alkali"
69
  if el.block == "s" and el.group == 2:
70
  return "alkaline-earth"
71
+ if el.block == "d":
72
+ return "transition"
73
  if el.block == "p" and el.group == 17:
74
  return "halogen"
75
  if el.block == "p" and el.group == 18:
76
  return "noble-gas"
 
 
77
  if el.block == "f" and 57 <= el.number <= 71:
78
  return "lanthanide"
79
  if el.block == "f" and 89 <= el.number <= 103:
 
92
  el = elements[Z]
93
  if el is None:
94
  continue
95
+ rows.append({
96
  "Z": el.number,
97
  "symbol": el.symbol,
98
  "name": el.name.title(),
99
  "period": getattr(el, "period", None),
100
+ "group": getattr(el, "group", None),
101
  "block": getattr(el, "block", None),
102
  "mass": to_float(getattr(el, "mass", None)),
103
  "density": to_float(getattr(el, "density", None)),
 
108
  "covalent_radius": to_float(getattr(el, "covalent_radius", None)),
109
  "category": classify_category(el),
110
  "is_radioactive": bool(getattr(el, "radioactive", False)),
111
+ })
 
112
  return pd.DataFrame(rows).sort_values("Z").reset_index(drop=True)
113
 
114
  DF = build_elements_df()
115
 
116
+ # ---------- hardcoded main-grid layout (periods 1–7, groups 1–18) ----------
117
+ # None = empty cell; numbers = atomic numbers
118
+ GRID = [
119
+ # P1
120
+ [1, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, 2],
121
+ # P2
122
+ [3, 4, None, None, None, None, None, None, None, None, None, None, 5, 6, 7, 8, 9, 10],
123
+ # P3
124
+ [11, 12, None, None, None, None, None, None, None, None, None, None, 13, 14, 15, 16, 17, 18],
125
+ # P4
126
+ [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36],
127
+ # P5
128
+ [37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54],
129
+ # P6 (La shown at group 3)
130
+ [55, 56, 57, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86],
131
+ # P7 (Ac shown at group 3)
132
+ [87, 88, 89, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118],
133
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
 
135
+ # f-block lists we display separately (omit La & Ac because they’re in the main grid)
136
+ LAN = list(range(58, 72)) # Ce..Lu
137
+ ACT = list(range(90, 104)) # Th..Lr
138
 
139
+ # ---------- plotting ----------
 
 
140
  def plot_trend(trend_df: pd.DataFrame, prop_key: str, Z: int, symbol: str):
141
  fig, ax = plt.subplots()
142
  ax.scatter(trend_df["Z"], trend_df[prop_key])
 
152
 
153
  def plot_heatmap(property_key: str):
154
  prop_label = dict(NUMERIC_PROPS)[property_key]
155
+ max_period, max_group = len(GRID), len(GRID[0])
156
+ grid_vals = np.full((max_period, max_group), np.nan, dtype=float)
157
+ for r in range(max_period):
158
+ for c in range(max_group):
159
  z = GRID[r][c]
160
  if z is None:
161
  continue
 
164
  grid_vals[r, c] = float(val)
165
  fig, ax = plt.subplots()
166
  im = ax.imshow(grid_vals, origin="upper", aspect="auto")
167
+ ax.set_xticks(range(max_group))
168
+ ax.set_xticklabels([str(i) for i in range(1, max_group + 1)])
169
+ ax.set_yticks(range(max_period))
170
+ ax.set_yticklabels([str(i) for i in range(1, max_period + 1)])
171
  ax.set_xlabel("Group")
172
  ax.set_ylabel("Period")
173
  ax.set_title(f"Periodic heatmap: {prop_label}")
 
175
  fig.tight_layout()
176
  return fig
177
 
178
+ # ---------- callbacks ----------
 
 
179
  def element_info(z_or_symbol: str):
180
  try:
181
  if z_or_symbol.isdigit():
 
195
  facts.append(GROUP_FACTS.get(row["category"], None))
196
  facts = [f for f in facts if f]
197
 
198
+ def show(v): # nicer NaN -> —
199
+ return v if (v is not None and not pd.isna(v)) else "—"
200
+
201
  props_lines = [
202
  f"{row['name']} ({symbol}), Z = {Z}",
203
  f"Period {int(row['period']) if not pd.isna(row['period']) else '—'}, "
204
  f"Group {row['group'] if row['group'] is not None else '—'}, "
205
  f"Block {row['block']} | Category: {row['category'].replace('-', ' ').title()}",
206
+ f"Atomic mass: {show(row['mass'])} u",
207
+ f"Density: {show(row['density'])} g/cm³",
208
+ f"Electronegativity: {show(row['electronegativity'])} (Pauling)",
209
+ f"Melting point: {show(row['melting_point'])} K | Boiling point: {show(row['boiling_point'])} K",
210
+ f"vdW radius: {show(row['vdw_radius'])} pm | Covalent radius: {show(row['covalent_radius'])} pm",
 
 
211
  f"Radioactive: {'Yes' if row['is_radioactive'] else 'No'}",
212
  ]
213
  info_text = "\n".join(props_lines)
 
216
  prop_key = "electronegativity" if not pd.isna(row["electronegativity"]) else "mass"
217
  trend_df = DF[["Z", "symbol", prop_key]].dropna()
218
  fig = plot_trend(trend_df, prop_key, Z, symbol)
 
219
  return info_text, facts_text, fig
220
 
221
  def handle_button_click(z: int):
 
227
  return gr.update(), gr.update(), gr.update()
228
  return element_info(query)
229
 
230
+ # ---------- UI ----------
 
 
231
  with gr.Blocks(title="Interactive Periodic Table") as demo:
232
+ gr.Markdown("Click an element or search by symbol/name/atomic number.")
233
 
234
  with gr.Row():
235
+ # Inspector
236
  with gr.Column(scale=1):
237
  gr.Markdown("### Inspector")
238
  search = gr.Textbox(label="Search (symbol/name/Z)", placeholder="e.g., C, Iron, 79")
 
247
  prop.change(lambda k: plot_heatmap(k), inputs=[prop], outputs=[heat])
248
  demo.load(lambda: plot_heatmap("electronegativity"), outputs=[heat])
249
 
250
+ # Main table
251
  with gr.Column(scale=2):
252
  gr.Markdown("### Main Table")
 
253
  with gr.Row():
254
  for g in range(1, 19):
255
  gr.Markdown(f"**{g}**")
256
+ for r in range(len(GRID)):
 
257
  with gr.Row():
258
+ for c in range(len(GRID[0])):
259
  z = GRID[r][c]
260
  if z is None:
261
  gr.Button("", interactive=False)
 
269
  with gr.Row():
270
  for z in LAN:
271
  sym = DF.loc[DF["Z"] == z, "symbol"].values[0]
272
+ gr.Button(sym).click(handle_button_click, inputs=[gr.Number(z, visible=False)],
273
+ outputs=[info, facts, trend])
 
274
  with gr.Row():
275
  for z in ACT:
276
  sym = DF.loc[DF["Z"] == z, "symbol"].values[0]
277
+ gr.Button(sym).click(handle_button_click, inputs=[gr.Number(z, visible=False)],
278
+ outputs=[info, facts, trend])
 
279
 
280
  if __name__ == "__main__":
281
  demo.launch()