flashstudio 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of flashstudio might be problematic. Click here for more details.

Files changed (97) hide show
  1. flashstudio/__init__.py +5 -0
  2. flashstudio/app.py +194 -0
  3. flashstudio/cli.py +18 -0
  4. flashstudio/components/__init__.py +0 -0
  5. flashstudio/components/project_manager.py +490 -0
  6. flashstudio/components/sidebar.py +87 -0
  7. flashstudio/components/styles.py +346 -0
  8. flashstudio/components/wizard.py +45 -0
  9. flashstudio/components/zone_drawer/__init__.py +177 -0
  10. flashstudio/constants.py +245 -0
  11. flashstudio/core/__init__.py +3 -0
  12. flashstudio/core/constants.py +3 -0
  13. flashstudio/launcher.py +288 -0
  14. flashstudio/pages/__init__.py +8 -0
  15. flashstudio/pages/dashboard/__init__.py +3 -0
  16. flashstudio/pages/dashboard/_common.py +10 -0
  17. flashstudio/pages/dashboard/overview/__init__.py +3 -0
  18. flashstudio/pages/dashboard/overview/metrics.py +33 -0
  19. flashstudio/pages/dashboard/overview/pipeline_status.py +35 -0
  20. flashstudio/pages/dashboard/overview/project_banner.py +23 -0
  21. flashstudio/pages/dashboard/overview/quick_actions.py +28 -0
  22. flashstudio/pages/dashboard/overview/recent_runs.py +99 -0
  23. flashstudio/pages/dashboard/overview/tab.py +35 -0
  24. flashstudio/pages/dashboard/page.py +17 -0
  25. flashstudio/pages/data/__init__.py +3 -0
  26. flashstudio/pages/data/_common.py +107 -0
  27. flashstudio/pages/data/download/__init__.py +3 -0
  28. flashstudio/pages/data/download/tab.py +75 -0
  29. flashstudio/pages/data/helpers.py +338 -0
  30. flashstudio/pages/data/page.py +50 -0
  31. flashstudio/pages/data/preview/__init__.py +3 -0
  32. flashstudio/pages/data/preview/tab.py +139 -0
  33. flashstudio/pages/data/upload/__init__.py +3 -0
  34. flashstudio/pages/data/upload/tab.py +156 -0
  35. flashstudio/pages/data/verify/__init__.py +3 -0
  36. flashstudio/pages/data/verify/tab.py +108 -0
  37. flashstudio/pages/export/__init__.py +3 -0
  38. flashstudio/pages/export/page.py +54 -0
  39. flashstudio/pages/export/weights/__init__.py +4 -0
  40. flashstudio/pages/export/weights/exporter.py +60 -0
  41. flashstudio/pages/export/weights/settings.py +86 -0
  42. flashstudio/pages/inference/__init__.py +3 -0
  43. flashstudio/pages/inference/_common.py +76 -0
  44. flashstudio/pages/inference/data/__init__.py +3 -0
  45. flashstudio/pages/inference/data/tab.py +121 -0
  46. flashstudio/pages/inference/model/__init__.py +3 -0
  47. flashstudio/pages/inference/model/tab.py +62 -0
  48. flashstudio/pages/inference/page.py +42 -0
  49. flashstudio/pages/inference/run/__init__.py +3 -0
  50. flashstudio/pages/inference/run/detection.py +133 -0
  51. flashstudio/pages/inference/run/tab.py +161 -0
  52. flashstudio/pages/inference/run/video.py +195 -0
  53. flashstudio/pages/inference/solution/__init__.py +3 -0
  54. flashstudio/pages/inference/solution/tab.py +121 -0
  55. flashstudio/pages/model/__init__.py +3 -0
  56. flashstudio/pages/model/_common.py +3 -0
  57. flashstudio/pages/model/advanced/__init__.py +3 -0
  58. flashstudio/pages/model/advanced/tab.py +54 -0
  59. flashstudio/pages/model/architecture/__init__.py +3 -0
  60. flashstudio/pages/model/architecture/tab.py +90 -0
  61. flashstudio/pages/model/augment/__init__.py +3 -0
  62. flashstudio/pages/model/augment/tab.py +18 -0
  63. flashstudio/pages/model/hyperparams/__init__.py +4 -0
  64. flashstudio/pages/model/hyperparams/tab.py +89 -0
  65. flashstudio/pages/model/page.py +30 -0
  66. flashstudio/pages/model/utils/__init__.py +3 -0
  67. flashstudio/pages/model/utils/config.py +27 -0
  68. flashstudio/pages/training/__init__.py +3 -0
  69. flashstudio/pages/training/_common.py +24 -0
  70. flashstudio/pages/training/launch/__init__.py +3 -0
  71. flashstudio/pages/training/launch/controls.py +84 -0
  72. flashstudio/pages/training/launch/dialogs.py +247 -0
  73. flashstudio/pages/training/launch/preflight.py +77 -0
  74. flashstudio/pages/training/launch/runner.py +440 -0
  75. flashstudio/pages/training/launch/tab.py +134 -0
  76. flashstudio/pages/training/monitor/__init__.py +3 -0
  77. flashstudio/pages/training/monitor/checkpoints.py +96 -0
  78. flashstudio/pages/training/monitor/curves.py +113 -0
  79. flashstudio/pages/training/monitor/dashboard.py +129 -0
  80. flashstudio/pages/training/monitor/gt_verification.py +122 -0
  81. flashstudio/pages/training/monitor/log_viewer.py +43 -0
  82. flashstudio/pages/training/monitor/parsers.py +253 -0
  83. flashstudio/pages/training/monitor/run_meta.py +165 -0
  84. flashstudio/pages/training/monitor/tab.py +142 -0
  85. flashstudio/pages/training/monitor/visualizations.py +52 -0
  86. flashstudio/pages/training/page.py +134 -0
  87. flashstudio/utils/__init__.py +152 -0
  88. flashstudio/utils/config.py +104 -0
  89. flashstudio/utils/device.py +67 -0
  90. flashstudio/utils/filesystem.py +67 -0
  91. flashstudio/utils/jobs.py +95 -0
  92. flashstudio/utils/training_hooks.py +266 -0
  93. flashstudio-0.0.1.dist-info/METADATA +107 -0
  94. flashstudio-0.0.1.dist-info/RECORD +97 -0
  95. flashstudio-0.0.1.dist-info/WHEEL +5 -0
  96. flashstudio-0.0.1.dist-info/entry_points.txt +2 -0
  97. flashstudio-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,28 @@
