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,27 @@
1
+ """FlashStudio — Config building/applying helpers."""
2
+
3
+ import streamlit as st
4
+ from flashstudio.utils.config import apply_training_config
5
+ from flashstudio.constants import (
6
+ ARCH_FAMILIES, DEFAULT_MODEL_ARCH,
7
+ TRAIN_EPOCHS, TRAIN_BATCH_SIZE, TRAIN_LR, TRAIN_IMG_SIZE,
8
+ )
9
+
10
+
11
+ def _build_cfg():
12
+ return {
13
+ "model": {"family": st.session_state.get("arch_family", ARCH_FAMILIES[0]), "variant": st.session_state.get("model_arch", DEFAULT_MODEL_ARCH)},
14
+ "training": {"epochs": st.session_state.get("epochs", TRAIN_EPOCHS), "batch_size": st.session_state.get("batch_size", TRAIN_BATCH_SIZE),
15
+ "lr": st.session_state.get("lr", TRAIN_LR), "img_size": st.session_state.get("img_size", TRAIN_IMG_SIZE)},
16
+ "augmentation": {"mosaic": st.session_state.get("aug_mosaic", True), "mixup": st.session_state.get("aug_mixup", False)},
17
+ "advanced": {"amp": st.session_state.get("amp", True), "compile": st.session_state.get("compile_model", False)},
18
+ }
19
+
20
+
21
+ def _apply_cfg(config):
22
+ if "model" in config:
23
+ m = config["model"]
24
+ for k, sk in [("family", "arch_family"), ("variant", "model_arch"), ("pretrained", "pretrain_option")]:
25
+ if k in m:
26
+ st.session_state[sk] = m[k]
27
+ apply_training_config(config)
@@ -0,0 +1,3 @@
1
+ """FlashStudio — Training page package."""
2
+
3
+ from flashstudio.pages.training.page import render_training_page # noqa: F401
@@ -0,0 +1,24 @@
1
+ """Shared helpers for the training page."""
2
+
3
+ import os
4
+ import glob as glob_module
5
+ import streamlit as st
6
+ from flashstudio.constants import TRAINING_LOG_GLOB
7
+
8
+
9
+ def _get_save_dir():
10
+ """Single source of truth: Model > Advanced > Save Dir. Used across the whole project."""
11
+ from flashstudio.utils import get_state
12
+ val = get_state("save_dir")
13
+ if not val:
14
+ from flashstudio.utils import DEFAULTS
15
+ val = DEFAULTS["save_dir"]
16
+ return val
17
+
18
+
19
+ def _find_log_file(run_dir: str):
20
+ """Find the best training log file — prefer the largest (stdout-redirected has more data)."""
21
+ logs = glob_module.glob(os.path.join(run_dir, TRAINING_LOG_GLOB))
22
+ if not logs:
23
+ return None
24
+ return max(logs, key=lambda p: os.path.getsize(p))
@@ -0,0 +1,3 @@
1
+ """Training launch sub-package (tab, preflight, runner, controls, dialogs)."""
2
+
3
+ from flashstudio.pages.training.launch.tab import _render_start_tab # noqa: F401
@@ -0,0 +1,84 @@
1
+ """Training process controls — stop, pause, resume."""
2
+
3
+ import os
4
+ import signal
5
+ import streamlit as st
6
+
7
+
8
+ def _stop_training():
9
+ """Stop the training subprocess by killing its process tree."""
10
+ from flashstudio.utils import flash
11
+
12
+ pid = st.session_state.get("training_pid")
13
+ if pid:
14
+ try:
15
+ os.kill(pid, signal.SIGTERM)
16
+ flash(f"Training stopped (PID {pid})", "success")
17
+ except ProcessLookupError:
18
+ flash("Training process already finished", "info")
19
+ except PermissionError:
20
+ try:
21
+ os.kill(pid, signal.SIGKILL)
22
+ flash(f"Training force-killed (PID {pid})", "warning")
23
+ except Exception as e:
24
+ flash(f"Could not kill process {pid}: {e}", "error")
25
+ except Exception as e:
26
+ flash(f"Error stopping training: {e}", "error")
27
+ else:
28
+ flash("No training process to stop", "warning")
29
+
30
+ st.session_state["training_active"] = False
31
+ st.session_state["training_status"] = "Stopped"
32
+ st.session_state["training_paused"] = False
33
+ st.session_state["training_pid"] = None
34
+ fh = st.session_state.pop("_log_file_handle", None)
35
+ if fh and not fh.closed:
36
+ try:
37
+ fh.close()
38
+ except Exception:
39
+ pass
40
+ st.rerun()
41
+
42
+
43
+ def _pause_training():
44
+ """Pause the training subprocess by sending SIGSTOP."""
45
+ from flashstudio.utils import flash
46
+
47
+ pid = st.session_state.get("training_pid")
48
+ if pid:
49
+ try:
50
+ os.kill(pid, signal.SIGSTOP)
51
+ st.session_state["training_paused"] = True
52
+ st.session_state["training_status"] = "Paused"
53
+ flash(f"Training paused (PID {pid}). GPU memory still held.", "success")
54
+ except ProcessLookupError:
55
+ flash("Training process already finished", "info")
56
+ st.session_state["training_active"] = False
57
+ st.session_state["training_paused"] = False
58
+ except Exception as e:
59
+ flash(f"Could not pause training: {e}", "error")
60
+ else:
61
+ flash("No training process found to pause", "warning")
62
+ st.rerun()
63
+
64
+
65
+ def _resume_active_training():
66
+ """Resume a paused training subprocess by sending SIGCONT."""
67
+ from flashstudio.utils import flash
68
+
69
+ pid = st.session_state.get("training_pid")
70
+ if pid:
71
+ try:
72
+ os.kill(pid, signal.SIGCONT)
73
+ st.session_state["training_paused"] = False
74
+ st.session_state["training_status"] = "Running"
75
+ flash(f"Training resumed (PID {pid})", "success")
76
+ except ProcessLookupError:
77
+ flash("Training process already finished", "info")
78
+ st.session_state["training_active"] = False
79
+ st.session_state["training_paused"] = False
80
+ except Exception as e:
81
+ flash(f"Could not resume training: {e}", "error")
82
+ else:
83
+ flash("No training process found to resume", "warning")
84
+ st.rerun()
@@ -0,0 +1,247 @@
1
+ """Training launch dialogs — clean workspace, resume, folder management, config."""
2
+
3
+ import os
4
+ import yaml
5
+ import streamlit as st
6
+
7
+ from flashstudio.utils.filesystem import dir_size_str
8
+ from flashstudio.utils.config import build_training_config, apply_training_config
9
+ from flashstudio.constants import (
10
+ CKPT_BEST, CKPT_BEST_INFERENCE, CKPT_BEST_FP16,
11
+ CKPT_FINAL_INFERENCE, CKPT_FINAL_FP16, CKPT_LAST, CKPT_LAST_INFERENCE,
12
+ TRAINING_LOG_CSV,
13
+ )
14
+ from flashstudio.pages.training._common import _get_save_dir
15
+
16
+
17
+ _build_config_dict = build_training_config
18
+ _apply_config_dict = apply_training_config
19
+ _get_folder_size_str = dir_size_str
20
+ _get_dir_size = dir_size_str
21
+
22
+
23
+ def _render_folder_dialogs(runs_root: str, existing_folders: list):
24
+ """Render rename/delete dialogs for folder management."""
25
+ import shutil
26
+ from flashstudio.utils import flash
27
+
28
+ if st.session_state.get("wf_rename_target"):
29
+ target = st.session_state["wf_rename_target"]
30
+ with st.container(border=True):
31
+ rename_val = st.text_input("New name", value=target, key="rename_wf_input")
32
+ rc1, rc2 = st.columns(2)
33
+ with rc1:
34
+ if st.button("Rename", type="primary", key="do_rename_wf"):
35
+ if rename_val and rename_val != target:
36
+ old_path = os.path.join(runs_root, target)
37
+ new_path = os.path.join(runs_root, rename_val)
38
+ if os.path.exists(new_path):
39
+ flash(f"Rename failed: `{rename_val}` already exists", "error")
40
+ else:
41
+ os.rename(old_path, new_path)
42
+ if st.session_state.get("active_workflow_folder") == target:
43
+ st.session_state["active_workflow_folder"] = rename_val
44
+ st.session_state["run_name"] = rename_val
45
+ flash(f"Renamed `{target}` → `{rename_val}`", "success")
46
+ st.session_state.pop("wf_rename_target", None)
47
+ st.rerun()
48
+ with rc2:
49
+ if st.button("Cancel", key="cancel_rename_wf"):
50
+ st.session_state.pop("wf_rename_target", None)
51
+ st.rerun()
52
+
53
+ if st.session_state.get("wf_delete_target"):
54
+ target = st.session_state["wf_delete_target"]
55
+ with st.container(border=True):
56
+ st.error(f"Delete **{target}** and all contents?")
57
+ dc1, dc2 = st.columns(2)
58
+ with dc1:
59
+ if st.button("Delete", type="primary", key="confirm_delete_wf"):
60
+ shutil.rmtree(os.path.join(runs_root, target), ignore_errors=True)
61
+ if st.session_state.get("active_workflow_folder") == target:
62
+ st.session_state.pop("active_workflow_folder", None)
63
+ st.session_state.pop("wf_delete_target", None)
64
+ flash(f"Deleted `{target}`", "success")
65
+ st.rerun()
66
+ with dc2:
67
+ if st.button("Cancel", key="cancel_delete_wf"):
68
+ st.session_state.pop("wf_delete_target", None)
69
+ st.rerun()
70
+
71
+ if st.session_state.get("show_delete_all_wf_folders"):
72
+ with st.container(border=True):
73
+ st.error(f"Delete ALL {len(existing_folders)} folders? This is permanent!")
74
+ dc1, dc2 = st.columns(2)
75
+ with dc1:
76
+ if st.button("DELETE ALL", type="primary", key="confirm_delete_all_wf"):
77
+ for f in existing_folders:
78
+ shutil.rmtree(os.path.join(runs_root, f), ignore_errors=True)
79
+ st.session_state.pop("active_workflow_folder", None)
80
+ st.session_state.pop("show_delete_all_wf_folders", None)
81
+ st.rerun()
82
+ with dc2:
83
+ if st.button("Cancel", key="cancel_delete_all_wf"):
84
+ st.session_state.pop("show_delete_all_wf_folders", None)
85
+ st.rerun()
86
+
87
+
88
+ def _render_config_file_dialog(run_path: str):
89
+ """Compact save/load config dialog."""
90
+ with st.container(border=True):
91
+ tab_save, tab_load = st.tabs(["Save", "Load"])
92
+ with tab_save:
93
+ config = _build_config_dict()
94
+ yaml_str = yaml.dump(config, default_flow_style=False, sort_keys=False)
95
+ with st.expander("YAML Preview"):
96
+ st.code(yaml_str, language="yaml")
97
+ sc1, sc2 = st.columns(2)
98
+ with sc1:
99
+ st.download_button("Download", yaml_str, file_name="config.yaml", mime="text/yaml",
100
+ use_container_width=True, type="primary")
101
+ with sc2:
102
+ if st.button("Save to Run", use_container_width=True, key="save_config_to_run"):
103
+ os.makedirs(run_path, exist_ok=True)
104
+ with open(os.path.join(run_path, "config.yaml"), "w") as f:
105
+ yaml.dump(config, f, default_flow_style=False, sort_keys=False)
106
+ st.success("Saved")
107
+ with tab_load:
108
+ load_method = st.radio("From", ["Upload", "Path", "Runs"], key="config_load_method", horizontal=True)
109
+ config_to_load = None
110
+ if load_method == "Upload":
111
+ uploaded = st.file_uploader("YAML", type=["yaml", "yml"], key="config_file_upload")
112
+ if uploaded:
113
+ try:
114
+ config_to_load = yaml.safe_load(uploaded.read().decode("utf-8"))
115
+ except Exception as e:
116
+ st.error(str(e)[:40])
117
+ elif load_method == "Path":
118
+ cp = st.text_input("Path", placeholder="/path/config.yaml", key="config_path_input")
119
+ if cp and os.path.isfile(cp):
120
+ try:
121
+ with open(cp) as f:
122
+ config_to_load = yaml.safe_load(f.read())
123
+ except Exception as e:
124
+ st.error(str(e)[:40])
125
+ else:
126
+ sd = _get_save_dir()
127
+ parent = sd if os.path.isdir(sd) else ""
128
+ found = [(d, os.path.join(parent, d, "config.yaml"))
129
+ for d in (os.listdir(parent) if parent and os.path.isdir(parent) else [])
130
+ if os.path.isfile(os.path.join(parent, d, "config.yaml"))]
131
+ if found:
132
+ sel = st.selectbox("Run", range(len(found)), format_func=lambda i: found[i][0], key="config_from_run_select")
133
+ with open(found[sel][1]) as f:
134
+ config_to_load = yaml.safe_load(f.read())
135
+ if config_to_load:
136
+ if st.button("Apply", type="primary", key="apply_loaded_config", use_container_width=True):
137
+ _apply_config_dict(config_to_load)
138
+ st.rerun()
139
+
140
+
141
+ def _render_clean_workspace_dialog():
142
+ """Compact workspace cleanup."""
143
+ with st.container(border=True):
144
+ parent_dir = _get_save_dir()
145
+
146
+ if not os.path.isdir(parent_dir):
147
+ st.info("Nothing to clean.")
148
+ if st.button("Close", key="close_clean"):
149
+ st.session_state["show_clean_dialog"] = False
150
+ st.rerun()
151
+ return
152
+
153
+ runs = [{"name": d, "path": os.path.join(parent_dir, d), "size": _get_dir_size(os.path.join(parent_dir, d)),
154
+ "has_best": (os.path.isfile(os.path.join(parent_dir, d, CKPT_BEST))
155
+ or os.path.isfile(os.path.join(parent_dir, d, CKPT_BEST_INFERENCE)))}
156
+ for d in sorted(os.listdir(parent_dir)) if os.path.isdir(os.path.join(parent_dir, d))]
157
+ if not runs:
158
+ st.info("No runs."); return
159
+
160
+ clean_mode = st.radio("Mode", ["Selected", "Keep best only", "Delete incomplete", "Full reset"],
161
+ key="clean_mode", horizontal=True)
162
+
163
+ if clean_mode == "Selected":
164
+ selected = st.multiselect("Runs", [r["name"] for r in runs], key="runs_to_delete")
165
+ if selected and st.button("Delete", type="primary", key="do_delete_selected"):
166
+ import shutil
167
+ for n in selected:
168
+ shutil.rmtree(os.path.join(parent_dir, n), ignore_errors=True)
169
+ st.session_state["show_clean_dialog"] = False; st.rerun()
170
+ elif clean_mode == "Keep best only":
171
+ if st.button("Clean", type="primary", key="do_keep_best"):
172
+ for r in runs:
173
+ _cleanup_run_keep_best(r["path"])
174
+ st.session_state["show_clean_dialog"] = False; st.rerun()
175
+ elif clean_mode == "Delete incomplete":
176
+ incomplete = [r for r in runs if not r["has_best"]]
177
+ st.caption(f"{len(incomplete)} incomplete")
178
+ if incomplete and st.button("Delete", type="primary", key="do_delete_incomplete"):
179
+ import shutil
180
+ for r in incomplete:
181
+ shutil.rmtree(r["path"])
182
+ st.session_state["show_clean_dialog"] = False; st.rerun()
183
+ elif clean_mode == "Full reset":
184
+ if st.button("DELETE ALL", type="primary", key="do_delete_all"):
185
+ import shutil
186
+ shutil.rmtree(parent_dir); os.makedirs(parent_dir, exist_ok=True)
187
+ st.session_state["show_clean_dialog"] = False; st.rerun()
188
+
189
+ if st.button("Cancel", key="cancel_clean"):
190
+ st.session_state["show_clean_dialog"] = False; st.rerun()
191
+
192
+
193
+ def _render_resume_dialog():
194
+ """Compact resume dialog."""
195
+ with st.container(border=True):
196
+ parent_dir = _get_save_dir()
197
+ resumable = []
198
+ if os.path.isdir(parent_dir):
199
+ for d in sorted(os.listdir(parent_dir), reverse=True):
200
+ full = os.path.join(parent_dir, d)
201
+ ckpt = os.path.join(full, CKPT_LAST)
202
+ if not os.path.isfile(ckpt):
203
+ ckpt = os.path.join(full, CKPT_LAST_INFERENCE)
204
+ if os.path.isdir(full) and os.path.isfile(ckpt):
205
+ resumable.append({"name": d, "path": full, "ckpt": ckpt})
206
+ if not resumable:
207
+ st.info("No resumable runs.")
208
+ if st.button("Close", key="close_resume"):
209
+ st.session_state["show_resume_dialog"] = False; st.rerun()
210
+ return
211
+ rc1, rc2, rc3 = st.columns([4, 1, 1])
212
+ with rc1:
213
+ selected = st.selectbox("Run", [r["name"] for r in resumable], key="resume_run_select", label_visibility="collapsed")
214
+ with rc2:
215
+ if st.button("Resume", type="primary", key="do_resume"):
216
+ from flashstudio.pages.training.launch.runner import _start_training
217
+ for r in resumable:
218
+ if r["name"] == selected:
219
+ st.session_state.update({"resume_training": True, "resume_path": r["ckpt"],
220
+ "active_run_path": r["path"], "training_active": True,
221
+ "training_status": "Resuming", "show_resume_dialog": False})
222
+ _start_training(); break
223
+ with rc3:
224
+ if st.button("Cancel", key="cancel_resume"):
225
+ st.session_state["show_resume_dialog"] = False; st.rerun()
226
+
227
+
228
+ def _cleanup_run_keep_best(run_dir: str) -> int:
229
+ """Remove non-essential files from a run, keeping final/best models and results."""
230
+ from flashstudio.constants import ONNX_MODEL_FILE, ONNX_DATA_FILE, VIS_DIR_NAMES, GT_VERIFICATION_DIR
231
+ keep = {
232
+ CKPT_BEST, TRAINING_LOG_CSV,
233
+ CKPT_BEST_INFERENCE, CKPT_BEST_FP16,
234
+ CKPT_FINAL_INFERENCE, CKPT_FINAL_FP16,
235
+ ONNX_MODEL_FILE, ONNX_DATA_FILE,
236
+ }
237
+ removed = 0
238
+ for f in os.listdir(run_dir):
239
+ fpath = os.path.join(run_dir, f)
240
+ if os.path.isfile(fpath) and f not in keep and not f.endswith(".log"):
241
+ os.remove(fpath)
242
+ removed += 1
243
+ elif os.path.isdir(fpath) and f in (*VIS_DIR_NAMES, GT_VERIFICATION_DIR):
244
+ import shutil
245
+ shutil.rmtree(fpath)
246
+ removed += 1
247
+ return removed
@@ -0,0 +1,77 @@
1
+ """Pre-flight validation checks before training."""
2
+
3
+ import os
4
+ import streamlit as st
5
+
6
+ from flashstudio.utils.device import has_cuda
7
+ from flashstudio.pages.training._common import _get_save_dir
8
+
9
+
10
+ def _run_preflight_checks() -> list:
11
+ """Run pre-flight validation checks before training. Returns list of (label, ok, msg)."""
12
+ checks = []
13
+
14
+ train_path = st.session_state.get("train_img_path", "")
15
+ if train_path and os.path.isdir(train_path):
16
+ checks.append(("Dataset", True, ""))
17
+ else:
18
+ checks.append(("Dataset", False, "No train data path"))
19
+
20
+ if train_path and os.path.isdir(train_path):
21
+ ann_found = False
22
+ # Check in train_img_path itself
23
+ ann_file = os.path.join(train_path, "_annotations.coco.json")
24
+ json_files = [f for f in os.listdir(train_path) if f.endswith(".json")]
25
+ if os.path.isfile(ann_file) or json_files:
26
+ ann_found = True
27
+ # Check parent directory (common layout: train/images/ + train/_annotations.coco.json)
28
+ if not ann_found:
29
+ parent = os.path.dirname(train_path)
30
+ parent_ann = os.path.join(parent, "_annotations.coco.json")
31
+ parent_jsons = [f for f in os.listdir(parent) if f.endswith(".json")] if os.path.isdir(parent) else []
32
+ if os.path.isfile(parent_ann) or parent_jsons:
33
+ ann_found = True
34
+ # Check session state annotation path
35
+ if not ann_found and st.session_state.get("train_ann_path"):
36
+ if os.path.isfile(st.session_state["train_ann_path"]):
37
+ ann_found = True
38
+ checks.append(("Annotations", ann_found, "" if ann_found else "No COCO JSON found"))
39
+ else:
40
+ checks.append(("Annotations", False, "No data path"))
41
+
42
+ nc = st.session_state.get("num_classes", 0)
43
+ cls_names = st.session_state.get("class_names", "")
44
+ if isinstance(cls_names, list):
45
+ cls_names = "\n".join(cls_names)
46
+ if nc and nc > 0:
47
+ checks.append(("Classes", True, f"{nc} classes"))
48
+ elif isinstance(cls_names, str) and cls_names.strip():
49
+ n = len([c for c in cls_names.strip().split("\n") if c.strip()])
50
+ checks.append(("Classes", True, f"{n} classes"))
51
+ else:
52
+ checks.append(("Classes", False, "No classes — go to Data → Upload"))
53
+
54
+ from flashstudio.utils import get_state
55
+ arch = get_state("model_arch") or ""
56
+ if arch:
57
+ checks.append(("Model", True, ""))
58
+ else:
59
+ checks.append(("Model", False, "No model selected"))
60
+
61
+ if has_cuda():
62
+ checks.append(("GPU", True, "CUDA"))
63
+ else:
64
+ checks.append(("GPU", True, "CPU (slow)"))
65
+
66
+ save_dir = _get_save_dir()
67
+ try:
68
+ stat = os.statvfs(os.path.dirname(save_dir) if not os.path.isdir(save_dir) else save_dir)
69
+ free_gb = (stat.f_bavail * stat.f_frsize) / (1024**3)
70
+ if free_gb > 1:
71
+ checks.append(("Disk", True, ""))
72
+ else:
73
+ checks.append(("Disk", False, f"Only {free_gb:.1f} GB free"))
74
+ except (OSError, AttributeError):
75
+ checks.append(("Disk", True, ""))
76
+
77
+ return checks