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,245 @@
1
+ """FlashStudio — Centralized constants. Single source of truth for all magic values."""
2
+
3
+ import os
4
+
5
+ # ════════════════════════════════════════
6
+ # FILE EXTENSIONS
7
+ # ════════════════════════════════════════
8
+
9
+ IMG_EXTENSIONS = (".jpg", ".jpeg", ".png", ".bmp", ".webp")
10
+ IMG_EXTENSIONS_ALL = (".jpg", ".jpeg", ".png", ".bmp", ".webp", ".tiff")
11
+ VIDEO_EXTENSIONS = (".mp4", ".avi", ".mov", ".mkv")
12
+ WEIGHT_EXTENSIONS = (".pt", ".pth", ".onnx", ".engine")
13
+ ARCHIVE_EXTENSIONS = (".zip", ".tar", ".gz", ".tar.gz", ".tgz")
14
+
15
+ # ════════════════════════════════════════
16
+ # CHECKPOINT FILE NAMES
17
+ # ════════════════════════════════════════
18
+
19
+ CKPT_BEST_INFERENCE = "model_best_inference.pth"
20
+ CKPT_BEST = "checkpoint_best.pth"
21
+ CKPT_LAST_INFERENCE = "model_last_inference.pth"
22
+ CKPT_LAST = "checkpoint_last.pth"
23
+ CKPT_FINAL_INFERENCE = "model_final_inference.pth"
24
+ CKPT_FINAL_FP16 = "model_final_fp16.pth"
25
+ CKPT_BEST_FP16 = "model_best_fp16.pth"
26
+
27
+ COMPLETE_MARKERS = (CKPT_FINAL_INFERENCE, CKPT_FINAL_FP16, CKPT_BEST_INFERENCE, CKPT_BEST)
28
+ BEST_WEIGHT_PRIORITY = [CKPT_BEST_INFERENCE, CKPT_BEST, CKPT_FINAL_INFERENCE, CKPT_FINAL_FP16]
29
+
30
+ TRAINING_LOG_CSV = "training_log.csv"
31
+
32
+ # ════════════════════════════════════════
33
+ # PATHS
34
+ # ════════════════════════════════════════
35
+
36
+ # Project root: FlashStudio/ directory (parent of flashstudio/ package)
37
+ _PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
38
+ PROJECT_ROOT = _PROJECT_ROOT
39
+
40
+ DEFAULT_SAVE_DIR = os.path.join(_PROJECT_ROOT, "flashstudio_workspaces")
41
+ DEFAULT_DATA_DIR = os.path.join(_PROJECT_ROOT, "data")
42
+ NETWORK_CHECK_URL = "http://images.cocodataset.org"
43
+ NETWORK_CHECK_TIMEOUT = 5
44
+
45
+ # ════════════════════════════════════════
46
+ # COLORS (for bounding boxes, visualization)
47
+ # ════════════════════════════════════════
48
+
49
+ BBOX_COLORS_RGB = [
50
+ (255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0),
51
+ (255, 0, 255), (0, 255, 255), (128, 0, 0), (0, 128, 0),
52
+ ]
53
+
54
+ BBOX_COLORS_HEX = [
55
+ "#FF6B6B", "#4ECDC4", "#45B7D1", "#96CEB4", "#FFEAA7",
56
+ "#DDA0DD", "#98D8C8", "#F7DC6F", "#BB8FCE", "#85C1E9",
57
+ ]
58
+
59
+ # Theme colors
60
+ COLOR_PRIMARY = "#7C3AED"
61
+ COLOR_SUCCESS = "#10B981"
62
+ COLOR_WARNING = "#F59E0B"
63
+ COLOR_ERROR = "#EF4444"
64
+ COLOR_TEXT_PRIMARY = "#1A1A2E"
65
+ COLOR_TEXT_SECONDARY = "#6B7280"
66
+ COLOR_TEXT_MUTED = "#9CA3AF"
67
+ COLOR_TEXT_BODY = "#4B5563"
68
+ COLOR_BORDER = "#F0F0F5"
69
+ COLOR_BG_HIGHLIGHT = "#F5F3FF"
70
+
71
+ # ════════════════════════════════════════
72
+ # INFERENCE DEFAULTS
73
+ # ════════════════════════════════════════
74
+
75
+ INFER_CONF_THRESHOLD = 0.25
76
+ INFER_NMS_THRESHOLD = 0.45
77
+ INFER_IMG_SIZE = 640
78
+ INFER_NUM_CLASSES = 80
79
+ INFER_MAX_FRAMES = 300
80
+ INFER_STREAM_DURATION = 60
81
+ INFER_FRAME_SKIP = 1
82
+ INFER_DISPLAY_WIDTH = 650
83
+ INFER_DEFAULT_RESOLUTION = (640, 480)
84
+
85
+ # ════════════════════════════════════════
86
+ # EXPORT DEFAULTS
87
+ # ════════════════════════════════════════
88
+
89
+ EXPORT_IMG_SIZES = [320, 416, 640]
90
+ EXPORT_OPSET_MIN = 11
91
+ EXPORT_OPSET_MAX = 18
92
+ EXPORT_OPSET_DEFAULT = 13
93
+ EXPORT_FORMATS = ["ONNX", "TorchScript"]
94
+
95
+ EXPORT_WEIGHT_MAP = {
96
+ "Best (inference)": [CKPT_BEST_INFERENCE, CKPT_BEST],
97
+ "Best (FP16)": [CKPT_BEST_FP16],
98
+ "Last": [CKPT_LAST_INFERENCE, CKPT_LAST],
99
+ }
100
+
101
+ # ════════════════════════════════════════
102
+ # TRAINING DEFAULTS (mirrors DEFAULTS in utils)
103
+ # ════════════════════════════════════════
104
+
105
+ TRAIN_EPOCHS = 100
106
+ TRAIN_BATCH_SIZE = 16
107
+ TRAIN_LR = 1e-3
108
+ TRAIN_IMG_SIZE = 320
109
+ TRAIN_WEIGHT_DECAY = 0.05
110
+ TRAIN_WARMUP_EPOCHS = 3
111
+ TRAIN_PATIENCE = 50
112
+ TRAIN_NUM_WORKERS = 4
113
+ TRAIN_GRAD_ACCUM = 1
114
+ TRAIN_VAL_INTERVAL = 5
115
+ TRAIN_LR_FINAL_RATIO = 0.1
116
+
117
+ BATCH_SIZE_OPTIONS = [2, 4, 8, 16, 32, 64, 128]
118
+ IMG_SIZE_OPTIONS = [320, 416, 640]
119
+
120
+ AUTOREFRESH_INTERVAL_MS = 5000
121
+ MAX_DISPLAY_RUNS = 8
122
+ VIS_QUEUE_SIZE = 3
123
+
124
+ VIS_DIR_NAMES = ("visualizations", "plots", "vis")
125
+ VIS_SKIP_FILE = "latest_visualization.jpg"
126
+ GT_VERIFICATION_DIR = "gt_verification"
127
+ GT_REPORT_FILE = "verification_report.json"
128
+ GT_SUMMARY_FILE = "verification_summary.txt"
129
+ TRAINING_LOG_GLOB = "train_*.log"
130
+ ONNX_MODEL_FILE = "model.onnx"
131
+ ONNX_DATA_FILE = "model.onnx.data"
132
+ CONFIG_YAML_FILE = "config.yaml"
133
+ RESULTS_JSON_FILE = "results.json"
134
+
135
+ # ════════════════════════════════════════
136
+ # MODEL ARCHITECTURE
137
+ # ════════════════════════════════════════
138
+
139
+ DEFAULT_MODEL_ARCH = "FlashDet-Pico"
140
+ DEFAULT_ARCH_FAMILY = "FlashDet (recommended)"
141
+ MODEL_SIZE_DEFAULT = "n"
142
+
143
+ ARCH_FAMILIES = ["FlashDet (recommended)", "YOLOv8", "YOLOv9", "YOLOv10", "YOLOv11", "YOLOX"]
144
+ OPTIMIZERS = ["AdamW", "SGD", "MuSGD"]
145
+ DEFAULT_OPTIMIZER = "AdamW"
146
+ LORA_VARIANTS = ["standard", "dora", "lora_plus", "adalora", "ortho", "lora_fa"]
147
+ LORA_RANK_DEFAULT = 8
148
+ LORA_ALPHA_DEFAULT = 16.0
149
+ LORA_DROPOUT_DEFAULT = 0.05
150
+ LORA_TARGETS_DEFAULT = ["backbone", "fpn"]
151
+ QLORA_DTYPE_DEFAULT = "int8"
152
+ CHUNKED_LOSS_CHUNK_SIZE = 1024
153
+ DEFAULT_FINETUNE_STRATEGY = "Full fine-tune"
154
+ DEFAULT_PRETRAIN_OPTION = "COCO pretrained"
155
+
156
+ FLASHDET_MODELS = {
157
+ "FlashDet-Pico": {"size": "p", "params": "~298K", "speed": "Ultra-fast", "backbone": "LiteBackbone(0.5x)", "neck": "PicoNeck(64ch)", "head": "E2EDualHead", "for": "Edge/Mobile"},
158
+ "FlashDet-Nano": {"size": "n", "params": "~790K", "speed": "Very fast", "backbone": "FlashBB(stem=32)", "neck": "PicoNeck(96ch)", "head": "E2EDualHead", "for": "IoT"},
159
+ "FlashDet-Small": {"size": "s", "params": "~1.8M", "speed": "Fast", "backbone": "FlashBB(stem=48)", "neck": "PicoNeck(128ch)", "head": "E2EDualHead", "for": "General"},
160
+ "FlashDet-Medium": {"size": "m", "params": "~3.6M", "speed": "Balanced", "backbone": "FlashBB(stem=64)", "neck": "PicoNeck(192ch)", "head": "E2EDualHead", "for": "Accuracy"},
161
+ "FlashDet-Large": {"size": "l", "params": "~5.8M", "speed": "Accurate", "backbone": "FlashBB(stem=80)", "neck": "PicoNeck(256ch)", "head": "E2EDualHead", "for": "Accuracy"},
162
+ "FlashDet-X": {"size": "x", "params": "~9.0M", "speed": "Max acc", "backbone": "FlashBB(stem=96)", "neck": "PicoNeck(320ch)", "head": "E2EDualHead", "for": "Server"},
163
+ }
164
+
165
+ # ════════════════════════════════════════
166
+ # ZONE DRAWING DEFAULTS
167
+ # ════════════════════════════════════════
168
+
169
+ ZONE_MAX_X = 1920
170
+ ZONE_MAX_Y = 1080
171
+ ZONE_DEFAULT_LINE = [(100, 240), (540, 240)]
172
+ ZONE_DEFAULT_POLYGON = "100,100\n500,100\n500,400\n100,400"
173
+
174
+ # ════════════════════════════════════════
175
+ # FONT
176
+ # ════════════════════════════════════════
177
+
178
+ def _resolve_font_path():
179
+ """Find a usable TrueType font across platforms, with env override."""
180
+ env = os.environ.get("FLASHSTUDIO_FONT_PATH", "")
181
+ if env and os.path.isfile(env):
182
+ return env
183
+ candidates = [
184
+ "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", # Debian/Ubuntu
185
+ "/usr/share/fonts/dejavu-sans-fonts/DejaVuSans-Bold.ttf", # Fedora/RHEL
186
+ "/usr/share/fonts/TTF/DejaVuSans-Bold.ttf", # Arch
187
+ "/System/Library/Fonts/Helvetica.ttc", # macOS
188
+ "C:\\Windows\\Fonts\\arial.ttf", # Windows
189
+ ]
190
+ for c in candidates:
191
+ if os.path.isfile(c):
192
+ return c
193
+ return ""
194
+
195
+ FONT_PATH = _resolve_font_path()
196
+ FONT_PATH_LINUX = FONT_PATH # backward compat alias
197
+ FONT_SIZE_DEFAULT = 14
198
+
199
+ # ════════════════════════════════════════
200
+ # UI LIMITS
201
+ # ════════════════════════════════════════
202
+
203
+ MAX_PREVIEW_COLS = 4
204
+ MAX_PREVIEW_IMAGES = 6
205
+ MAX_WEIGHTS_DISPLAY = 4
206
+ SIDEBAR_LABEL_TRUNCATE = 20
207
+ GPU_NAME_TRUNCATE = 18
208
+ DATASET_NAME_TRUNCATE = 18
209
+
210
+ # ════════════════════════════════════════
211
+ # COCO CLASSES (standard 80-class COCO)
212
+ # ════════════════════════════════════════
213
+
214
+ COCO_CLASSES = [
215
+ "person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck",
216
+ "boat", "traffic light", "fire hydrant", "stop sign", "parking meter", "bench",
217
+ "bird", "cat", "dog", "horse", "sheep", "cow", "elephant", "bear", "zebra",
218
+ "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee",
219
+ "skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove",
220
+ "skateboard", "surfboard", "tennis racket", "bottle", "wine glass", "cup",
221
+ "fork", "knife", "spoon", "bowl", "banana", "apple", "sandwich", "orange",
222
+ "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "couch",
223
+ "potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse",
224
+ "remote", "keyboard", "cell phone", "microwave", "oven", "toaster", "sink",
225
+ "refrigerator", "book", "clock", "vase", "scissors", "teddy bear",
226
+ "hair drier", "toothbrush",
227
+ ]
228
+
229
+ # ════════════════════════════════════════
230
+ # SIZE FORMATTING THRESHOLDS
231
+ # ════════════════════════════════════════
232
+
233
+ SIZE_GB = 1_073_741_824
234
+ SIZE_MB = 1_048_576
235
+ SIZE_KB = 1024
236
+
237
+
238
+ def format_bytes(total_bytes: int) -> str:
239
+ """Format byte count to human-readable string."""
240
+ if total_bytes > SIZE_GB:
241
+ return f"{total_bytes / SIZE_GB:.1f} GB"
242
+ elif total_bytes > SIZE_MB:
243
+ return f"{total_bytes / SIZE_MB:.0f} MB"
244
+ else:
245
+ return f"{total_bytes / SIZE_KB:.0f} KB"
@@ -0,0 +1,3 @@
1
+ """FlashStudio core package."""
2
+
3
+ from flashstudio.core.constants import * # noqa: F401,F403
@@ -0,0 +1,3 @@
1
+ """Re-export all constants from the canonical location."""
2
+
3
+ from flashstudio.constants import * # noqa: F401,F403
@@ -0,0 +1,288 @@
1
+ """Launch FlashStudio UI — works locally and inside Google Colab."""
2
+
3
+ import subprocess
4
+ import sys
5
+ import os
6
+ import re
7
+ import time as _time
8
+ from pathlib import Path
9
+
10
+ from flashstudio.utils.device import is_colab
11
+
12
+ _STREAMLIT_CONFIG = """\
13
+ [theme]
14
+ primaryColor = "#7C3AED"
15
+ backgroundColor = "#FFFFFF"
16
+ secondaryBackgroundColor = "#F8F9FB"
17
+ textColor = "#1A1A2E"
18
+ font = "sans serif"
19
+
20
+ [server]
21
+ headless = true
22
+ port = 8501
23
+ enableCORS = false
24
+ enableXsrfProtection = false
25
+
26
+ [client]
27
+ showSidebarNavigation = false
28
+
29
+ [browser]
30
+ gatherUsageStats = false
31
+ """
32
+
33
+
34
+ def _get_app_path() -> str:
35
+ return str(Path(__file__).parent / "app.py")
36
+
37
+
38
+ def _ensure_streamlit_config():
39
+ """Write the default Streamlit config if one doesn't already exist."""
40
+ config_dir = Path.home() / ".streamlit"
41
+ config_file = config_dir / "config.toml"
42
+ if config_file.exists():
43
+ return
44
+ config_dir.mkdir(parents=True, exist_ok=True)
45
+ config_file.write_text(_STREAMLIT_CONFIG)
46
+
47
+
48
+ def launch(port: int = 8501, share: bool = True, ngrok_token: str | None = None):
49
+ """Launch FlashStudio Streamlit app.
50
+
51
+ Args:
52
+ port: Port to run the Streamlit server on.
53
+ share: If True and running in Colab, creates a public tunnel
54
+ (works on mobile too).
55
+ ngrok_token: Optional ngrok auth token. If not provided, uses
56
+ NGROK_TOKEN env var.
57
+ """
58
+ _ensure_streamlit_config()
59
+ app_path = _get_app_path()
60
+
61
+ if is_colab():
62
+ return _launch_colab(app_path, port, share, ngrok_token)
63
+ else:
64
+ _launch_local(app_path, port)
65
+ return None
66
+
67
+
68
+ # ---------------------------------------------------------------------------
69
+ # Tunnel helpers
70
+ # ---------------------------------------------------------------------------
71
+
72
+ def _start_cloudflared_tunnel(port: int) -> str | None:
73
+ """Start a free Cloudflare tunnel (no sign-up required).
74
+
75
+ Returns the public https URL or None on failure.
76
+ """
77
+ binary = "/usr/local/bin/cloudflared"
78
+ if not os.path.isfile(binary):
79
+ try:
80
+ subprocess.check_call(
81
+ ["wget", "-q",
82
+ "https://github.com/cloudflare/cloudflared/releases/latest"
83
+ "/download/cloudflared-linux-amd64",
84
+ "-O", binary],
85
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
86
+ )
87
+ os.chmod(binary, 0o755)
88
+ except Exception:
89
+ return None
90
+
91
+ tunnel_proc = subprocess.Popen(
92
+ [binary, "tunnel", "--url", f"http://localhost:{port}"],
93
+ stdout=subprocess.PIPE,
94
+ stderr=subprocess.PIPE,
95
+ )
96
+
97
+ url_re = re.compile(r"https://[a-z0-9-]+\.trycloudflare\.com")
98
+ deadline = _time.monotonic() + 20
99
+ while _time.monotonic() < deadline:
100
+ line = tunnel_proc.stderr.readline().decode("utf-8", errors="replace")
101
+ if not line:
102
+ if tunnel_proc.poll() is not None:
103
+ return None
104
+ _time.sleep(0.2)
105
+ continue
106
+ match = url_re.search(line)
107
+ if match:
108
+ return match.group(0)
109
+
110
+ return None
111
+
112
+
113
+ def _start_ngrok_tunnel(port: int, ngrok_token: str) -> str | None:
114
+ """Start an ngrok tunnel. Requires an auth token."""
115
+ try:
116
+ from pyngrok import ngrok, conf
117
+ except ImportError:
118
+ try:
119
+ subprocess.check_call(
120
+ [sys.executable, "-m", "pip", "install", "-q", "pyngrok"],
121
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
122
+ )
123
+ from pyngrok import ngrok, conf
124
+ except Exception:
125
+ return None
126
+ try:
127
+ conf.get_default().auth_token = ngrok_token
128
+ return str(ngrok.connect(port, "http"))
129
+ except Exception:
130
+ return None
131
+
132
+
133
+ def _get_colab_proxy_url(port: int) -> str | None:
134
+ """Get Colab's built-in proxy URL (only works in the same browser session)."""
135
+ try:
136
+ from google.colab.output import eval_js
137
+ result = eval_js(f"google.colab.kernel.proxyPort({port})")
138
+ if result and str(result).startswith("http"):
139
+ return str(result)
140
+ except Exception:
141
+ pass
142
+ return None
143
+
144
+
145
+ # ---------------------------------------------------------------------------
146
+ # Colab launcher
147
+ # ---------------------------------------------------------------------------
148
+
149
+ def _launch_colab(app_path: str, port: int, share: bool, ngrok_token: str | None):
150
+ """Launch in Google Colab with a public URL that works on any device."""
151
+ import urllib.request
152
+
153
+ print(" Starting FlashStudio...", end="", flush=True)
154
+
155
+ proc = subprocess.Popen(
156
+ [sys.executable, "-m", "streamlit", "run", app_path,
157
+ "--server.port", str(port),
158
+ "--server.headless", "true",
159
+ "--server.enableCORS", "false",
160
+ "--server.enableXsrfProtection", "false"],
161
+ stdout=subprocess.PIPE,
162
+ stderr=subprocess.PIPE,
163
+ )
164
+
165
+ ready = False
166
+ for _ in range(30):
167
+ _time.sleep(1)
168
+ if proc.poll() is not None:
169
+ _, stderr = proc.communicate()
170
+ err_msg = (stderr.decode("utf-8", errors="replace")[-500:]
171
+ if stderr else "Unknown error")
172
+ print(f"\n\n FlashStudio failed to start:\n {err_msg}")
173
+ return proc
174
+ try:
175
+ urllib.request.urlopen(
176
+ f"http://localhost:{port}/_stcore/health", timeout=2)
177
+ ready = True
178
+ break
179
+ except Exception:
180
+ print(".", end="", flush=True)
181
+
182
+ if ready:
183
+ print(" Ready!")
184
+ else:
185
+ print(" (still loading, URL may take a moment)")
186
+
187
+ # --- Obtain a public URL ---
188
+ # Priority: cloudflared (free, works everywhere including mobile)
189
+ # > ngrok (needs token) > Colab proxy (desktop-only)
190
+ public_url = None
191
+ tunnel_type = None
192
+
193
+ if share:
194
+ # 1) Free Cloudflare tunnel — works on mobile, no sign-up
195
+ print(" Setting up public URL (works on mobile)...", end="", flush=True)
196
+ public_url = _start_cloudflared_tunnel(port)
197
+ if public_url:
198
+ tunnel_type = "cloudflared"
199
+ print(" Done!")
200
+
201
+ # 2) ngrok — if a token is available
202
+ if not public_url:
203
+ token = ngrok_token or os.environ.get("NGROK_TOKEN", "")
204
+ if token:
205
+ public_url = _start_ngrok_tunnel(port, token)
206
+ if public_url:
207
+ tunnel_type = "ngrok"
208
+ print(" Done!")
209
+
210
+ if not public_url:
211
+ print(" Skipped (using Colab proxy)")
212
+
213
+ # 3) Colab's built-in proxy (fallback — desktop browser only)
214
+ colab_proxy_url = _get_colab_proxy_url(port)
215
+ if not public_url:
216
+ public_url = colab_proxy_url
217
+
218
+ # --- Display results ---
219
+ print(f"\n{'=' * 60}")
220
+ print(" FlashStudio is running!")
221
+ if public_url:
222
+ print(f" Open: {public_url}")
223
+ if tunnel_type:
224
+ print(f" (works on mobile & any device)")
225
+ else:
226
+ print(f" ⚠ This URL only works in this browser.")
227
+ print(f" On mobile? Re-run with: launch(share=True)")
228
+ else:
229
+ print(f" Port: {port}")
230
+ print("")
231
+ print(" Run this in a new cell to get the URL:")
232
+ print(f" from google.colab.output import eval_js")
233
+ print(f" print(eval_js('google.colab.kernel.proxyPort({port})'))")
234
+ print(f"{'=' * 60}\n")
235
+
236
+ # Show clickable link
237
+ if public_url:
238
+ try:
239
+ from IPython.display import display, HTML
240
+ mobile_badge = ""
241
+ if tunnel_type:
242
+ mobile_badge = (
243
+ '<span style="display:inline-block;font-size:11px;'
244
+ 'padding:2px 8px;background:#10B981;color:white;'
245
+ 'border-radius:4px;margin-left:8px;vertical-align:middle;">'
246
+ 'Mobile Ready</span>'
247
+ )
248
+ display(HTML(
249
+ f'<div style="margin:8px 0;">'
250
+ f'<a href="{public_url}" target="_blank" '
251
+ f'style="display:inline-block;font-size:16px;padding:10px 24px;'
252
+ f'background:#7C3AED;color:white;border-radius:8px;'
253
+ f'text-decoration:none;font-weight:600;">'
254
+ f'Open FlashStudio</a>{mobile_badge}</div>'
255
+ ))
256
+ except Exception:
257
+ pass
258
+
259
+ # Embed Streamlit directly in the Colab cell output
260
+ try:
261
+ from google.colab import output
262
+ output.serve_kernel_port_as_iframe(port, height=800)
263
+ except Exception:
264
+ if not public_url:
265
+ try:
266
+ from IPython.display import display, HTML
267
+ display(HTML(
268
+ f'<div style="padding:12px;background:#FEF3C7;'
269
+ f'border-radius:8px;margin:8px 0;font-size:14px;">'
270
+ f'Run in a <b>new cell</b>:<br>'
271
+ f'<code style="background:#F3F4F6;padding:4px 8px;'
272
+ f'border-radius:4px;">'
273
+ f'from google.colab import output; '
274
+ f'output.serve_kernel_port_as_iframe({port}, height=800)'
275
+ f'</code></div>'
276
+ ))
277
+ except Exception:
278
+ pass
279
+
280
+ return proc
281
+
282
+
283
+ def _launch_local(app_path: str, port: int):
284
+ """Launch locally with streamlit."""
285
+ subprocess.run(
286
+ [sys.executable, "-m", "streamlit", "run", app_path,
287
+ "--server.port", str(port)],
288
+ )
@@ -0,0 +1,8 @@
1
+ """FlashStudio pages package — each page is a sub-package."""
2
+
3
+ from flashstudio.pages.dashboard.page import render_dashboard # noqa: F401
4
+ from flashstudio.pages.data.page import render_data_page # noqa: F401
5
+ from flashstudio.pages.model.page import render_model_page # noqa: F401
6
+ from flashstudio.pages.training.page import render_training_page # noqa: F401
7
+ from flashstudio.pages.export.page import render_export_page # noqa: F401
8
+ from flashstudio.pages.inference.page import render_inference_page # noqa: F401
@@ -0,0 +1,3 @@
1
+ """FlashStudio — Dashboard page package."""
2
+
3
+ from flashstudio.pages.dashboard.page import render_dashboard # noqa: F401
@@ -0,0 +1,10 @@
1
+ """Shared imports for the dashboard page."""
2
+
3
+ from flashstudio.constants import ( # noqa: F401
4
+ IMG_EXTENSIONS, DEFAULT_SAVE_DIR, MAX_DISPLAY_RUNS,
5
+ CKPT_BEST, CKPT_BEST_INFERENCE, CKPT_FINAL_INFERENCE,
6
+ CKPT_FINAL_FP16, TRAINING_LOG_CSV,
7
+ COLOR_SUCCESS, COLOR_TEXT_PRIMARY, COLOR_TEXT_SECONDARY,
8
+ COLOR_TEXT_MUTED, COLOR_BORDER, COLOR_BG_HIGHLIGHT,
9
+ GPU_NAME_TRUNCATE, DATASET_NAME_TRUNCATE,
10
+ )
@@ -0,0 +1,3 @@
1
+ """Dashboard overview sub-package."""
2
+
3
+ from flashstudio.pages.dashboard.overview.tab import render_overview # noqa: F401
@@ -0,0 +1,33 @@
1
+ """Dashboard — key metrics section (GPU, dataset, model, best mAP)."""
2
+
3
+ import os
4
+ import streamlit as st
5
+ from flashstudio.pages.dashboard._common import (
6
+ IMG_EXTENSIONS,
7
+ GPU_NAME_TRUNCATE, DATASET_NAME_TRUNCATE,
8
+ )
9
+
10
+
11
+ def render_metrics(gpu, stats):
12
+ m1, m2, m3, m4 = st.columns(4)
13
+ with m1:
14
+ gpu_name = gpu["name"][:GPU_NAME_TRUNCATE] if gpu["available"] else "CPU only"
15
+ vram = f"{gpu['memory_total']:.0f} GB" if gpu["available"] else "\u2014"
16
+ st.metric("GPU", gpu_name, delta=f"VRAM: {vram}", delta_color="off")
17
+ with m2:
18
+ ds_name = (st.session_state.get("dataset_name") or "Not loaded")[:DATASET_NAME_TRUNCATE]
19
+ train_path = st.session_state.get("train_img_path", "")
20
+ img_count = 0
21
+ if train_path and os.path.isdir(train_path):
22
+ img_count = len([f for f in os.listdir(train_path) if f.lower().endswith(IMG_EXTENSIONS)])
23
+ st.metric("Dataset", ds_name, delta=f"{img_count} images", delta_color="off")
24
+ with m3:
25
+ from flashstudio.utils import get_state
26
+ model = get_state("model_arch")
27
+ model_short = model.replace("FlashDet-", "")
28
+ epochs = get_state("epochs")
29
+ st.metric("Model", model_short, delta=f"{epochs} epochs", delta_color="off")
30
+ with m4:
31
+ best_map = f"{stats['best_map']:.3f}" if stats and stats["best_map"] is not None else "\u2014"
32
+ runs_count = stats["runs"] if stats else 0
33
+ st.metric("Best mAP", best_map, delta=f"{runs_count} runs", delta_color="off")
@@ -0,0 +1,35 @@
1
+ """Dashboard — pipeline status section."""
2
+
3
+ import streamlit as st
4
+ from flashstudio.pages.dashboard._common import (
5
+ COLOR_SUCCESS, COLOR_TEXT_PRIMARY, COLOR_TEXT_MUTED, COLOR_BORDER,
6
+ )
7
+
8
+
9
+ def render_pipeline_status():
10
+ from flashstudio.utils import get_state
11
+ ds = st.session_state.get("dataset_name")
12
+ model = get_state("model_arch")
13
+ train_status = st.session_state.get("training_status", "")
14
+ exported = st.session_state.get("exported_files")
15
+
16
+ steps = [
17
+ ("Data", ds is not None, ds or "Not loaded"),
18
+ ("Model", model is not None, model.replace("FlashDet-", "") if model else "Not set"),
19
+ ("Training", train_status in ("Complete", "Running"), train_status or "Not started"),
20
+ ("Export", bool(exported), "Ready" if exported else "Pending"),
21
+ ]
22
+
23
+ for label, done, detail in steps:
24
+ color = COLOR_SUCCESS if done else "#D1D5DB"
25
+ text_color = COLOR_TEXT_PRIMARY if done else COLOR_TEXT_MUTED
26
+ st.markdown(
27
+ f'<div style="display:flex;align-items:center;gap:0.6rem;padding:0.35rem 0;'
28
+ f'border-bottom:1px solid {COLOR_BORDER};">'
29
+ f'<div style="width:10px;height:10px;border-radius:50%;background:{color};flex-shrink:0;"></div>'
30
+ f'<div style="flex:1;">'
31
+ f'<span style="font-size:0.85rem;font-weight:600;color:{text_color};">{label}</span>'
32
+ f'<span style="font-size:0.78rem;color:{COLOR_TEXT_MUTED};margin-left:0.5rem;">{detail}</span>'
33
+ f'</div></div>',
34
+ unsafe_allow_html=True,
35
+ )
@@ -0,0 +1,23 @@
1
+ """Dashboard — project info banner section."""
2
+
3
+ import streamlit as st
4
+ from flashstudio.pages.dashboard._common import (
5
+ COLOR_SUCCESS, COLOR_TEXT_PRIMARY, COLOR_TEXT_SECONDARY,
6
+ COLOR_TEXT_MUTED, COLOR_BG_HIGHLIGHT,
7
+ )
8
+
9
+
10
+ def render_project_banner(project):
11
+ proj_name = project.get("name", "Untitled")
12
+ proj_desc = project.get("description", "")
13
+ created = project.get("created", "")[:10]
14
+ modified = project.get("last_modified", "")[:10]
15
+ st.markdown(
16
+ f'<div style="background:{COLOR_BG_HIGHLIGHT};border-left:4px solid {COLOR_SUCCESS};padding:0.8rem 1rem;'
17
+ f'border-radius:0 8px 8px 0;margin-bottom:0.8rem;">'
18
+ f'<div style="font-size:1.1rem;font-weight:700;color:{COLOR_TEXT_PRIMARY};">{proj_name}</div>'
19
+ f'{("<div style=" + chr(34) + "font-size:0.85rem;color:" + COLOR_TEXT_SECONDARY + ";margin-top:0.2rem;" + chr(34) + ">" + proj_desc + "</div>") if proj_desc else ""}'
20
+ f'<div style="font-size:0.78rem;color:{COLOR_TEXT_MUTED};margin-top:0.3rem;">Created: {created} · Modified: {modified}</div>'
21
+ f'</div>',
22
+ unsafe_allow_html=True,
23
+ )