1
+ """Dashboard — quick action buttons section."""
2
+
3
+ import streamlit as st
4
+
5
+
6
+ def render_quick_actions():
7
+ ac1, ac2, ac3 = st.columns(3)
8
+ with ac1:
9
+ if st.button("Upload Data", use_container_width=True, key="dash_goto_data"):
10
+ st.session_state["current_step"] = 1
11
+ st.rerun()
12
+ if st.button("Configure Model", use_container_width=True, key="dash_goto_model"):
13
+ st.session_state["current_step"] = 2
14
+ st.rerun()
15
+ with ac2:
16
+ if st.button("Start Training", use_container_width=True, type="primary", key="dash_goto_train"):
17
+ st.session_state["current_step"] = 3
18
+ st.rerun()
19
+ if st.button("Export Model", use_container_width=True, key="dash_goto_export"):
20
+ st.session_state["current_step"] = 4
21
+ st.rerun()
22
+ with ac3:
23
+ if st.button("Run Inference", use_container_width=True, key="dash_goto_infer"):
24
+ st.session_state["current_step"] = 5
25
+ st.rerun()
26
+ if st.button("Projects", use_container_width=True, key="dash_goto_proj"):
27
+ st.session_state["show_project_manager"] = True
28
+ st.rerun()
@@ -0,0 +1,99 @@
1
+ """Dashboard — recent runs section."""
2
+
3
+ import os
4
+ import re
5
+ import time
6
+ import glob as glob_module
7
+ import streamlit as st
8
+ from flashstudio.pages.dashboard._common import (
9
+ DEFAULT_SAVE_DIR, MAX_DISPLAY_RUNS,
10
+ CKPT_BEST, CKPT_BEST_INFERENCE, CKPT_FINAL_INFERENCE,
11
+ CKPT_FINAL_FP16, TRAINING_LOG_CSV,
12
+ COLOR_SUCCESS, COLOR_TEXT_PRIMARY, COLOR_TEXT_SECONDARY,
13
+ COLOR_TEXT_MUTED, COLOR_BORDER,
14
+ )
15
+
16
+
17
+ def render_recent_runs():
18
+ from flashstudio.components.project_manager import get_active_project, get_project_dir
19
+
20
+ user_save_dir = st.session_state.get("save_dir", "")
21
+
22
+ candidates = []
23
+ active_proj = get_active_project()
24
+ if active_proj:
25
+ candidates.append(os.path.join(get_project_dir(active_proj["id"]), "runs"))
26
+ if user_save_dir and user_save_dir != DEFAULT_SAVE_DIR:
27
+ candidates.append(user_save_dir)
28
+ candidates.append(DEFAULT_SAVE_DIR)
29
+ ws = None
30
+ for c in candidates:
31
+ c = os.path.abspath(c)
32
+ if os.path.isdir(c):
33
+ ws = c
34
+ break
35
+
36
+ if not ws:
37
+ st.caption("No workspace found. Start training to create runs.")
38
+ return
39
+
40
+ dirs = sorted(
41
+ [d for d in os.listdir(ws) if os.path.isdir(os.path.join(ws, d))],
42
+ key=lambda d: os.path.getmtime(os.path.join(ws, d)), reverse=True,
43
+ )[:MAX_DISPLAY_RUNS]
44
+
45
+ if not dirs:
46
+ st.caption("No runs yet. Go to Training to start your first run.")
47
+ return
48
+
49
+ for name in dirs:
50
+ rd = os.path.join(ws, name)
51
+ has_best = (os.path.isfile(os.path.join(rd, CKPT_BEST))
52
+ or os.path.isfile(os.path.join(rd, CKPT_BEST_INFERENCE)))
53
+ has_final = (os.path.isfile(os.path.join(rd, CKPT_FINAL_INFERENCE))
54
+ or os.path.isfile(os.path.join(rd, CKPT_FINAL_FP16)))
55
+ status = "Done" if (has_best or has_final) else "\u2014"
56
+ info = ""
57
+ csv_path = os.path.join(rd, TRAINING_LOG_CSV)
58
+ if os.path.isfile(csv_path):
59
+ try:
60
+ import csv as _csv
61
+ with open(csv_path, "r") as _f:
62
+ rows = list(_csv.DictReader(_f))
63
+ if rows:
64
+ vals = [float(r.get("mAP@0.5", 0)) for r in rows if r.get("mAP@0.5", "").strip()]
65
+ if vals:
66
+ info = f"mAP {max(vals):.3f}"
67
+ except Exception:
68
+ pass
69
+ if not info:
70
+ from flashstudio.constants import TRAINING_LOG_GLOB
71
+ log_files = glob_module.glob(os.path.join(rd, TRAINING_LOG_GLOB))
72
+ if log_files:
73
+ try:
74
+ best_log = max(log_files, key=lambda p: os.path.getsize(p))
75
+ with open(best_log, "r", encoding="utf-8", errors="replace") as _f:
76
+ for _ln in _f.readlines()[-10:]:
77
+ bm = re.search(r"Best mAP@0\.5:\s*([\d.]+)", _ln)
78
+ if bm:
79
+ info = f"mAP {bm.group(1)}"
80
+ except OSError:
81
+ pass
82
+
83
+ mtime = os.path.getmtime(rd)
84
+ date_str = time.strftime("%b %d, %H:%M", time.localtime(mtime))
85
+ status_color = COLOR_SUCCESS if (has_best or has_final) else COLOR_TEXT_MUTED
86
+
87
+ st.markdown(
88
+ f'<div style="display:flex;justify-content:space-between;align-items:center;'
89
+ f'font-size:0.85rem;padding:0.4rem 0;border-bottom:1px solid {COLOR_BORDER};">'
90
+ f'<div>'
91
+ f'<span style="font-weight:600;color:{COLOR_TEXT_PRIMARY};">{name[:25]}</span>'
92
+ f'<span style="color:{COLOR_TEXT_MUTED};font-size:0.75rem;margin-left:0.5rem;">{date_str}</span>'
93
+ f'</div>'
94
+ f'<div style="display:flex;gap:0.8rem;align-items:center;">'
95
+ f'<span style="color:{COLOR_TEXT_SECONDARY};">{info}</span>'
96
+ f'<span style="color:{status_color};font-weight:600;">{status}</span>'
97
+ f'</div></div>',
98
+ unsafe_allow_html=True,
99
+ )
@@ -0,0 +1,35 @@
1
+ """Dashboard overview — orchestrates all overview sections."""
2
+
3
+ import streamlit as st
4
+ from flashstudio.pages.dashboard.overview.project_banner import render_project_banner
5
+ from flashstudio.pages.dashboard.overview.metrics import render_metrics
6
+ from flashstudio.pages.dashboard.overview.pipeline_status import render_pipeline_status
7
+ from flashstudio.pages.dashboard.overview.quick_actions import render_quick_actions
8
+ from flashstudio.pages.dashboard.overview.recent_runs import render_recent_runs
9
+
10
+
11
+ def render_overview(project, stats):
12
+ from flashstudio.utils.device import get_gpu_info
13
+
14
+ gpu = get_gpu_info()
15
+
16
+ if project:
17
+ render_project_banner(project)
18
+
19
+ render_metrics(gpu, stats)
20
+
21
+ left, right = st.columns(2)
22
+
23
+ with left:
24
+ with st.container(border=True):
25
+ st.markdown("#### Pipeline Status")
26
+ render_pipeline_status()
27
+
28
+ with right:
29
+ with st.container(border=True):
30
+ st.markdown("#### Quick Actions")
31
+ render_quick_actions()
32
+
33
+ with st.container(border=True):
34
+ st.markdown("#### Recent Runs")
35
+ render_recent_runs()
@@ -0,0 +1,17 @@
1
+ """FlashStudio — Dashboard."""
2
+
3
+ from flashstudio.components.styles import render_page_header
4
+ from flashstudio.pages.dashboard.overview.tab import render_overview
5
+
6
+
7
+ def render_dashboard():
8
+ from flashstudio.components.project_manager import get_active_project, get_project_stats
9
+ from flashstudio.utils import show_flashes
10
+
11
+ render_page_header("", "Dashboard")
12
+ show_flashes()
13
+
14
+ project = get_active_project()
15
+ stats = get_project_stats(project["id"]) if project else None
16
+
17
+ render_overview(project, stats)
@@ -0,0 +1,3 @@
1
+ """FlashStudio — Data page package."""
2
+
3
+ from flashstudio.pages.data.page import render_data_page # noqa: F401
@@ -0,0 +1,107 @@
1
+ """Shared helpers and constants for the data page."""
2
+
3
+ import os
4
+ import json
5
+ import streamlit as st
6
+
7
+ from flashstudio.constants import (
8
+ DEFAULT_DATA_DIR, NETWORK_CHECK_URL, NETWORK_CHECK_TIMEOUT,
9
+ )
10
+
11
+ try:
12
+ from flashdet import download_dataset, list_datasets # noqa: F401
13
+ from flashdet.data import ( # noqa: F401
14
+ convert_dataset, detect_dataset_format,
15
+ verify_dataset, summarize_coco_root,
16
+ )
17
+ _HAS_FLASHDET = True
18
+ except ImportError:
19
+ _HAS_FLASHDET = False
20
+
21
+ _FLASHDET_INSTALL_MSG = "FlashDet required — install with: `pip install flashdet`"
22
+
23
+
24
+ def _extract_classes_from_coco_json(ann_path: str) -> list[str]:
25
+ """Read class names from a COCO-format annotation JSON file."""
26
+ try:
27
+ with open(ann_path, encoding="utf-8") as f:
28
+ data = json.load(f)
29
+ cats = data.get("categories", [])
30
+ if not cats:
31
+ return []
32
+ sorted_cats = sorted(cats, key=lambda c: c.get("id", 0))
33
+ return [c["name"] for c in sorted_cats]
34
+ except (OSError, json.JSONDecodeError, KeyError, TypeError):
35
+ return []
36
+
37
+
38
+ def _auto_detect_classes():
39
+ """Try to auto-detect classes from the loaded dataset annotation."""
40
+ train_path = st.session_state.get("train_img_path", "")
41
+ if not train_path or not os.path.isdir(train_path):
42
+ return []
43
+
44
+ ann_file = os.path.join(train_path, "_annotations.coco.json")
45
+ if not os.path.isfile(ann_file):
46
+ json_files = [f for f in os.listdir(train_path) if f.endswith(".json")]
47
+ if json_files:
48
+ ann_file = os.path.join(train_path, json_files[0])
49
+ else:
50
+ return []
51
+
52
+ return _extract_classes_from_coco_json(ann_file)
53
+
54
+
55
+ def _get_native_datasets():
56
+ if not _HAS_FLASHDET:
57
+ return []
58
+ try:
59
+ return list_datasets()
60
+ except Exception:
61
+ return []
62
+
63
+
64
+ def _check_network() -> bool:
65
+ import urllib.request
66
+ import urllib.error
67
+ try:
68
+ urllib.request.urlopen(NETWORK_CHECK_URL, timeout=NETWORK_CHECK_TIMEOUT)
69
+ return True
70
+ except (urllib.error.URLError, OSError):
71
+ return False
72
+
73
+
74
+ def _dataset_already_downloaded(dataset_id: str, output_dir: str | None = None) -> str | None:
75
+ if output_dir is None:
76
+ output_dir = os.path.join(DEFAULT_DATA_DIR, dataset_id)
77
+ if not os.path.isdir(output_dir):
78
+ return None
79
+ t = os.path.join(output_dir, "train")
80
+ v = os.path.join(output_dir, "valid")
81
+ if not os.path.isdir(v):
82
+ v = os.path.join(output_dir, "val")
83
+ if os.path.isdir(t) and os.path.isdir(v) and os.listdir(t) and os.listdir(v):
84
+ return output_dir
85
+ return None
86
+
87
+
88
+ EXTERNAL_DATASETS = [
89
+ {"name": "COCO128", "imgs": 128, "cls": 80, "sz": "7MB", "fmt": "COCO", "url": "https://github.com/ultralytics/yolov5/releases/download/v1.0/coco128.zip"},
90
+ {"name": "Objects365", "imgs": 5000, "cls": 365, "sz": "~2GB", "fmt": "COCO", "url": "https://dorc.ks3-cn-beijing.ksyun.com/data-set/2020Objects365%E6%95%B0%E6%8D%AE%E9%9B%86/val/zhiyuan_objv2_val.json"},
91
+ {"name": "GlobalWheat", "imgs": 4700, "cls": 1, "sz": "~3GB", "fmt": "COCO", "url": "https://zenodo.org/record/4298502"},
92
+ {"name": "VisDrone", "imgs": 6471, "cls": 10, "sz": "~2GB", "fmt": "Custom", "url": "https://github.com/VisDrone/VisDrone-Dataset"},
93
+ {"name": "SKU-110K", "imgs": 11762, "cls": 1, "sz": "~3GB", "fmt": "CSV", "url": "https://github.com/eg4000/SKU110K_CVPR19"},
94
+ {"name": "WIDER Face", "imgs": 32203, "cls": 1, "sz": "~1.5GB", "fmt": "Custom", "url": "http://shuoyang1213.me/WIDERFACE/"},
95
+ {"name": "Cityscapes", "imgs": 5000, "cls": 30, "sz": "~11GB", "fmt": "Custom", "url": "https://www.cityscapes-dataset.com/"},
96
+ {"name": "BDD100K", "imgs": 100000, "cls": 10, "sz": "~6GB", "fmt": "Custom", "url": "https://bdd-data.berkeley.edu/"},
97
+ {"name": "KITTI", "imgs": 7481, "cls": 3, "sz": "~12GB", "fmt": "Custom", "url": "http://www.cvlibs.net/datasets/kitti/eval_object.php"},
98
+ {"name": "xView", "imgs": 1400, "cls": 60, "sz": "~4GB", "fmt": "GeoJSON", "url": "http://xviewdataset.org/"},
99
+ {"name": "OpenImages V7", "imgs": 1900000, "cls": 600, "sz": "~500GB", "fmt": "CSV", "url": "https://storage.googleapis.com/openimages/web/index.html"},
100
+ {"name": "LVIS v1", "imgs": 164000, "cls": 1203, "sz": "~20GB", "fmt": "COCO", "url": "https://www.lvisdataset.org/"},
101
+ ]
102
+
103
+ QUICK_START = [
104
+ {"id": "sample", "name": "FlashDet Sample", "imgs": 50, "cls": 80, "sz": "~1GB"},
105
+ {"id": "coco2017-val", "name": "COCO 2017 Val", "imgs": 5000, "cls": 80, "sz": "~1GB"},
106
+ {"id": "voc2007", "name": "VOC 2007", "imgs": 9963, "cls": 20, "sz": "~900MB"},
107
+ ]
@@ -0,0 +1,3 @@
1
+ """Download tab sub-package."""
2
+
3
+ from flashstudio.pages.data.download.tab import _render_download # noqa: F401
@@ -0,0 +1,75 @@
1
+ """Download tab — Quick Start, Native, and External dataset sections."""
2
+
3
+ import streamlit as st
4
+
5
+ from flashstudio.constants import INFER_NUM_CLASSES
6
+ from flashstudio.pages.data._common import (
7
+ _get_native_datasets, _dataset_already_downloaded,
8
+ EXTERNAL_DATASETS, QUICK_START,
9
+ )
10
+ from flashstudio.pages.data.helpers import (
11
+ _use_existing, _run_flashdet_download, _run_external_download,
12
+ )
13
+
14
+
15
+ def _render_download():
16
+ # Quick start — compact single row each
17
+ st.markdown("#### Quick Start")
18
+ for idx, ds in enumerate(QUICK_START):
19
+ did = ds["id"]
20
+ existing = _dataset_already_downloaded(did)
21
+ c1, c2 = st.columns([5, 1])
22
+ with c1:
23
+ st.markdown(
24
+ f'<div style="font-size:0.88rem;"><b>{ds["name"]}</b> · '
25
+ f'{ds["imgs"]} imgs · {ds["cls"]} cls · {ds["sz"]}</div>',
26
+ unsafe_allow_html=True,
27
+ )
28
+ with c2:
29
+ label = "Use" if existing else "Get"
30
+ if st.button(label, key=f"qs_{idx}", use_container_width=True, type="primary"):
31
+ if existing:
32
+ _use_existing(existing, ds)
33
+ else:
34
+ st.session_state.update({"dataset_name": ds["name"], "dataset_classes": ds["cls"], "dataset_id": did})
35
+ _run_flashdet_download(did)
36
+
37
+ st.divider()
38
+
39
+ # Native datasets
40
+ native = _get_native_datasets()
41
+ if native:
42
+ with st.expander(f"FlashDet Native ({len(native)} datasets)", expanded=False):
43
+ for i, ds in enumerate(native):
44
+ did = ds.get("id", ds.get("name", "").lower().replace(" ", ""))
45
+ existing = _dataset_already_downloaded(did)
46
+ c1, c2 = st.columns([5, 1])
47
+ with c1:
48
+ st.caption(f"**{ds.get('name', did)}** · {ds.get('classes', '?')} cls")
49
+ with c2:
50
+ if st.button("Use" if existing else "Get", key=f"nat_{i}", use_container_width=True):
51
+ if existing:
52
+ _use_existing(existing, ds)
53
+ else:
54
+ st.session_state.update({"dataset_name": ds.get("name", did), "dataset_id": did, "dataset_classes": ds.get("classes", INFER_NUM_CLASSES)})
55
+ _run_flashdet_download(did)
56
+
57
+ # External
58
+ with st.expander(f"External Datasets ({len(EXTERNAL_DATASETS)})", expanded=False):
59
+ for i, ds in enumerate(EXTERNAL_DATASETS):
60
+ c1, c2 = st.columns([5, 1])
61
+ with c1:
62
+ st.markdown(
63
+ f'<div style="font-size:0.88rem;"><b>{ds["name"]}</b> · '
64
+ f'{ds["imgs"]} · {ds["cls"]} cls · {ds["sz"]} · {ds["fmt"]}</div>',
65
+ unsafe_allow_html=True,
66
+ )
67
+ with c2:
68
+ url = ds.get("url", "")
69
+ is_manual = not url or "VisDrone" in url
70
+ if st.button("Link" if is_manual else "Get", key=f"ext_{i}", use_container_width=True):
71
+ if is_manual:
72
+ st.info(f"Manual: [{url}]({url})")
73
+ else:
74
+ st.session_state.update({"dataset_name": ds["name"], "dataset_classes": ds["cls"]})
75
+ _run_external_download(ds)