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
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""Inference — Video processing utilities."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import time
|
|
5
|
+
import tempfile
|
|
6
|
+
import streamlit as st
|
|
7
|
+
from PIL import Image, ImageDraw
|
|
8
|
+
from flashstudio.constants import (
|
|
9
|
+
COCO_CLASSES, INFER_CONF_THRESHOLD, INFER_NMS_THRESHOLD,
|
|
10
|
+
INFER_IMG_SIZE, INFER_MAX_FRAMES, INFER_FRAME_SKIP,
|
|
11
|
+
INFER_DEFAULT_RESOLUTION, COLOR_PRIMARY,
|
|
12
|
+
)
|
|
13
|
+
from flashstudio.pages.inference._common import (
|
|
14
|
+
_get_class_names, _HAS_PREDICTOR, _SOLUTIONS_AVAILABLE,
|
|
15
|
+
)
|
|
16
|
+
from flashstudio.pages.inference.run.detection import _draw_boxes_cv2
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
from flashdet import Predictor, FlashTracker
|
|
20
|
+
except ImportError:
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _run_video():
|
|
25
|
+
video = st.session_state.get("infer_video")
|
|
26
|
+
solution = st.session_state.get("selected_solution", "None (Detection Only)")
|
|
27
|
+
weights = st.session_state.get("infer_weights_path", "") or st.session_state.get("infer_weights_file")
|
|
28
|
+
if weights:
|
|
29
|
+
try:
|
|
30
|
+
_run_flashdet_video(video, solution)
|
|
31
|
+
return
|
|
32
|
+
except Exception as e:
|
|
33
|
+
st.warning(f"FlashDet error: {e}. Demo mode.")
|
|
34
|
+
_run_demo_video(solution)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _run_demo_video(solution_name):
|
|
38
|
+
t0 = time.perf_counter()
|
|
39
|
+
progress = st.progress(0)
|
|
40
|
+
frames = []
|
|
41
|
+
for i in range(4):
|
|
42
|
+
time.sleep(0.2)
|
|
43
|
+
progress.progress((i + 1) / 4)
|
|
44
|
+
img = Image.new("RGB", INFER_DEFAULT_RESOLUTION, (248, 248, 252))
|
|
45
|
+
draw = ImageDraw.Draw(img)
|
|
46
|
+
draw.rectangle([80 + i * 40, 80 + i * 30, 250 + i * 40, 230 + i * 30], outline=COLOR_PRIMARY, width=3)
|
|
47
|
+
draw.text((90 + i * 40, 65 + i * 30), f"car 0.{85 + i}", fill=COLOR_PRIMARY)
|
|
48
|
+
frames.append(img)
|
|
49
|
+
progress.empty()
|
|
50
|
+
elapsed = time.perf_counter() - t0
|
|
51
|
+
|
|
52
|
+
sol_output = {}
|
|
53
|
+
if "Counter" in solution_name:
|
|
54
|
+
sol_output = {"in_count": 12, "out_count": 8}
|
|
55
|
+
elif "Region" in solution_name:
|
|
56
|
+
sol_output = {"region_counts": {"Zone A": 5, "Zone B": 3}}
|
|
57
|
+
elif "Speed" in solution_name:
|
|
58
|
+
sol_output = {"speeds": [[1, 45.2], [2, 38.7], [3, 52.1]]}
|
|
59
|
+
|
|
60
|
+
st.session_state["video_results"] = {
|
|
61
|
+
"frames": 300, "detections": 1247, "processing_fps": 300 / max(elapsed, 0.001),
|
|
62
|
+
"elapsed_time": elapsed, "solution_output": sol_output, "frames_preview": frames,
|
|
63
|
+
"output_path": _get_output_path("output.mp4") if st.session_state.get("infer_save_video") else None,
|
|
64
|
+
}
|
|
65
|
+
st.rerun()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _get_output_path(filename):
|
|
69
|
+
"""Resolve output path: prefer save_dir, fall back to tempdir."""
|
|
70
|
+
sd = st.session_state.get("save_dir", "")
|
|
71
|
+
if sd and os.path.isdir(sd):
|
|
72
|
+
return os.path.join(sd, filename)
|
|
73
|
+
return os.path.join(tempfile.gettempdir(), filename)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _run_flashdet_video(video_file, solution_name):
|
|
77
|
+
if not _HAS_PREDICTOR:
|
|
78
|
+
raise ImportError("Predictor unavailable")
|
|
79
|
+
import cv2
|
|
80
|
+
tmp = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False)
|
|
81
|
+
tmp.write(video_file.read()); tmp.flush(); video_file.seek(0)
|
|
82
|
+
cap = cv2.VideoCapture(tmp.name)
|
|
83
|
+
total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
|
84
|
+
fps = cap.get(cv2.CAP_PROP_FPS) or 30
|
|
85
|
+
|
|
86
|
+
weights = st.session_state.get("infer_weights_path", "")
|
|
87
|
+
if not weights:
|
|
88
|
+
up = st.session_state.get("infer_weights_file")
|
|
89
|
+
if up:
|
|
90
|
+
wtmp = tempfile.NamedTemporaryFile(suffix=".pth", delete=False)
|
|
91
|
+
wtmp.write(up.read()); wtmp.flush(); up.seek(0)
|
|
92
|
+
weights = wtmp.name
|
|
93
|
+
|
|
94
|
+
custom_cls = _get_class_names()
|
|
95
|
+
predictor = Predictor(model_path=weights, device=st.session_state.get("infer_device", "cpu").split(" ")[0],
|
|
96
|
+
conf_thresh=st.session_state.get("infer_conf", INFER_CONF_THRESHOLD),
|
|
97
|
+
nms_thresh=st.session_state.get("infer_nms", INFER_NMS_THRESHOLD),
|
|
98
|
+
input_size=st.session_state.get("infer_img_size", INFER_IMG_SIZE),
|
|
99
|
+
class_names=custom_cls if custom_cls != COCO_CLASSES else None)
|
|
100
|
+
tracker = FlashTracker()
|
|
101
|
+
solution = _create_solution(solution_name, predictor, tracker, fps)
|
|
102
|
+
|
|
103
|
+
max_frames = st.session_state.get("infer_max_frames", INFER_MAX_FRAMES) or total
|
|
104
|
+
skip = st.session_state.get("infer_frame_skip", INFER_FRAME_SKIP)
|
|
105
|
+
frames_preview, processed, total_dets = [], 0, 0
|
|
106
|
+
t0 = time.perf_counter()
|
|
107
|
+
progress = st.progress(0)
|
|
108
|
+
|
|
109
|
+
for idx in range(min(total, max_frames)):
|
|
110
|
+
ret, frame = cap.read()
|
|
111
|
+
if not ret:
|
|
112
|
+
break
|
|
113
|
+
if idx % skip != 0:
|
|
114
|
+
continue
|
|
115
|
+
if solution:
|
|
116
|
+
annotated, results = solution.process_frame(frame)
|
|
117
|
+
else:
|
|
118
|
+
results = predictor(frame)
|
|
119
|
+
annotated = _draw_boxes_cv2(frame, results)
|
|
120
|
+
if isinstance(results, (list, tuple)):
|
|
121
|
+
total_dets += len(results)
|
|
122
|
+
processed += 1
|
|
123
|
+
if idx % max(total // 4, 1) == 0:
|
|
124
|
+
frames_preview.append(Image.fromarray(cv2.cvtColor(annotated or frame, cv2.COLOR_BGR2RGB)))
|
|
125
|
+
progress.progress(idx / min(total, max_frames))
|
|
126
|
+
|
|
127
|
+
cap.release(); progress.empty()
|
|
128
|
+
try:
|
|
129
|
+
os.unlink(tmp.name)
|
|
130
|
+
except OSError:
|
|
131
|
+
pass
|
|
132
|
+
elapsed = time.perf_counter() - t0
|
|
133
|
+
sol_output = {}
|
|
134
|
+
if solution:
|
|
135
|
+
try:
|
|
136
|
+
sol_output = solution.get_results()
|
|
137
|
+
if not isinstance(sol_output, dict):
|
|
138
|
+
sol_output = {}
|
|
139
|
+
except Exception:
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
st.session_state["video_results"] = {
|
|
143
|
+
"frames": processed, "detections": total_dets, "processing_fps": processed / max(elapsed, 0.001),
|
|
144
|
+
"elapsed_time": elapsed, "solution_output": sol_output, "frames_preview": frames_preview,
|
|
145
|
+
"output_path": _get_output_path("output.mp4") if st.session_state.get("infer_save_video") else None,
|
|
146
|
+
}
|
|
147
|
+
st.rerun()
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _create_solution(solution_name, predictor, tracker, fps):
|
|
151
|
+
line_pts = st.session_state.get("zone_line_points")
|
|
152
|
+
polygons = st.session_state.get("zone_polygons")
|
|
153
|
+
class_filter_names = st.session_state.get("infer_class_filter") or None
|
|
154
|
+
classes = None
|
|
155
|
+
if class_filter_names:
|
|
156
|
+
all_cls = _get_class_names()
|
|
157
|
+
classes = [all_cls.index(n) for n in class_filter_names if n in all_cls]
|
|
158
|
+
if not classes:
|
|
159
|
+
classes = None
|
|
160
|
+
|
|
161
|
+
if solution_name == "None (Detection Only)":
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
first_poly = polygons[0] if polygons and len(polygons) > 0 else []
|
|
165
|
+
sol_map = {
|
|
166
|
+
"Object Counter": ("ObjectCounter", dict(line_points=line_pts, classes=classes)),
|
|
167
|
+
"Region Counter": ("RegionCounter", dict(regions={"zone_0": first_poly} if first_poly else {}, classes=classes)),
|
|
168
|
+
"Speed Estimator": ("SpeedEstimator", dict(fps=fps, classes=classes)),
|
|
169
|
+
"Heatmap": ("Heatmap", dict(classes=classes)),
|
|
170
|
+
"Security Alarm": ("SecurityAlarm", dict(restricted_zones=polygons or [], classes=classes)),
|
|
171
|
+
"Trajectory": ("TrajectoryVisualizer", dict(classes=classes)),
|
|
172
|
+
"Blurrer": ("ObjectBlurrer", dict(classes=classes)),
|
|
173
|
+
"Queue": ("QueueManager", dict(queue_regions=polygons or [], classes=classes, fps=fps)),
|
|
174
|
+
"Crowd": ("CrowdDensity", dict(classes=classes)),
|
|
175
|
+
"Parking": ("ParkingManager", dict(parking_spots=polygons or [], classes=classes)),
|
|
176
|
+
"Traffic": ("TrafficFlow", dict(fps=fps, classes=classes)),
|
|
177
|
+
"Dwell": ("DwellTimeAnalyzer", dict(zones={"zone_0": first_poly} if first_poly else {}, fps=fps, classes=classes)),
|
|
178
|
+
"Distance": ("DistanceCalculator", dict(classes=classes)),
|
|
179
|
+
"Workout": ("WorkoutMonitor", dict(classes=classes)),
|
|
180
|
+
"Cropper": ("ObjectCropper", dict(classes=classes)),
|
|
181
|
+
"Analytics": ("AnalyticsDashboard", dict(classes=classes)),
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
for keyword, (cls_name, kwargs) in sol_map.items():
|
|
185
|
+
if keyword in solution_name:
|
|
186
|
+
cls = _SOLUTIONS_AVAILABLE.get(cls_name)
|
|
187
|
+
if not cls:
|
|
188
|
+
return None
|
|
189
|
+
try:
|
|
190
|
+
if cls_name not in ("Heatmap", "ObjectBlurrer", "CrowdDensity"):
|
|
191
|
+
return cls(predictor=predictor, tracker=tracker, **kwargs)
|
|
192
|
+
return cls(predictor=predictor, **kwargs)
|
|
193
|
+
except TypeError:
|
|
194
|
+
return cls(predictor=predictor, **kwargs)
|
|
195
|
+
return None
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Inference — Solution tab."""
|
|
2
|
+
|
|
3
|
+
import streamlit as st
|
|
4
|
+
from flashstudio.constants import (
|
|
5
|
+
INFER_DISPLAY_WIDTH, INFER_DEFAULT_RESOLUTION,
|
|
6
|
+
ZONE_MAX_X, ZONE_MAX_Y, ZONE_DEFAULT_LINE, ZONE_DEFAULT_POLYGON,
|
|
7
|
+
)
|
|
8
|
+
from flashstudio.pages.inference._common import (
|
|
9
|
+
SOLUTIONS, _SOLUTIONS_AVAILABLE, _solution_classes, _HAS_ZONE_DRAWER,
|
|
10
|
+
)
|
|
11
|
+
from flashstudio.pages.inference.data.tab import _get_first_frame
|
|
12
|
+
|
|
13
|
+
if _HAS_ZONE_DRAWER:
|
|
14
|
+
from flashstudio.components.zone_drawer import zone_drawer
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _tab_solution():
|
|
18
|
+
sc1, sc2 = st.columns([2, 3])
|
|
19
|
+
with sc1:
|
|
20
|
+
selected = st.selectbox("Solution", list(SOLUTIONS.keys()), key="selected_solution",
|
|
21
|
+
format_func=lambda x: x)
|
|
22
|
+
sol = SOLUTIONS[selected]
|
|
23
|
+
st.caption(sol["desc"])
|
|
24
|
+
avail = len(_SOLUTIONS_AVAILABLE)
|
|
25
|
+
total = len(_solution_classes)
|
|
26
|
+
st.caption(f"{avail}/{total} solutions available")
|
|
27
|
+
|
|
28
|
+
with sc2:
|
|
29
|
+
if sol["needs_zone"]:
|
|
30
|
+
st.info("Zone required — draw below using Polygon, Rectangle, or Line.")
|
|
31
|
+
else:
|
|
32
|
+
st.caption("Zone optional — draw one to limit detection area.")
|
|
33
|
+
has_line = bool(st.session_state.get("zone_line_points"))
|
|
34
|
+
has_poly = bool(st.session_state.get("zone_polygons"))
|
|
35
|
+
if has_line or has_poly:
|
|
36
|
+
zone_type = "Line" if has_line else "Polygon"
|
|
37
|
+
st.success(f"Zone set: {zone_type}")
|
|
38
|
+
elif sol["needs_zone"]:
|
|
39
|
+
st.warning("No zone defined yet")
|
|
40
|
+
|
|
41
|
+
_zone_draw_ui(sol)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _zone_draw_ui(sol):
|
|
45
|
+
default_type = sol.get("zone_type", "polygon")
|
|
46
|
+
mode_options = ["polygon", "rect", "line"]
|
|
47
|
+
default_idx = mode_options.index(default_type) if default_type in mode_options else 0
|
|
48
|
+
draw_mode = st.radio("Draw Mode", mode_options, index=default_idx, horizontal=True, key="zone_draw_mode",
|
|
49
|
+
format_func=lambda x: {"polygon": "Polygon", "rect": "Rectangle", "line": "Line"}[x])
|
|
50
|
+
|
|
51
|
+
frame_img = _get_first_frame()
|
|
52
|
+
|
|
53
|
+
existing_points = st.session_state.get("zone_draw_points", [])
|
|
54
|
+
existing_closed = st.session_state.get("zone_closed", False)
|
|
55
|
+
|
|
56
|
+
if _HAS_ZONE_DRAWER:
|
|
57
|
+
result = zone_drawer(image=frame_img, mode=draw_mode, points=existing_points,
|
|
58
|
+
closed=existing_closed, display_width=INFER_DISPLAY_WIDTH)
|
|
59
|
+
if result and result.get("points"):
|
|
60
|
+
pts = result["points"]
|
|
61
|
+
img_w = frame_img.size[0] if frame_img else INFER_DEFAULT_RESOLUTION[0]
|
|
62
|
+
img_h = frame_img.size[1] if frame_img else INFER_DEFAULT_RESOLUTION[1]
|
|
63
|
+
dw = result.get("displayWidth", INFER_DISPLAY_WIDTH)
|
|
64
|
+
dh = result.get("displayHeight", INFER_DEFAULT_RESOLUTION[1])
|
|
65
|
+
sx = img_w / dw if dw else 1
|
|
66
|
+
sy = img_h / dh if dh else 1
|
|
67
|
+
_store_zone_coords(pts, draw_mode, sx, sy)
|
|
68
|
+
else:
|
|
69
|
+
st.warning("Zone drawing component not available.")
|
|
70
|
+
|
|
71
|
+
with st.expander("Set coordinates manually", expanded=not _HAS_ZONE_DRAWER):
|
|
72
|
+
_manual_zone_input(draw_mode)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _manual_zone_input(draw_mode):
|
|
76
|
+
if draw_mode == "line":
|
|
77
|
+
c1, c2 = st.columns(2)
|
|
78
|
+
with c1:
|
|
79
|
+
x1 = st.number_input("X1", 0, ZONE_MAX_X, ZONE_DEFAULT_LINE[0][0], key="manual_line_x1")
|
|
80
|
+
y1 = st.number_input("Y1", 0, ZONE_MAX_Y, ZONE_DEFAULT_LINE[0][1], key="manual_line_y1")
|
|
81
|
+
with c2:
|
|
82
|
+
x2 = st.number_input("X2", 0, ZONE_MAX_X, ZONE_DEFAULT_LINE[1][0], key="manual_line_x2")
|
|
83
|
+
y2 = st.number_input("Y2", 0, ZONE_MAX_Y, ZONE_DEFAULT_LINE[1][1], key="manual_line_y2")
|
|
84
|
+
if st.button("Set Line", key="set_manual_line"):
|
|
85
|
+
st.session_state["zone_line_points"] = [(x1, y1), (x2, y2)]
|
|
86
|
+
st.success(f"({x1},{y1}) → ({x2},{y2})")
|
|
87
|
+
else:
|
|
88
|
+
pts = st.text_area("Points (x,y per line)", ZONE_DEFAULT_POLYGON, key="manual_polygon_text")
|
|
89
|
+
btn_label = "Set Rectangle" if draw_mode == "rect" else "Set Polygon"
|
|
90
|
+
min_pts = 2 if draw_mode == "rect" else 3
|
|
91
|
+
if st.button(btn_label, key="set_manual_polygon"):
|
|
92
|
+
parsed = []
|
|
93
|
+
for line in pts.strip().split("\n"):
|
|
94
|
+
p = line.strip().split(",")
|
|
95
|
+
if len(p) == 2:
|
|
96
|
+
try:
|
|
97
|
+
parsed.append((int(p[0].strip()), int(p[1].strip())))
|
|
98
|
+
except ValueError:
|
|
99
|
+
continue
|
|
100
|
+
if len(parsed) >= min_pts:
|
|
101
|
+
if draw_mode == "rect" and len(parsed) >= 2:
|
|
102
|
+
x1, y1 = parsed[0]; x2, y2 = parsed[1]
|
|
103
|
+
st.session_state["zone_polygons"] = [[(x1, y1), (x2, y1), (x2, y2), (x1, y2)]]
|
|
104
|
+
st.success(f"Rectangle: ({x1},{y1}) → ({x2},{y2})")
|
|
105
|
+
else:
|
|
106
|
+
st.session_state["zone_polygons"] = [parsed]
|
|
107
|
+
st.success(f"{len(parsed)} vertices")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _store_zone_coords(display_points, draw_mode, scale_x, scale_y):
|
|
111
|
+
orig = [(int(x * scale_x), int(y * scale_y)) for x, y in display_points]
|
|
112
|
+
if draw_mode == "line" and len(orig) >= 2:
|
|
113
|
+
st.session_state["zone_line_points"] = [orig[0], orig[1]]
|
|
114
|
+
st.success(f"Line: {orig[0]} → {orig[1]}")
|
|
115
|
+
elif draw_mode == "rect" and len(orig) >= 2:
|
|
116
|
+
x1, y1 = orig[0]; x2, y2 = orig[1]
|
|
117
|
+
st.session_state["zone_polygons"] = [[(x1, y1), (x2, y1), (x2, y2), (x1, y2)]]
|
|
118
|
+
st.success(f"Rect: ({x1},{y1})→({x2},{y2})")
|
|
119
|
+
elif draw_mode == "polygon" and len(orig) >= 3:
|
|
120
|
+
st.session_state["zone_polygons"] = [orig]
|
|
121
|
+
st.success(f"Polygon: {len(orig)} pts") if st.session_state.get("zone_closed") else st.info(f"{len(orig)} pts, click more or close")
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""FlashStudio — Advanced options tab."""
|
|
2
|
+
|
|
3
|
+
import streamlit as st
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _tab_adv():
|
|
7
|
+
col1, col2 = st.columns(2)
|
|
8
|
+
with col1:
|
|
9
|
+
with st.container(border=True):
|
|
10
|
+
st.markdown("#### Memory")
|
|
11
|
+
ac1, ac2 = st.columns(2)
|
|
12
|
+
with ac1:
|
|
13
|
+
st.checkbox("Act. Checkpoint", False, key="activation_checkpointing")
|
|
14
|
+
st.checkbox("Act. Offload", False, key="activation_offloading")
|
|
15
|
+
with ac2:
|
|
16
|
+
st.checkbox("Optim in Bwd", False, key="optimizer_in_bwd")
|
|
17
|
+
st.checkbox("torch.compile", False, key="compile_model")
|
|
18
|
+
st.checkbox("8-bit Optimizer", False, key="use_8bit_optimizer")
|
|
19
|
+
st.checkbox("Chunked Loss", False, key="chunked_loss")
|
|
20
|
+
if st.session_state.get("chunked_loss"):
|
|
21
|
+
st.number_input("Chunk Size", 256, 4096, 1024, key="chunk_size")
|
|
22
|
+
|
|
23
|
+
with col2:
|
|
24
|
+
with st.container(border=True):
|
|
25
|
+
st.markdown("#### Distributed & IO")
|
|
26
|
+
st.checkbox("Multi-GPU (DDP)", False, key="ddp")
|
|
27
|
+
|
|
28
|
+
nc = st.session_state.get("num_classes", 0)
|
|
29
|
+
cls_names = st.session_state.get("class_names", "")
|
|
30
|
+
if isinstance(cls_names, list):
|
|
31
|
+
cls_names = "\n".join(cls_names)
|
|
32
|
+
if nc and cls_names.strip():
|
|
33
|
+
names_list = [c.strip() for c in cls_names.strip().split("\n") if c.strip()]
|
|
34
|
+
preview = ", ".join(names_list[:5])
|
|
35
|
+
if len(names_list) > 5:
|
|
36
|
+
preview += f" ... (+{len(names_list) - 5})"
|
|
37
|
+
st.caption(f"Classes ({nc}): {preview}")
|
|
38
|
+
else:
|
|
39
|
+
st.caption("Classes: not set — go to Data → Upload")
|
|
40
|
+
|
|
41
|
+
st.text_input("Class File (.txt)", placeholder="auto-generated from dataset classes",
|
|
42
|
+
key="class_file", help="Leave empty to auto-generate from dataset classes")
|
|
43
|
+
|
|
44
|
+
from flashstudio.utils import DEFAULTS
|
|
45
|
+
if "save_dir" not in st.session_state or not st.session_state.get("save_dir", "").strip():
|
|
46
|
+
st.session_state["save_dir"] = DEFAULTS["save_dir"]
|
|
47
|
+
st.text_input("Save Dir", key="save_dir", placeholder=DEFAULTS["save_dir"])
|
|
48
|
+
rc1, rc2 = st.columns(2)
|
|
49
|
+
with rc1:
|
|
50
|
+
st.checkbox("Save Best Only", True, key="save_best")
|
|
51
|
+
with rc2:
|
|
52
|
+
st.checkbox("Resume", False, key="resume_training")
|
|
53
|
+
if st.session_state.get("resume_training"):
|
|
54
|
+
st.text_input("Checkpoint", placeholder="checkpoint_last.pth", key="resume_path")
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""FlashStudio — Model architecture tab."""
|
|
2
|
+
|
|
3
|
+
import streamlit as st
|
|
4
|
+
from flashstudio.constants import (
|
|
5
|
+
FLASHDET_MODELS, LORA_VARIANTS, ARCH_FAMILIES,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _tab_arch():
|
|
10
|
+
arch = st.radio("Family", ARCH_FAMILIES, key="arch_family", horizontal=True)
|
|
11
|
+
|
|
12
|
+
# Head config — num_classes from dataset
|
|
13
|
+
nc = st.session_state.get("num_classes", 0)
|
|
14
|
+
cls_names = st.session_state.get("class_names", "")
|
|
15
|
+
if isinstance(cls_names, list):
|
|
16
|
+
cls_names = "\n".join(cls_names)
|
|
17
|
+
if not nc and cls_names.strip():
|
|
18
|
+
nc = len([c for c in cls_names.strip().split("\n") if c.strip()])
|
|
19
|
+
st.session_state["num_classes"] = nc
|
|
20
|
+
|
|
21
|
+
if arch == "FlashDet (recommended)":
|
|
22
|
+
col_sel, col_info = st.columns([2, 3])
|
|
23
|
+
with col_sel:
|
|
24
|
+
model = st.radio("Size", list(FLASHDET_MODELS.keys()), index=0, key="model_arch")
|
|
25
|
+
info = FLASHDET_MODELS[model]
|
|
26
|
+
if model == "FlashDet-Pico":
|
|
27
|
+
st.selectbox("Backbone", ["LiteBackbone", "PicoBackbone"], key="pico_backbone")
|
|
28
|
+
else:
|
|
29
|
+
st.caption(f"Backbone: {info['backbone']}")
|
|
30
|
+
st.caption(f"{info['neck']} → {info['head']} · CIoU+BCE+L1 · STAL · ProgLoss")
|
|
31
|
+
|
|
32
|
+
with col_info:
|
|
33
|
+
m1, m2, m3, m4 = st.columns(4)
|
|
34
|
+
with m1:
|
|
35
|
+
st.metric("Params", info["params"])
|
|
36
|
+
with m2:
|
|
37
|
+
st.metric("Speed", info["speed"])
|
|
38
|
+
with m3:
|
|
39
|
+
st.metric("Best For", info["for"])
|
|
40
|
+
with m4:
|
|
41
|
+
st.metric("Head Classes", nc if nc else "—")
|
|
42
|
+
if nc:
|
|
43
|
+
st.caption(f"Model head output → **{nc}** classes (auto-set from dataset)")
|
|
44
|
+
else:
|
|
45
|
+
st.info("No classes detected yet. Go to **Data** → **Upload** to load a dataset and classes will be set automatically.")
|
|
46
|
+
_pretrain_finetune()
|
|
47
|
+
else:
|
|
48
|
+
name = arch.replace(" (recommended)", "")
|
|
49
|
+
col_sel, col_info = st.columns([2, 3])
|
|
50
|
+
with col_sel:
|
|
51
|
+
st.slider("Width ×", 0.25, 1.5, 0.5, 0.25, key="yolo_width_mult")
|
|
52
|
+
st.slider("Depth ×", 0.33, 1.5, 0.33, 0.33, key="yolo_depth_mult")
|
|
53
|
+
if name == "YOLOv9":
|
|
54
|
+
st.checkbox("PGI", True, key="yolo_use_pgi")
|
|
55
|
+
elif name == "YOLOv10":
|
|
56
|
+
st.checkbox("PSA", True, key="yolo_use_psa")
|
|
57
|
+
elif name == "YOLOv11":
|
|
58
|
+
st.checkbox("C2PSA", True, key="yolo_use_c2psa")
|
|
59
|
+
with col_info:
|
|
60
|
+
st.caption(f"{name} — fixed backbone/neck/head. Control via width/depth multipliers.")
|
|
61
|
+
if nc:
|
|
62
|
+
st.metric("Head Classes", nc)
|
|
63
|
+
_pretrain_finetune()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _pretrain_finetune():
|
|
67
|
+
with st.container(border=True):
|
|
68
|
+
c1, c2 = st.columns(2)
|
|
69
|
+
with c1:
|
|
70
|
+
st.markdown("#### Pretrained")
|
|
71
|
+
pt = st.radio("Init", ["COCO pretrained", "ImageNet backbone", "Random", "Custom"],
|
|
72
|
+
key="pretrain_option", label_visibility="collapsed")
|
|
73
|
+
if pt == "Custom":
|
|
74
|
+
st.text_input("Path", placeholder="/path/model.pth", key="custom_weights")
|
|
75
|
+
with c2:
|
|
76
|
+
st.markdown("#### Fine-tune")
|
|
77
|
+
ft = st.radio("Mode", ["Full fine-tune", "Freeze backbone", "Freeze BB+neck", "LoRA"],
|
|
78
|
+
key="finetune_strategy", label_visibility="collapsed")
|
|
79
|
+
if ft == "LoRA":
|
|
80
|
+
lc1, lc2 = st.columns(2)
|
|
81
|
+
with lc1:
|
|
82
|
+
st.selectbox("Variant", LORA_VARIANTS, key="lora_variant")
|
|
83
|
+
st.slider("Rank", 4, 64, 16, key="lora_rank")
|
|
84
|
+
with lc2:
|
|
85
|
+
st.slider("Alpha", 8, 128, 32, key="lora_alpha")
|
|
86
|
+
st.slider("Dropout", 0.0, 0.5, 0.0, key="lora_dropout")
|
|
87
|
+
st.multiselect("Targets", ["backbone", "neck", "head"], default=["backbone", "neck"], key="lora_targets")
|
|
88
|
+
st.checkbox("QLoRA", False, key="qlora")
|
|
89
|
+
if st.session_state.get("qlora"):
|
|
90
|
+
st.selectbox("Quant", ["int8", "nf4"], key="qlora_dtype")
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""FlashStudio — Augmentation tab."""
|
|
2
|
+
|
|
3
|
+
import streamlit as st
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _tab_aug():
|
|
7
|
+
col1, col2 = st.columns(2)
|
|
8
|
+
with col1:
|
|
9
|
+
with st.container(border=True):
|
|
10
|
+
st.markdown("#### Multi-Image")
|
|
11
|
+
st.checkbox("Mosaic (4-image)", True, key="aug_mosaic")
|
|
12
|
+
st.checkbox("MixUp", False, key="aug_mixup")
|
|
13
|
+
st.checkbox("CopyPaste", False, key="aug_copypaste")
|
|
14
|
+
with col2:
|
|
15
|
+
with st.container(border=True):
|
|
16
|
+
st.markdown("#### Built-in (always on)")
|
|
17
|
+
for a in ["Scale jitter 0.5–1.5×", "Horizontal flip", "Color jitter", "Letterbox resize", "ImageNet norm"]:
|
|
18
|
+
st.markdown(f'<span style="font-size:0.88rem;color:#4B5563;">• {a}</span>', unsafe_allow_html=True)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""FlashStudio — Hyperparameters tab, summary bar."""
|
|
2
|
+
|
|
3
|
+
import yaml
|
|
4
|
+
import streamlit as st
|
|
5
|
+
from flashstudio.constants import (
|
|
6
|
+
OPTIMIZERS,
|
|
7
|
+
TRAIN_EPOCHS, TRAIN_BATCH_SIZE, TRAIN_LR, TRAIN_IMG_SIZE,
|
|
8
|
+
TRAIN_WEIGHT_DECAY, TRAIN_WARMUP_EPOCHS, TRAIN_PATIENCE,
|
|
9
|
+
TRAIN_NUM_WORKERS, TRAIN_GRAD_ACCUM, TRAIN_VAL_INTERVAL,
|
|
10
|
+
TRAIN_LR_FINAL_RATIO, BATCH_SIZE_OPTIONS, IMG_SIZE_OPTIONS,
|
|
11
|
+
DEFAULT_MODEL_ARCH,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _tab_hyper():
|
|
16
|
+
col1, col2 = st.columns(2)
|
|
17
|
+
with col1:
|
|
18
|
+
with st.container(border=True):
|
|
19
|
+
st.markdown("#### Core")
|
|
20
|
+
hc1, hc2 = st.columns(2)
|
|
21
|
+
with hc1:
|
|
22
|
+
st.number_input("Epochs", min_value=1, max_value=10000, value=TRAIN_EPOCHS, step=10, key="epochs")
|
|
23
|
+
st.select_slider("Batch", BATCH_SIZE_OPTIONS, value=TRAIN_BATCH_SIZE, key="batch_size")
|
|
24
|
+
st.select_slider("Img Size", IMG_SIZE_OPTIONS, value=TRAIN_IMG_SIZE, key="img_size")
|
|
25
|
+
with hc2:
|
|
26
|
+
st.slider("LR", 1e-5, 1e-1, TRAIN_LR, format="%.5f", key="lr")
|
|
27
|
+
st.slider("Weight Decay", 0.0, 0.1, TRAIN_WEIGHT_DECAY, format="%.3f", key="weight_decay")
|
|
28
|
+
st.selectbox("Optimizer", OPTIMIZERS, key="optimizer")
|
|
29
|
+
|
|
30
|
+
with col2:
|
|
31
|
+
with st.container(border=True):
|
|
32
|
+
st.markdown("#### Schedule & Options")
|
|
33
|
+
sc1, sc2 = st.columns(2)
|
|
34
|
+
with sc1:
|
|
35
|
+
st.slider("Warmup Ep", 0, 20, TRAIN_WARMUP_EPOCHS, key="warmup_epochs")
|
|
36
|
+
st.slider("LR Final ×", 0.01, 0.5, TRAIN_LR_FINAL_RATIO, format="%.2f", key="lr_final_ratio")
|
|
37
|
+
st.slider("Workers", 0, 16, TRAIN_NUM_WORKERS, key="num_workers")
|
|
38
|
+
with sc2:
|
|
39
|
+
st.slider("Patience", 5, 100, TRAIN_PATIENCE, key="patience")
|
|
40
|
+
st.number_input("Grad Accum", 1, 16, TRAIN_GRAD_ACCUM, key="grad_accum")
|
|
41
|
+
st.slider("Val Interval", 1, 20, TRAIN_VAL_INTERVAL, key="val_interval")
|
|
42
|
+
oc1, oc2, oc3 = st.columns(3)
|
|
43
|
+
with oc1:
|
|
44
|
+
st.checkbox("AMP FP16", True, key="amp")
|
|
45
|
+
with oc2:
|
|
46
|
+
st.checkbox("Grad Clip", True, key="grad_clip")
|
|
47
|
+
with oc3:
|
|
48
|
+
st.checkbox("Multi-Scale", False, key="multiscale")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _summary_bar():
|
|
52
|
+
model = st.session_state.get("model_arch", DEFAULT_MODEL_ARCH)
|
|
53
|
+
nc = st.session_state.get("num_classes", 0)
|
|
54
|
+
cls_names = st.session_state.get("class_names", "")
|
|
55
|
+
if isinstance(cls_names, list):
|
|
56
|
+
cls_names = "\n".join(cls_names)
|
|
57
|
+
nc_display = nc if nc else len([c for c in cls_names.strip().split("\n") if c.strip()]) if cls_names.strip() else "—"
|
|
58
|
+
st.markdown(
|
|
59
|
+
f'<div class="info-bar">'
|
|
60
|
+
f'Model: <b>{model.replace("FlashDet-", "")}</b> · '
|
|
61
|
+
f'Classes: <b>{nc_display}</b> · '
|
|
62
|
+
f'Ep: <b>{st.session_state.get("epochs", TRAIN_EPOCHS)}</b> · '
|
|
63
|
+
f'BS: <b>{st.session_state.get("batch_size", TRAIN_BATCH_SIZE)}</b> · '
|
|
64
|
+
f'LR: <b>{st.session_state.get("lr", TRAIN_LR):.1e}</b> · '
|
|
65
|
+
f'Img: <b>{st.session_state.get("img_size", TRAIN_IMG_SIZE)}</b> · '
|
|
66
|
+
f'AMP: <b>{"On" if st.session_state.get("amp", True) else "Off"}</b>'
|
|
67
|
+
f'</div>',
|
|
68
|
+
unsafe_allow_html=True,
|
|
69
|
+
)
|
|
70
|
+
with st.expander("Save / Load Config", expanded=False):
|
|
71
|
+
c1, c2 = st.columns(2)
|
|
72
|
+
with c1:
|
|
73
|
+
from flashstudio.utils.config import build_training_config
|
|
74
|
+
full_cfg = build_training_config()
|
|
75
|
+
st.download_button("Download YAML", yaml.dump(full_cfg, default_flow_style=False, sort_keys=False),
|
|
76
|
+
file_name="config.yaml", mime="text/yaml", use_container_width=True)
|
|
77
|
+
with c2:
|
|
78
|
+
up = st.file_uploader("Load", type=["yaml", "yml"], key="cfg_upload", label_visibility="collapsed")
|
|
79
|
+
if up:
|
|
80
|
+
try:
|
|
81
|
+
loaded = yaml.safe_load(up.read().decode("utf-8"))
|
|
82
|
+
if st.button("Apply", key="apply_cfg", use_container_width=True):
|
|
83
|
+
from flashstudio.utils import flash
|
|
84
|
+
from flashstudio.pages.model.utils.config import _apply_cfg
|
|
85
|
+
_apply_cfg(loaded)
|
|
86
|
+
flash("Config applied", "success")
|
|
87
|
+
st.rerun()
|
|
88
|
+
except Exception as e:
|
|
89
|
+
st.error(str(e)[:40])
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""FlashStudio — Model Config (ultra-compact, no-scroll)."""
|
|
2
|
+
|
|
3
|
+
import streamlit as st
|
|
4
|
+
|
|
5
|
+
from flashstudio.pages.model.architecture.tab import _tab_arch
|
|
6
|
+
from flashstudio.pages.model.hyperparams.tab import _tab_hyper, _summary_bar
|
|
7
|
+
from flashstudio.pages.model.augment.tab import _tab_aug
|
|
8
|
+
from flashstudio.pages.model.advanced.tab import _tab_adv
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def render_model_page():
|
|
12
|
+
from flashstudio.components.styles import render_page_header
|
|
13
|
+
from flashstudio.utils import show_flashes, update_config_mirror
|
|
14
|
+
render_page_header("", "Model")
|
|
15
|
+
show_flashes()
|
|
16
|
+
|
|
17
|
+
tab_arch, tab_hyper, tab_aug, tab_adv = st.tabs(["Architecture", "Hyperparams", "Augment", "Advanced"])
|
|
18
|
+
|
|
19
|
+
with tab_arch:
|
|
20
|
+
_tab_arch()
|
|
21
|
+
with tab_hyper:
|
|
22
|
+
_tab_hyper()
|
|
23
|
+
with tab_aug:
|
|
24
|
+
_tab_aug()
|
|
25
|
+
with tab_adv:
|
|
26
|
+
_tab_adv()
|
|
27
|
+
|
|
28
|
+
_summary_bar()
|
|
29
|
+
|
|
30
|
+
update_config_mirror()
|