-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathgui_export.py
More file actions
162 lines (141 loc) · 5.75 KB
/
Copy pathgui_export.py
File metadata and controls
162 lines (141 loc) · 5.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
"""RaceVideoToLog export thread — runs Pipeline in a native thread.
Separated from gui.py to avoid coupling _ExportThread to RaceVideoToLogApp.
Takes all parameters explicitly instead of reaching through self.app.
"""
from __future__ import annotations
import threading
from pathlib import Path
from PySide6.QtCore import QThread, Signal
from PySide6.QtWidgets import QWidget
def _to_int_or_none(s: str) -> int | None:
"""空串/非法 → None(段管线 frame_start/end 需要 int|None)。"""
s = (s or "").strip()
if not s:
return None
try:
return int(float(s))
except ValueError:
return None
class _CancelExport(Exception):
"""内部异常:用户取消了导出任务。"""
pass
class ExportThread(QThread):
"""后台导出线程:在原生 threading.Thread 中运行 Pipeline,通过信号与 GUI 通信。
使用原生线程而非 QThread 的工作线程,以避免 QThread 导致的
GPU 推理性能损失。所有参数通过构造函数显式传入,无隐式依赖。
"""
progress_updated = Signal(str, float)
finished = Signal(str)
error_occurred = Signal(str)
cancelled = Signal()
pipeline_ready = Signal(object, object) # (pipeline, output_path)
def __init__(self,
video_path: Path,
roi: tuple,
max_speed_kmh: float,
max_accel_mps2: float,
buffer_size: int,
decode_backend: str,
ocr_backend: str,
fill_width: int,
speed_format: str,
frame_start: str,
frame_end: str,
force_aspect: float,
output_path: Path,
monitor_enabled: bool = True,
gray_output: bool = False,
yuv_output: bool = False,
rep_crop_format: str | None = None,
parent: QWidget | None = None,
) -> None:
super().__init__(parent)
self._video_path = video_path
self._roi = roi
self._max_speed_kmh = max_speed_kmh
self._max_accel_mps2 = max_accel_mps2
self._buffer_size = buffer_size
self._decode_backend = decode_backend
self._ocr_backend = ocr_backend
self._fill_width = fill_width
self._speed_format = speed_format
self._frame_start = _to_int_or_none(frame_start)
self._frame_end = _to_int_or_none(frame_end)
self._force_aspect = force_aspect
self._monitor_enabled = monitor_enabled
self._gray_output = gray_output
self._yuv_output = yuv_output
self._rep_crop_format = rep_crop_format or (
"yuv" if yuv_output else ("gray" if gray_output else "yuv"))
self._output_path = output_path
self._cancel_flag = False
def run(self) -> None:
"""Run Pipeline in a native threading.Thread, wait for completion."""
from segment_flow import SegmentPipeline
done = threading.Event()
error_container: list[Exception] = []
result_container: dict = {}
def _worker() -> None:
import config
import monitor as _monitor
try:
if self._monitor_enabled:
_monitor.start(interval_s=config.MONITOR_INTERVAL_S,
with_gpu=config.MONITOR_GPU)
self._check_cancel()
pipeline = SegmentPipeline(
video_path=str(self._video_path),
roi=self._roi,
max_speed_kmh=self._max_speed_kmh,
max_accel_mps2=self._max_accel_mps2,
buffer_size=self._buffer_size,
decode_backend=self._decode_backend,
ocr_backend=self._ocr_backend,
fill_width=self._fill_width,
speed_format=self._speed_format,
frame_start=self._frame_start,
frame_end=self._frame_end,
progress_cb=self._emit_progress,
force_aspect=self._force_aspect,
fps=None,
cancel_check=self._check_cancel,
rep_crop_format=self._rep_crop_format,
)
pipeline.run(self._output_path)
result_container["mode"] = "auto"
result_container["timing"] = pipeline.timing_flat()
self.pipeline_ready.emit(pipeline, self._output_path)
except _CancelExport:
result_container["cancelled"] = True
except Exception as exc:
import traceback
traceback.print_exc()
error_container.append(exc)
finally:
# 取消/异常路径也必须停止监测(finally 兜底,幂等)
if self._monitor_enabled:
_stats = _monitor.stop()
if _stats:
_monitor.log_run(self._video_path.name, _stats,
result_container.get("timing"))
done.set()
t = threading.Thread(target=_worker, daemon=True)
t.start()
while not done.wait(1.0):
if self._cancel_flag:
done.set()
t.join(2.0)
result_container["cancelled"] = True
break
t.join(2.0)
if error_container:
self.error_occurred.emit(str(error_container[0]))
elif result_container.get("cancelled"):
self.cancelled.emit()
else:
self.finished.emit(result_container["mode"])
def _check_cancel(self) -> None:
if self._cancel_flag:
raise _CancelExport()
def _emit_progress(self, msg: str, pct: float) -> None:
self.progress_updated.emit(msg, pct)