skarugu commited on
Commit
c950571
Β·
verified Β·
1 Parent(s): fffcba8

Updated the streamlit app and removed the train.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +9 -418
src/streamlit_app.py CHANGED
@@ -42,8 +42,6 @@ import time
42
  import zipfile
43
  import hashlib
44
  import re
45
- import subprocess
46
- import sys
47
  from datetime import datetime
48
  from pathlib import Path
49
 
@@ -104,7 +102,7 @@ def _safe_binary_closing(mask, footprint):
104
  # CONFIG ← edit these two lines to match your HF model repo
105
  # ─────────────────────────────────────────────────────────────────────────────
106
  MODEL_REPO_ID = "skarugu/myotube-unet"
107
- MODEL_FILENAME = os.environ.get("MODEL_FILENAME", "model_final.pt")
108
 
109
  CONF_FLAG_THR = 0.60 # images below this confidence are queued for retraining
110
  QUEUE_DIR = Path("retrain_queue")
@@ -1411,153 +1409,28 @@ class UNet(nn.Module):
1411
  return self.out(x)
1412
 
1413
 
1414
- def _group_count(channels: int) -> int:
1415
- for groups in (8, 4, 2, 1):
1416
- if channels % groups == 0:
1417
- return groups
1418
- return 1
1419
-
1420
-
1421
- class ResidualBlock(nn.Module):
1422
- def __init__(self, in_ch: int, out_ch: int):
1423
- super().__init__()
1424
- self.conv1 = nn.Conv2d(in_ch, out_ch, 3, padding=1, bias=False)
1425
- self.norm1 = nn.GroupNorm(_group_count(out_ch), out_ch)
1426
- self.conv2 = nn.Conv2d(out_ch, out_ch, 3, padding=1, bias=False)
1427
- self.norm2 = nn.GroupNorm(_group_count(out_ch), out_ch)
1428
- self.skip = nn.Identity() if in_ch == out_ch else nn.Conv2d(in_ch, out_ch, 1, bias=False)
1429
-
1430
- def forward(self, x):
1431
- identity = self.skip(x)
1432
- x = torch.nn.functional.silu(self.norm1(self.conv1(x)))
1433
- x = self.norm2(self.conv2(x))
1434
- return torch.nn.functional.silu(x + identity)
1435
-
1436
-
1437
- class AttentionGate(nn.Module):
1438
- def __init__(self, gate_ch: int, skip_ch: int, inter_ch: int):
1439
- super().__init__()
1440
- self.gate = nn.Sequential(
1441
- nn.Conv2d(gate_ch, inter_ch, 1, bias=True),
1442
- nn.GroupNorm(_group_count(inter_ch), inter_ch),
1443
- )
1444
- self.skip = nn.Sequential(
1445
- nn.Conv2d(skip_ch, inter_ch, 1, bias=True),
1446
- nn.GroupNorm(_group_count(inter_ch), inter_ch),
1447
- )
1448
- self.psi = nn.Sequential(nn.Conv2d(inter_ch, 1, 1, bias=True), nn.Sigmoid())
1449
-
1450
- def forward(self, gate, skip):
1451
- alpha = self.psi(torch.nn.functional.silu(self.gate(gate) + self.skip(skip)))
1452
- return skip * alpha
1453
-
1454
-
1455
- def _center_pad_like(x, ref):
1456
- diff_y = ref.size(2) - x.size(2)
1457
- diff_x = ref.size(3) - x.size(3)
1458
- if diff_x or diff_y:
1459
- x = torch.nn.functional.pad(
1460
- x,
1461
- [diff_x // 2, diff_x - diff_x // 2, diff_y // 2, diff_y - diff_y // 2],
1462
- )
1463
- return x
1464
-
1465
-
1466
- class AttentionResUNetV2(nn.Module):
1467
- def __init__(self, in_ch: int = 2, out_ch: int = 2, base: int = 32):
1468
- super().__init__()
1469
- self.e1 = ResidualBlock(in_ch, base)
1470
- self.e2 = ResidualBlock(base, base * 2)
1471
- self.e3 = ResidualBlock(base * 2, base * 4)
1472
- self.e4 = ResidualBlock(base * 4, base * 8)
1473
- self.pool = nn.MaxPool2d(2)
1474
- self.bridge = ResidualBlock(base * 8, base * 16)
1475
-
1476
- self.up4 = nn.ConvTranspose2d(base * 16, base * 8, 2, 2)
1477
- self.att4 = AttentionGate(base * 8, base * 8, base * 4)
1478
- self.dec4 = ResidualBlock(base * 16, base * 8)
1479
- self.up3 = nn.ConvTranspose2d(base * 8, base * 4, 2, 2)
1480
- self.att3 = AttentionGate(base * 4, base * 4, base * 2)
1481
- self.dec3 = ResidualBlock(base * 8, base * 4)
1482
- self.up2 = nn.ConvTranspose2d(base * 4, base * 2, 2, 2)
1483
- self.att2 = AttentionGate(base * 2, base * 2, base)
1484
- self.dec2 = ResidualBlock(base * 4, base * 2)
1485
- self.up1 = nn.ConvTranspose2d(base * 2, base, 2, 2)
1486
- self.att1 = AttentionGate(base, base, max(base // 2, 1))
1487
- self.dec1 = ResidualBlock(base * 2, base)
1488
- self.out = nn.Conv2d(base, out_ch, 1)
1489
-
1490
- def forward(self, x):
1491
- e1 = self.e1(x)
1492
- e2 = self.e2(self.pool(e1))
1493
- e3 = self.e3(self.pool(e2))
1494
- e4 = self.e4(self.pool(e3))
1495
- b = self.bridge(self.pool(e4))
1496
-
1497
- x = _center_pad_like(self.up4(b), e4)
1498
- x = self.dec4(torch.cat([x, self.att4(x, e4)], dim=1))
1499
- x = _center_pad_like(self.up3(x), e3)
1500
- x = self.dec3(torch.cat([x, self.att3(x, e3)], dim=1))
1501
- x = _center_pad_like(self.up2(x), e2)
1502
- x = self.dec2(torch.cat([x, self.att2(x, e2)], dim=1))
1503
- x = _center_pad_like(self.up1(x), e1)
1504
- x = self.dec1(torch.cat([x, self.att1(x, e1)], dim=1))
1505
- return self.out(x)
1506
-
1507
-
1508
  @st.cache_resource
1509
  def load_model(device: str):
1510
- active_filename = MODEL_FILENAME
1511
- try:
1512
- local = hf_hub_download(
1513
- repo_id=MODEL_REPO_ID,
1514
- filename=active_filename,
1515
- force_download=True,
1516
- )
1517
- except Exception as exc:
1518
- missing_requested_model = (
1519
- active_filename != "model_final.pt"
1520
- and (
1521
- "EntryNotFound" in exc.__class__.__name__
1522
- or "404" in str(exc)
1523
- or "Entry Not Found" in str(exc)
1524
- )
1525
- )
1526
- if not missing_requested_model:
1527
- raise
1528
- st.warning(
1529
- f"`{active_filename}` is not in `{MODEL_REPO_ID}` yet. "
1530
- "Using `model_final.pt` so the app can stay open while v2 is trained."
1531
- )
1532
- active_filename = "model_final.pt"
1533
- local = hf_hub_download(
1534
- repo_id=MODEL_REPO_ID,
1535
- filename=active_filename,
1536
- force_download=True,
1537
- )
1538
  file_sha = sha256_file(local)
1539
  mtime = time.ctime(os.path.getmtime(local))
1540
  size_mb = os.path.getsize(local) / 1e6
1541
 
1542
  st.sidebar.markdown("### πŸ” Model debug")
1543
  st.sidebar.caption(f"Repo: `{MODEL_REPO_ID}`")
1544
- st.sidebar.caption(f"Requested file: `{MODEL_FILENAME}`")
1545
- st.sidebar.caption(f"Loaded file: `{active_filename}`")
1546
  st.sidebar.caption(f"Size: {size_mb:.2f} MB")
1547
  st.sidebar.caption(f"Modified: {mtime}")
1548
  st.sidebar.caption(f"SHA256: `{file_sha[:20]}…`")
1549
 
1550
  ckpt = torch.load(local, map_location=device)
1551
  state = ckpt["model"] if isinstance(ckpt, dict) and "model" in ckpt else ckpt
1552
- args = ckpt.get("args", {}) if isinstance(ckpt, dict) else {}
1553
- arch = ckpt.get("arch", args.get("arch", "unet_v1")) if isinstance(ckpt, dict) else "unet_v1"
1554
- base = int(args.get("base", 32)) if isinstance(args, dict) else 32
1555
-
1556
- if arch == "attention_resunet_v2":
1557
- model = AttentionResUNetV2(in_ch=2, out_ch=2, base=base)
1558
- else:
1559
- model = UNet(in_ch=2, out_ch=2, base=32)
1560
- st.sidebar.caption(f"Architecture: `{arch}`")
1561
  model.load_state_dict(state)
1562
  model.to(device).eval()
1563
  return model
@@ -1585,147 +1458,6 @@ h1,h2,h3,h4 { color:#90caf9; }
1585
  st.title("πŸ”¬ MyoSeg β€” Myotube & Nuclei Analyser")
1586
  device = "cuda" if torch.cuda.is_available() else "cpu"
1587
 
1588
- V2_RUN_DIR = Path("runs_v2")
1589
- V2_PID_PATH = V2_RUN_DIR / "train_v2.pid"
1590
- V2_LOG_PATH = V2_RUN_DIR / "train_v2_latest.log"
1591
-
1592
-
1593
- def _pid_is_running(pid: int) -> bool:
1594
- if pid <= 0:
1595
- return False
1596
- try:
1597
- os.kill(pid, 0)
1598
- return True
1599
- except OSError:
1600
- return False
1601
-
1602
-
1603
- def _v2_training_status():
1604
- if not V2_PID_PATH.exists():
1605
- return False, None
1606
- try:
1607
- pid = int(V2_PID_PATH.read_text().strip())
1608
- except Exception:
1609
- return False, None
1610
- return _pid_is_running(pid), pid
1611
-
1612
-
1613
- def _start_v2_training(epochs: int, batch_size: int, base: int, image_size: int):
1614
- V2_RUN_DIR.mkdir(parents=True, exist_ok=True)
1615
- cmd = [
1616
- sys.executable,
1617
- "train_model_v2.py",
1618
- "--data_root", "data",
1619
- "--corrections_dir", "corrections",
1620
- "--out_dir", str(V2_RUN_DIR),
1621
- "--epochs", str(int(epochs)),
1622
- "--batch_size", str(int(batch_size)),
1623
- "--image_size", str(int(image_size)),
1624
- "--base", str(int(base)),
1625
- "--push_to_hub",
1626
- "--hf_repo", MODEL_REPO_ID,
1627
- "--hf_filename", "model_v2.pt",
1628
- ]
1629
- env = os.environ.copy()
1630
- env["PYTHONUNBUFFERED"] = "1"
1631
- with V2_LOG_PATH.open("ab", buffering=0) as log_f:
1632
- log_f.write(
1633
- f"\n\n=== MyoSeg v2 training started {datetime.now().isoformat()} ===\n".encode()
1634
- )
1635
- log_f.write(("Command: " + " ".join(cmd) + "\n\n").encode())
1636
- proc = subprocess.Popen(
1637
- cmd,
1638
- stdout=log_f,
1639
- stderr=subprocess.STDOUT,
1640
- cwd=".",
1641
- env=env,
1642
- )
1643
- V2_PID_PATH.write_text(str(proc.pid))
1644
- return proc.pid
1645
-
1646
-
1647
- def render_v2_training_panel(image_size: int, key_prefix: str = "v2_top"):
1648
- train_script = Path("train_model_v2.py")
1649
- has_script = train_script.exists()
1650
- has_token = bool(os.environ.get("HF_TOKEN"))
1651
- running, pid = _v2_training_status()
1652
- latest_model = V2_RUN_DIR / "model_v2.pt"
1653
- latest_metrics = V2_RUN_DIR / "metrics_v2.json"
1654
-
1655
- s1, s2, s3 = st.columns(3)
1656
- s1.metric("train_model_v2.py", "Found" if has_script else "Missing")
1657
- s2.metric("HF_TOKEN", "Ready" if has_token else "Missing")
1658
- s3.metric("v2 status", f"Running pid {pid}" if running else "Idle")
1659
-
1660
- if not has_script:
1661
- st.error("Upload train_model_v2.py to the Space before starting v2 training.")
1662
- if not has_token:
1663
- st.warning("Add HF_TOKEN as a Space secret with write access before training.")
1664
-
1665
- c1, c2, c3 = st.columns(3)
1666
- with c1:
1667
- v2_epochs = st.number_input(
1668
- "v2 epochs", min_value=5, max_value=200, value=80, step=5,
1669
- key=f"{key_prefix}_epochs",
1670
- )
1671
- with c2:
1672
- v2_batch = st.selectbox("v2 batch size", [2, 4], index=0, key=f"{key_prefix}_batch")
1673
- with c3:
1674
- v2_base = st.selectbox("v2 base width", [24, 32], index=1, key=f"{key_prefix}_base")
1675
-
1676
- st.caption(
1677
- "This trains attention_resunet_v2 on data/images plus data/masks/Nuclei_m "
1678
- "and data/masks/Myotubes_m, then uploads model_v2.pt to the model repo."
1679
- )
1680
-
1681
- start_disabled = running or not has_script or not has_token
1682
- if st.button("Start v2 training now", type="primary", disabled=start_disabled, key=f"{key_prefix}_start"):
1683
- new_pid = _start_v2_training(
1684
- epochs=int(v2_epochs),
1685
- batch_size=int(v2_batch),
1686
- base=int(v2_base),
1687
- image_size=int(image_size),
1688
- )
1689
- st.success(f"v2 training started in the background. Process id: {new_pid}")
1690
- st.info("Refresh this page to update status. Watch the log preview below for progress.")
1691
-
1692
- if running:
1693
- st.info("v2 training is currently running. Avoid restarting the Space until it finishes.")
1694
-
1695
- if latest_model.exists():
1696
- st.success(f"Local v2 model exists: {latest_model}")
1697
- st.download_button(
1698
- "Download model_v2.pt",
1699
- data=latest_model.read_bytes(),
1700
- file_name="model_v2.pt",
1701
- mime="application/octet-stream",
1702
- key=f"{key_prefix}_download_model",
1703
- )
1704
- if latest_metrics.exists():
1705
- try:
1706
- metrics_text = latest_metrics.read_text()
1707
- st.json(json.loads(metrics_text))
1708
- st.download_button(
1709
- "Download metrics_v2.json",
1710
- data=metrics_text,
1711
- file_name="metrics_v2.json",
1712
- mime="application/json",
1713
- key=f"{key_prefix}_download_metrics",
1714
- )
1715
- except Exception:
1716
- st.caption(f"Metrics file exists: {latest_metrics}")
1717
-
1718
- if V2_LOG_PATH.exists():
1719
- log_text = V2_LOG_PATH.read_text(errors="replace")
1720
- st.download_button(
1721
- "Download v2 training log",
1722
- data=log_text,
1723
- file_name="train_v2_latest.log",
1724
- mime="text/plain",
1725
- key=f"{key_prefix}_download_log",
1726
- )
1727
- st.text_area("Latest v2 training log", log_text[-6000:], height=260, key=f"{key_prefix}_log")
1728
-
1729
  # ─────────────────────────────────────────────────────────────────────────────
1730
  # SIDEBAR
1731
  # ─────────────────────────────────────────────────────────────────────────────
@@ -1919,10 +1651,6 @@ with st.sidebar:
1919
 
1920
 
1921
  # ─────────────────────────────────────────────────────────────────────────────
1922
- with st.expander("Train v2 model", expanded=True):
1923
- render_v2_training_panel(image_size=int(image_size), key_prefix="v2_top")
1924
-
1925
-
1926
  # FILE UPLOADER
1927
  # ─────────────────────────────────────────────────────────────────────────────
1928
  _uploader_label = (
@@ -2187,64 +1915,6 @@ with c3:
2187
 
2188
  st.divider()
2189
 
2190
- V2_RUN_DIR = Path("runs_v2")
2191
- V2_PID_PATH = V2_RUN_DIR / "train_v2.pid"
2192
- V2_LOG_PATH = V2_RUN_DIR / "train_v2_latest.log"
2193
-
2194
-
2195
- def _pid_is_running(pid: int) -> bool:
2196
- if pid <= 0:
2197
- return False
2198
- try:
2199
- os.kill(pid, 0)
2200
- return True
2201
- except OSError:
2202
- return False
2203
-
2204
-
2205
- def _v2_training_status():
2206
- if not V2_PID_PATH.exists():
2207
- return False, None
2208
- try:
2209
- pid = int(V2_PID_PATH.read_text().strip())
2210
- except Exception:
2211
- return False, None
2212
- return _pid_is_running(pid), pid
2213
-
2214
-
2215
- def _start_v2_training(epochs: int, batch_size: int, base: int, image_size: int):
2216
- V2_RUN_DIR.mkdir(parents=True, exist_ok=True)
2217
- cmd = [
2218
- sys.executable,
2219
- "train_model_v2.py",
2220
- "--data_root", "data",
2221
- "--corrections_dir", "corrections",
2222
- "--out_dir", str(V2_RUN_DIR),
2223
- "--epochs", str(int(epochs)),
2224
- "--batch_size", str(int(batch_size)),
2225
- "--image_size", str(int(image_size)),
2226
- "--base", str(int(base)),
2227
- "--push_to_hub",
2228
- "--hf_repo", MODEL_REPO_ID,
2229
- "--hf_filename", "model_v2.pt",
2230
- ]
2231
- env = os.environ.copy()
2232
- env["PYTHONUNBUFFERED"] = "1"
2233
- with V2_LOG_PATH.open("ab", buffering=0) as log_f:
2234
- log_f.write(
2235
- f"\n\n=== MyoSeg v2 training started {datetime.now().isoformat()} ===\n".encode()
2236
- )
2237
- log_f.write(("Command: " + " ".join(cmd) + "\n\n").encode())
2238
- proc = subprocess.Popen(
2239
- cmd,
2240
- stdout=log_f,
2241
- stderr=subprocess.STDOUT,
2242
- cwd=".",
2243
- env=env,
2244
- )
2245
- V2_PID_PATH.write_text(str(proc.pid))
2246
- return proc.pid
2247
-
2248
  # ─────────────────────────────────────────────────────────────────────────────
2249
  # PER-IMAGE PREVIEW + ANIMATED METRICS
2250
  # ─────────────────────────────────────────────────────────────────────────────
@@ -2637,82 +2307,3 @@ with st.expander("πŸ”§ Self-training queue status"):
2637
  import subprocess
2638
  subprocess.Popen(["python", "self_train.py", "--manual"])
2639
  st.info("Retraining started in the background. Check terminal / logs for progress.")
2640
-
2641
-
2642
- with st.expander("Train v2 model"):
2643
- train_script = Path("train_model_v2.py")
2644
- has_script = train_script.exists()
2645
- has_token = bool(os.environ.get("HF_TOKEN"))
2646
- running, pid = _v2_training_status()
2647
- latest_model = V2_RUN_DIR / "model_v2.pt"
2648
- latest_metrics = V2_RUN_DIR / "metrics_v2.json"
2649
-
2650
- s1, s2, s3 = st.columns(3)
2651
- s1.metric("train_model_v2.py", "Found" if has_script else "Missing")
2652
- s2.metric("HF_TOKEN", "Ready" if has_token else "Missing")
2653
- s3.metric("v2 status", f"Running pid {pid}" if running else "Idle")
2654
-
2655
- if not has_script:
2656
- st.error("Upload train_model_v2.py to the Space before starting v2 training.")
2657
- if not has_token:
2658
- st.warning("Add HF_TOKEN as a Space secret with write access before training.")
2659
-
2660
- c1, c2, c3 = st.columns(3)
2661
- with c1:
2662
- v2_epochs = st.number_input("v2 epochs", min_value=5, max_value=200, value=80, step=5)
2663
- with c2:
2664
- v2_batch = st.selectbox("v2 batch size", [2, 4], index=0)
2665
- with c3:
2666
- v2_base = st.selectbox("v2 base width", [24, 32], index=1)
2667
-
2668
- st.caption(
2669
- "This trains attention_resunet_v2 on data/images plus data/masks/Nuclei_m "
2670
- "and data/masks/Myotubes_m, then uploads model_v2.pt to the model repo."
2671
- )
2672
-
2673
- start_disabled = running or not has_script or not has_token
2674
- if st.button("Start v2 training now", type="primary", disabled=start_disabled):
2675
- new_pid = _start_v2_training(
2676
- epochs=int(v2_epochs),
2677
- batch_size=int(v2_batch),
2678
- base=int(v2_base),
2679
- image_size=int(image_size),
2680
- )
2681
- st.success(f"v2 training started in the background. Process id: {new_pid}")
2682
- st.info("Refresh this page to update status. Watch the log preview below for progress.")
2683
-
2684
- if running:
2685
- st.info("v2 training is currently running. Avoid restarting the Space until it finishes.")
2686
-
2687
- if latest_model.exists():
2688
- st.success(f"Local v2 model exists: {latest_model}")
2689
- st.download_button(
2690
- "Download model_v2.pt",
2691
- data=latest_model.read_bytes(),
2692
- file_name="model_v2.pt",
2693
- mime="application/octet-stream",
2694
- key="v2_bottom_download_model",
2695
- )
2696
- if latest_metrics.exists():
2697
- try:
2698
- metrics_text = latest_metrics.read_text()
2699
- st.json(json.loads(metrics_text))
2700
- st.download_button(
2701
- "Download metrics_v2.json",
2702
- data=metrics_text,
2703
- file_name="metrics_v2.json",
2704
- mime="application/json",
2705
- key="v2_bottom_download_metrics",
2706
- )
2707
- except Exception:
2708
- st.caption(f"Metrics file exists: {latest_metrics}")
2709
-
2710
- if V2_LOG_PATH.exists():
2711
- log_text = V2_LOG_PATH.read_text(errors="replace")
2712
- st.download_button(
2713
- "Download v2 training log",
2714
- data=log_text,
2715
- file_name="train_v2_latest.log",
2716
- mime="text/plain",
2717
- )
2718
- st.text_area("Latest v2 training log", log_text[-6000:], height=260)
 
42
  import zipfile
43
  import hashlib
44
  import re
 
 
45
  from datetime import datetime
46
  from pathlib import Path
47
 
 
102
  # CONFIG ← edit these two lines to match your HF model repo
103
  # ─────────────────────────────────────────────────────────────────────────────
104
  MODEL_REPO_ID = "skarugu/myotube-unet"
105
+ MODEL_FILENAME = "model_final.pt"
106
 
107
  CONF_FLAG_THR = 0.60 # images below this confidence are queued for retraining
108
  QUEUE_DIR = Path("retrain_queue")
 
1409
  return self.out(x)
1410
 
1411
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1412
  @st.cache_resource
1413
  def load_model(device: str):
1414
+ local = hf_hub_download(
1415
+ repo_id=MODEL_REPO_ID,
1416
+ filename=MODEL_FILENAME,
1417
+ force_download=True,
1418
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1419
  file_sha = sha256_file(local)
1420
  mtime = time.ctime(os.path.getmtime(local))
1421
  size_mb = os.path.getsize(local) / 1e6
1422
 
1423
  st.sidebar.markdown("### πŸ” Model debug")
1424
  st.sidebar.caption(f"Repo: `{MODEL_REPO_ID}`")
1425
+ st.sidebar.caption(f"File: `{MODEL_FILENAME}`")
 
1426
  st.sidebar.caption(f"Size: {size_mb:.2f} MB")
1427
  st.sidebar.caption(f"Modified: {mtime}")
1428
  st.sidebar.caption(f"SHA256: `{file_sha[:20]}…`")
1429
 
1430
  ckpt = torch.load(local, map_location=device)
1431
  state = ckpt["model"] if isinstance(ckpt, dict) and "model" in ckpt else ckpt
1432
+ model = UNet(in_ch=2, out_ch=2, base=32)
1433
+ st.sidebar.caption("Architecture: `unet_v1`")
 
 
 
 
 
 
 
1434
  model.load_state_dict(state)
1435
  model.to(device).eval()
1436
  return model
 
1458
  st.title("πŸ”¬ MyoSeg β€” Myotube & Nuclei Analyser")
1459
  device = "cuda" if torch.cuda.is_available() else "cpu"
1460
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1461
  # ─────────────────────────────────────────────────────────────────────────────
1462
  # SIDEBAR
1463
  # ─────────────────────────────────────────────────────────────────────────────
 
1651
 
1652
 
1653
  # ─────────────────────────────────────────────────────────────────────────────
 
 
 
 
1654
  # FILE UPLOADER
1655
  # ─────────────────────────────────────────────────────────────────────────────
1656
  _uploader_label = (
 
1915
 
1916
  st.divider()
1917
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1918
  # ─────────────────────────────────────────────────────────────────────────────
1919
  # PER-IMAGE PREVIEW + ANIMATED METRICS
1920
  # ─────────────────────────────────────────────────────────────────────────────
 
2307
  import subprocess
2308
  subprocess.Popen(["python", "self_train.py", "--manual"])
2309
  st.info("Retraining started in the background. Check terminal / logs for progress.")