TahaRasouli commited on
Commit
bb651d9
·
verified ·
1 Parent(s): 18a9ae2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +33 -45
app.py CHANGED
@@ -10,7 +10,7 @@ import math
10
  from datetime import datetime
11
 
12
  # ==========================================
13
- # 1. JSON & HELPER LOGIC (Kept same)
14
  # ==========================================
15
  def get_sorted_nodes(G):
16
  return sorted(list(G.nodes()), key=lambda l: (l[0], l[1]))
@@ -430,10 +430,9 @@ IMG_WIDTH_PX = 800
430
  IMG_HEIGHT_PX = 800
431
 
432
  def plot_graph_to_image(graph, width, height, title="Network"):
433
- # Create figure with exact pixel control (no margins)
434
  dpi = 100
435
  fig = plt.figure(figsize=(IMG_WIDTH_PX/dpi, IMG_HEIGHT_PX/dpi), dpi=dpi)
436
- ax = fig.add_axes([0, 0, 1, 1]) # 0% to 100% of figure, no borders
437
 
438
  pos = {node: (node[0], node[1]) for node in graph.nodes()}
439
  nx.draw_networkx_edges(graph, pos, ax=ax, width=2, alpha=0.6, edge_color="#333")
@@ -443,18 +442,12 @@ def plot_graph_to_image(graph, width, height, title="Network"):
443
  labels = {node: str(i+1) for i, node in enumerate(sorted_nodes)}
444
  nx.draw_networkx_labels(graph, pos, labels, ax=ax, font_size=8, font_color="white", font_weight="bold")
445
 
446
- # Exact Limits Mapping:
447
- # Grid goes 0..Width. We want Click (0,0) to be Top-Left.
448
- # We map 0..Width to X, 0..Height to Y.
449
- # To center nodes, we set limits -0.5 to Width+0.5
450
  ax.set_xlim(-0.5, width + 0.5)
451
- ax.set_ylim(height + 0.5, -0.5) # Inverted Y
452
 
453
- # Draw faint grid
454
  ax.grid(True, linestyle=':', alpha=0.3)
455
  ax.set_axis_on()
456
 
457
- # Save to temp file
458
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
459
  fname = f"temp_plot_{timestamp}.png"
460
  plt.savefig(fname)
@@ -464,50 +457,32 @@ def plot_graph_to_image(graph, width, height, title="Network"):
464
  def handle_plot_click(evt: gr.SelectData, state_data):
465
  if not state_data or "graph" not in state_data: return None, "Generate first.", state_data
466
 
467
- # 1. Get click coordinates (pixels)
468
  click_x, click_y = evt.index
469
  width = state_data["width"]
470
  height = state_data["height"]
471
 
472
- # 2. Map Pixels -> Grid
473
- # Logic: Total pixels = 800. Range = Width + 1.
474
- # NormX = ClickX / 800.
475
- # DataX = NormX * (Width + 1) - 0.5
476
- # GridX = round(DataX)
477
-
478
  range_x = width + 1.0
479
  range_y = height + 1.0
480
-
481
  norm_x = click_x / IMG_WIDTH_PX
482
  norm_y = click_y / IMG_HEIGHT_PX
483
-
484
- # Since limits are -0.5 to W+0.5, total span is W+1
485
  data_x = norm_x * range_x - 0.5
486
  data_y = norm_y * range_y - 0.5
487
-
488
  grid_x = int(round(data_x))
489
  grid_y = int(round(data_y))
490
 
491
- # 3. Perform Action (Smart Toggle)
492
  gen = NetworkGenerator(width, height)
493
  gen.graph = state_data["graph"]
494
 
495
- # Check bounds
496
  if 0 <= grid_x <= width and 0 <= grid_y <= height:
497
  if gen.graph.has_node((grid_x, grid_y)):
498
- # Delete
499
  success, msg = gen.manual_delete_node(grid_x, grid_y)
500
  action = "Deleted"
501
  else:
502
- # Add
503
  success, msg = gen.manual_add_node(grid_x, grid_y)
