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.
- flashstudio/__init__.py +5 -0
- flashstudio/app.py +194 -0
- flashstudio/cli.py +18 -0
- flashstudio/components/__init__.py +0 -0
- flashstudio/components/project_manager.py +490 -0
- flashstudio/components/sidebar.py +87 -0
- flashstudio/components/styles.py +346 -0
- flashstudio/components/wizard.py +45 -0
- flashstudio/components/zone_drawer/__init__.py +177 -0
- flashstudio/constants.py +245 -0
- flashstudio/core/__init__.py +3 -0
- flashstudio/core/constants.py +3 -0
- flashstudio/launcher.py +288 -0
- flashstudio/pages/__init__.py +8 -0
- flashstudio/pages/dashboard/__init__.py +3 -0
- flashstudio/pages/dashboard/_common.py +10 -0
- flashstudio/pages/dashboard/overview/__init__.py +3 -0
- flashstudio/pages/dashboard/overview/metrics.py +33 -0
- flashstudio/pages/dashboard/overview/pipeline_status.py +35 -0
- flashstudio/pages/dashboard/overview/project_banner.py +23 -0
- flashstudio/pages/dashboard/overview/quick_actions.py +28 -0
- flashstudio/pages/dashboard/overview/recent_runs.py +99 -0
- flashstudio/pages/dashboard/overview/tab.py +35 -0
- flashstudio/pages/dashboard/page.py +17 -0
- flashstudio/pages/data/__init__.py +3 -0
- flashstudio/pages/data/_common.py +107 -0
- flashstudio/pages/data/download/__init__.py +3 -0
- flashstudio/pages/data/download/tab.py +75 -0
- flashstudio/pages/data/helpers.py +338 -0
- flashstudio/pages/data/page.py +50 -0
- flashstudio/pages/data/preview/__init__.py +3 -0
- flashstudio/pages/data/preview/tab.py +139 -0
- flashstudio/pages/data/upload/__init__.py +3 -0
- flashstudio/pages/data/upload/tab.py +156 -0
- flashstudio/pages/data/verify/__init__.py +3 -0
- flashstudio/pages/data/verify/tab.py +108 -0
- flashstudio/pages/export/__init__.py +3 -0
- flashstudio/pages/export/page.py +54 -0
- flashstudio/pages/export/weights/__init__.py +4 -0
- flashstudio/pages/export/weights/exporter.py +60 -0
- flashstudio/pages/export/weights/settings.py +86 -0
- flashstudio/pages/inference/__init__.py +3 -0
- flashstudio/pages/inference/_common.py +76 -0
- flashstudio/pages/inference/data/__init__.py +3 -0
- flashstudio/pages/inference/data/tab.py +121 -0
- flashstudio/pages/inference/model/__init__.py +3 -0
- flashstudio/pages/inference/model/tab.py +62 -0
- flashstudio/pages/inference/page.py +42 -0
- flashstudio/pages/inference/run/__init__.py +3 -0
- flashstudio/pages/inference/run/detection.py +133 -0
- flashstudio/pages/inference/run/tab.py +161 -0
- flashstudio/pages/inference/run/video.py +195 -0
- flashstudio/pages/inference/solution/__init__.py +3 -0
- flashstudio/pages/inference/solution/tab.py +121 -0
- flashstudio/pages/model/__init__.py +3 -0
- flashstudio/pages/model/_common.py +3 -0
- flashstudio/pages/model/advanced/__init__.py +3 -0
- flashstudio/pages/model/advanced/tab.py +54 -0
- flashstudio/pages/model/architecture/__init__.py +3 -0
- flashstudio/pages/model/architecture/tab.py +90 -0
- flashstudio/pages/model/augment/__init__.py +3 -0
- flashstudio/pages/model/augment/tab.py +18 -0
- flashstudio/pages/model/hyperparams/__init__.py +4 -0
- flashstudio/pages/model/hyperparams/tab.py +89 -0
- flashstudio/pages/model/page.py +30 -0
- flashstudio/pages/model/utils/__init__.py +3 -0
- flashstudio/pages/model/utils/config.py +27 -0
- flashstudio/pages/training/__init__.py +3 -0
- flashstudio/pages/training/_common.py +24 -0
- flashstudio/pages/training/launch/__init__.py +3 -0
- flashstudio/pages/training/launch/controls.py +84 -0
- flashstudio/pages/training/launch/dialogs.py +247 -0
- flashstudio/pages/training/launch/preflight.py +77 -0
- flashstudio/pages/training/launch/runner.py +440 -0
- flashstudio/pages/training/launch/tab.py +134 -0
- flashstudio/pages/training/monitor/__init__.py +3 -0
- flashstudio/pages/training/monitor/checkpoints.py +96 -0
- flashstudio/pages/training/monitor/curves.py +113 -0
- flashstudio/pages/training/monitor/dashboard.py +129 -0
- flashstudio/pages/training/monitor/gt_verification.py +122 -0
- flashstudio/pages/training/monitor/log_viewer.py +43 -0
- flashstudio/pages/training/monitor/parsers.py +253 -0
- flashstudio/pages/training/monitor/run_meta.py +165 -0
- flashstudio/pages/training/monitor/tab.py +142 -0
- flashstudio/pages/training/monitor/visualizations.py +52 -0
- flashstudio/pages/training/page.py +134 -0
- flashstudio/utils/__init__.py +152 -0
- flashstudio/utils/config.py +104 -0
- flashstudio/utils/device.py +67 -0
- flashstudio/utils/filesystem.py +67 -0
- flashstudio/utils/jobs.py +95 -0
- flashstudio/utils/training_hooks.py +266 -0
- flashstudio-0.0.1.dist-info/METADATA +107 -0
- flashstudio-0.0.1.dist-info/RECORD +97 -0
- flashstudio-0.0.1.dist-info/WHEEL +5 -0
- flashstudio-0.0.1.dist-info/entry_points.txt +2 -0
- flashstudio-0.0.1.dist-info/top_level.txt +1 -0
flashstudio/constants.py
ADDED
|
@@ -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"
|
flashstudio/launcher.py
ADDED
|
@@ -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,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,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
|
+
)
|