504
  action = "Added"
505
  else:
506
- success = False
507
- msg = "Clicked outside grid."
508
- action = "Ignored"
509
 
510
- # 4. Refresh
511
  if success:
512
  state_data["graph"] = gen.graph
513
  img_path = plot_graph_to_image(gen.graph, width, height)
@@ -516,10 +491,9 @@ def handle_plot_click(evt: gr.SelectData, state_data):
516
  else:
517
  return gr.update(), f"Error: {msg}", state_data
518
 
519
- # ... (Previous helper functions get_preset_dims, update_ui_for_variant, etc. remain same) ...
520
  def get_preset_dims(preset_mode, topology):
521
  if preset_mode == "Custom": return gr.update(interactive=True), gr.update(interactive=True)
522
- dims = (6, 11) if topology=="linear" and preset_mode=="Medium" else (8,8) # Simplified for brevity
523
  if preset_mode == "Small": dims = (4, 4)
524
  if preset_mode == "Large": dims = (16, 16) if topology!="linear" else (10, 26)
525
  return gr.update(value=dims[0], interactive=False), gr.update(value=dims[1], interactive=False)
@@ -540,16 +514,14 @@ def generate_and_store(topology, width, height, variant, void_frac, t_nodes, t_e
540
  if variant == "Fixed": t_nodes, t_edges = 0, 0
541
  gen = NetworkGenerator(width, height, var_code, topology, void_frac, t_nodes, t_edges)
542
  graph = gen.generate()
543
-
544
  img_path = plot_graph_to_image(graph, width, height)
545
  metrics = f"**Nodes:** {len(graph.nodes())} | **Edges:** {len(graph.edges())} | **Density:** {nx.density(graph):.2f}"
546
  state_data = { "graph": graph, "width": width, "height": height, "topology": topology }
547
- return img_path, metrics, state_data, gr.update(interactive=True), gr.update(interactive=True)
548
  except Exception as e:
549
- return None, f"Error: {e}", None, gr.update(interactive=False), gr.update(interactive=False)
550
 
551
  def run_batch_generation(count, topology, width, height, variant, void_frac, t_nodes, t_edges):
552
- # (Same batch logic as before)
553
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
554
  dir_name = f"batch_{timestamp}"
555
  os.makedirs(dir_name, exist_ok=True)
@@ -568,6 +540,15 @@ def run_batch_generation(count, topology, width, height, variant, void_frac, t_n
568
  except Exception as e:
569
  return None
570
 
 
 
 
 
 
 
 
 
 
571
  # ==========================================
572
  # GRADIO UI
573
  # ==========================================
@@ -584,14 +565,20 @@ with gr.Blocks(title="Interactive Network Generator") as demo:
584
  with gr.Row():
585
  width = gr.Number(8, label="Width", interactive=False)
586
  height = gr.Number(8, label="Height", interactive=False)
587
- variant = gr.Dropdown(["Fixed", "Custom"], value="Fixed", label="Variant")
 
 
 
 
 
 
588
  void_frac = gr.Slider(0.0, 0.9, 0.35, label="Void Fraction", interactive=False)
589
- gr.Markdown("### Custom Overrides")
590
- with gr.Row():
591
- t_nodes = gr.Number(0, label="Nodes", interactive=False)
592
- t_edges = gr.Number(0, label="Edges", interactive=False)
593
  gen_btn = gr.Button("Generate", variant="primary")
594
-
 
 
 
595
  with gr.Tab("Batch"):
596
  batch_count = gr.Slider(1, 50, 5, step=1, label="Count")
597
  batch_btn = gr.Button("Generate Batch ZIP")
@@ -599,8 +586,6 @@ with gr.Blocks(title="Interactive Network Generator") as demo:
599
 
600
  with gr.Column(scale=2):
601
  metrics = gr.Markdown("Ready.")
602
- # Important: interactive=False prevents user from uploading their own image,
603
- # but select events still fire.
604
  plot_img = gr.Image(label="Interactive Graph", interactive=False, height=800, width=800)
605
 
606
  # EVENTS
@@ -615,11 +600,14 @@ with gr.Blocks(title="Interactive Network Generator") as demo:
615
  topology.change(update_ui_for_variant, inputs_var, [void_frac, t_nodes, t_edges])
616
 
617
  gen_args = [topology, width, height, variant, void_frac, t_nodes, t_edges]
618
- gen_btn.click(generate_and_store, gen_args, [plot_img, metrics, state])
619
 
620
- # CLICK EVENT
621
  plot_img.select(handle_plot_click, [state], [plot_img, metrics, state])
622
 
 
 
 
 
623
  batch_args = [batch_count, topology, width, height, variant, void_frac, t_nodes, t_edges]
624
  batch_btn.click(run_batch_generation, batch_args, [file_out])
625
 
 
10
  from datetime import datetime
11
 
12
  # ==========================================
13
+ # 1. JSON & HELPER LOGIC
14
  # ==========================================
15
  def get_sorted_nodes(G):
16
  return sorted(list(G.nodes()), key=lambda l: (l[0], l[1]))
 
430
  IMG_HEIGHT_PX = 800
431
 
432
  def plot_graph_to_image(graph, width, height, title="Network"):
 
433
  dpi = 100
434
  fig = plt.figure(figsize=(IMG_WIDTH_PX/dpi, IMG_HEIGHT_PX/dpi), dpi=dpi)
435
+ ax = fig.add_axes([0, 0, 1, 1])
436
 
437
  pos = {node: (node[0], node[1]) for node in graph.nodes()}
438
  nx.draw_networkx_edges(graph, pos, ax=ax, width=2, alpha=0.6, edge_color="#333")
 
442
  labels = {node: str(i+1) for i, node in enumerate(sorted_nodes)}
443
  nx.draw_networkx_labels(graph, pos, labels, ax=ax, font_size=8, font_color="white", font_weight="bold")
444
 
 
 
 
 
445
  ax.set_xlim(-0.5, width + 0.5)
446
+ ax.set_ylim(height + 0.5, -0.5)
447
 
 
448
  ax.grid(True, linestyle=':', alpha=0.3)
449
  ax.set_axis_on()
450
 
 
451
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
452
  fname = f"temp_plot_{timestamp}.png"
453
  plt.savefig(fname)
 
457
  def handle_plot_click(evt: gr.SelectData, state_data):
458
  if not state_data or "graph" not in state_data: return None, "Generate first.", state_data
459
 
 
460
  click_x, click_y = evt.index
461
  width = state_data["width"]
462
  height = state_data["height"]
463
 
 
 
 
 
 
 
464
  range_x = width + 1.0
465
  range_y = height + 1.0
 
466
  norm_x = click_x / IMG_WIDTH_PX
467
  norm_y = click_y / IMG_HEIGHT_PX
 
 
468
  data_x = norm_x * range_x - 0.5
469
  data_y = norm_y * range_y - 0.5
 
470
  grid_x = int(round(data_x))
471
  grid_y = int(round(data_y))
472
 
 
473
  gen = NetworkGenerator(width, height)
474
  gen.graph = state_data["graph"]
475
 
 
476
  if 0 <= grid_x <= width and 0 <= grid_y <= height:
477
  if gen.graph.has_node((grid_x, grid_y)):
 
478
  success, msg = gen.manual_delete_node(grid_x, grid_y)
479
  action = "Deleted"
480
  else:
 
481
  success, msg = gen.manual_add_node(grid_x, grid_y)
482
  action = "Added"
483
  else:
484
+ success, msg, action = False, "Clicked outside.", "Ignored"
 
 
485
 
 
486
  if success:
487
  state_data["graph"] = gen.graph
488
  img_path = plot_graph_to_image(gen.graph, width, height)
 
491
  else:
492
  return gr.update(), f"Error: {msg}", state_data
493
 
 
494
  def get_preset_dims(preset_mode, topology):
495
  if preset_mode == "Custom": return gr.update(interactive=True), gr.update(interactive=True)
496
+ dims = (6, 11) if topology=="linear" and preset_mode=="Medium" else (8,8)
497
  if preset_mode == "Small": dims = (4, 4)
498
  if preset_mode == "Large": dims = (16, 16) if topology!="linear" else (10, 26)
499
  return gr.update(value=dims[0], interactive=False), gr.update(value=dims[1], interactive=False)
 
514
  if variant == "Fixed": t_nodes, t_edges = 0, 0
515
  gen = NetworkGenerator(width, height, var_code, topology, void_frac, t_nodes, t_edges)
516
  graph = gen.generate()
 
517
  img_path = plot_graph_to_image(graph, width, height)
518
  metrics = f"**Nodes:** {len(graph.nodes())} | **Edges:** {len(graph.edges())} | **Density:** {nx.density(graph):.2f}"
519
  state_data = { "graph": graph, "width": width, "height": height, "topology": topology }
520
+ return img_path, metrics, state_data, gr.update(interactive=True), gr.update(interactive=True), gr.update(interactive=True)
521
  except Exception as e:
522
+ return None, f"Error: {e}", None, gr.update(interactive=False), gr.update(interactive=False), gr.update(interactive=False)
523
 
524
  def run_batch_generation(count, topology, width, height, variant, void_frac, t_nodes, t_edges):
 
525
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
526
  dir_name = f"batch_{timestamp}"
527
  os.makedirs(dir_name, exist_ok=True)
 
540
  except Exception as e:
541
  return None
542
 
543
+ def save_single_json_action(state_data):
544
+ if not state_data or "graph" not in state_data: return None
545
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
546
+ json_content = generate_full_json_dict(state_data["graph"], loop=1)
547
+ fname = f"single_network_{timestamp}.json"
548
+ with open(fname, 'w') as f:
549
+ json.dump(json_content, f, indent=4)
550
+ return fname
551
+
552
  # ==========================================
553
  # GRADIO UI
554
  # ==========================================
 
565
  with gr.Row():
566
  width = gr.Number(8, label="Width", interactive=False)
567
  height = gr.Number(8, label="Height", interactive=False)
568
+
569
+ with gr.Group():
570
+ variant = gr.Dropdown(["Fixed", "Custom"], value="Fixed", label="Variant")
571
+ with gr.Row():
572
+ t_nodes = gr.Number(0, label="Target Nodes", interactive=False)
573
+ t_edges = gr.Number(0, label="Target Edges", interactive=False)
574
+
575
  void_frac = gr.Slider(0.0, 0.9, 0.35, label="Void Fraction", interactive=False)
576
+
 
 
 
577
  gen_btn = gr.Button("Generate", variant="primary")
578
+ with gr.Row():
579
+ save_json_btn = gr.Button("Download JSON", interactive=False)
580
+ json_file = gr.File(label="Saved JSON", visible=False)
581
+
582
  with gr.Tab("Batch"):
583
  batch_count = gr.Slider(1, 50, 5, step=1, label="Count")
584
  batch_btn = gr.Button("Generate Batch ZIP")
 
586
 
587
  with gr.Column(scale=2):
588
  metrics = gr.Markdown("Ready.")
 
 
589
  plot_img = gr.Image(label="Interactive Graph", interactive=False, height=800, width=800)
590
 
591
  # EVENTS
 
600
  topology.change(update_ui_for_variant, inputs_var, [void_frac, t_nodes, t_edges])
601
 
602
  gen_args = [topology, width, height, variant, void_frac, t_nodes, t_edges]
603
+ gen_btn.click(generate_and_store, gen_args, [plot_img, metrics, state, save_json_btn, json_file])
604
 
 
605
  plot_img.select(handle_plot_click, [state], [plot_img, metrics, state])
606
 
607
+ save_json_btn.click(save_single_json_action, [state], [json_file]).then(
608
+ lambda: gr.update(visible=True), None, [json_file]
609
+ )
610
+
611
  batch_args = [batch_count, topology, width, height, variant, void_frac, t_nodes, t_edges]
612
  batch_btn.click(run_batch_generation, batch_args, [file_out])
613