bugfixes
This commit is contained in:
parent
cb7f32d24d
commit
e4635bb1f1
@ -1842,6 +1842,9 @@ func trainingCreateNextSample() (*TrainingSample, error) {
|
|||||||
|
|
||||||
func trainingCreateUncertainNextSampleWithProgress(startedAtMs int64) (*TrainingSample, error) {
|
func trainingCreateUncertainNextSampleWithProgress(startedAtMs int64) (*TrainingSample, error) {
|
||||||
const attempts = 8
|
const attempts = 8
|
||||||
|
const stepsPerAttempt = 4
|
||||||
|
|
||||||
|
totalSteps := attempts*stepsPerAttempt + 1
|
||||||
|
|
||||||
root, err := trainingRootDir()
|
root, err := trainingRootDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1853,22 +1856,47 @@ func trainingCreateUncertainNextSampleWithProgress(startedAtMs int64) (*Training
|
|||||||
errs := []string{}
|
errs := []string{}
|
||||||
|
|
||||||
for i := 0; i < attempts; i++ {
|
for i := 0; i < attempts; i++ {
|
||||||
publishAnalysisStep(
|
attempt := i + 1
|
||||||
|
stepStart := i*stepsPerAttempt + 1
|
||||||
|
|
||||||
|
prefix := fmt.Sprintf("Kandidat %d/%d: ", attempt, attempts)
|
||||||
|
|
||||||
|
sample, err := trainingCreateNextSampleWithProgressRange(
|
||||||
startedAtMs,
|
startedAtMs,
|
||||||
i+1,
|
stepStart,
|
||||||
attempts+1,
|
totalSteps,
|
||||||
"",
|
prefix,
|
||||||
fmt.Sprintf("Unsicherer Kandidat %d/%d wird analysiert…", i+1, attempts),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
sample, err := trainingCreateNextSample()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, err.Error())
|
errs = append(errs, err.Error())
|
||||||
|
|
||||||
|
publishAnalysisStep(
|
||||||
|
startedAtMs,
|
||||||
|
stepStart+stepsPerAttempt-1,
|
||||||
|
totalSteps,
|
||||||
|
"",
|
||||||
|
fmt.Sprintf("Kandidat %d/%d fehlgeschlagen…", attempt, attempts),
|
||||||
|
)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
score := trainingPredictionUncertaintyScore(sample.Prediction)
|
score := trainingPredictionUncertaintyScore(sample.Prediction)
|
||||||
|
|
||||||
|
publishAnalysisStep(
|
||||||
|
startedAtMs,
|
||||||
|
stepStart+stepsPerAttempt-1,
|
||||||
|
totalSteps,
|
||||||
|
sample.SourceFile,
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Kandidat %d/%d bewertet · Unsicherheit %.0f%%",
|
||||||
|
attempt,
|
||||||
|
attempts,
|
||||||
|
score*100,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
if score > bestScore {
|
if score > bestScore {
|
||||||
if best != nil {
|
if best != nil {
|
||||||
trainingDeleteSampleFiles(root, best.SampleID)
|
trainingDeleteSampleFiles(root, best.SampleID)
|
||||||
@ -1891,8 +1919,8 @@ func trainingCreateUncertainNextSampleWithProgress(startedAtMs int64) (*Training
|
|||||||
|
|
||||||
publishAnalysisStep(
|
publishAnalysisStep(
|
||||||
startedAtMs,
|
startedAtMs,
|
||||||
attempts+1,
|
totalSteps,
|
||||||
attempts+1,
|
totalSteps,
|
||||||
best.SourceFile,
|
best.SourceFile,
|
||||||
fmt.Sprintf("Unsicherer Kandidat gewählt · Score %.0f%%", bestScore*100),
|
fmt.Sprintf("Unsicherer Kandidat gewählt · Score %.0f%%", bestScore*100),
|
||||||
)
|
)
|
||||||
@ -1988,7 +2016,31 @@ func trainingPredictionUncertaintyScore(pred TrainingPrediction) float64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func trainingCreateNextSampleWithProgress(startedAtMs int64) (*TrainingSample, error) {
|
func trainingCreateNextSampleWithProgress(startedAtMs int64) (*TrainingSample, error) {
|
||||||
publishAnalysisStep(startedAtMs, 1, 4, "", "Video wird ausgewählt…")
|
return trainingCreateNextSampleWithProgressRange(
|
||||||
|
startedAtMs,
|
||||||
|
1,
|
||||||
|
4,
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func trainingCreateNextSampleWithProgressRange(
|
||||||
|
startedAtMs int64,
|
||||||
|
stepStart int,
|
||||||
|
stepTotal int,
|
||||||
|
prefix string,
|
||||||
|
) (*TrainingSample, error) {
|
||||||
|
publishStep := func(localStep int, sourceFile string, message string) {
|
||||||
|
publishAnalysisStep(
|
||||||
|
startedAtMs,
|
||||||
|
stepStart+localStep-1,
|
||||||
|
stepTotal,
|
||||||
|
sourceFile,
|
||||||
|
prefix+message,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
publishStep(1, "", "Video wird ausgewählt…")
|
||||||
|
|
||||||
settings := getSettings()
|
settings := getSettings()
|
||||||
|
|
||||||
@ -2002,7 +2054,9 @@ func trainingCreateNextSampleWithProgress(startedAtMs int64) (*TrainingSample, e
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
publishAnalysisStep(startedAtMs, 2, 4, filepath.Base(videoPath), "Bild wird extrahiert…")
|
sourceFile := filepath.Base(videoPath)
|
||||||
|
|
||||||
|
publishStep(2, sourceFile, "Bild wird extrahiert…")
|
||||||
|
|
||||||
duration := trainingProbeDurationSeconds(videoPath)
|
duration := trainingProbeDurationSeconds(videoPath)
|
||||||
second := trainingRandomSecond(duration)
|
second := trainingRandomSecond(duration)
|
||||||
@ -2019,6 +2073,7 @@ func trainingCreateNextSampleWithProgress(startedAtMs int64) (*TrainingSample, e
|
|||||||
if err := os.MkdirAll(filepath.Join(root, "frames"), 0755); err != nil {
|
if err := os.MkdirAll(filepath.Join(root, "frames"), 0755); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.MkdirAll(filepath.Join(root, "samples"), 0755); err != nil {
|
if err := os.MkdirAll(filepath.Join(root, "samples"), 0755); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -2027,6 +2082,8 @@ func trainingCreateNextSampleWithProgress(startedAtMs int64) (*TrainingSample, e
|
|||||||
framePath := filepath.Join(root, "frames", id+".jpg")
|
framePath := filepath.Join(root, "frames", id+".jpg")
|
||||||
|
|
||||||
if err := trainingExtractFrame(videoPath, framePath, second); err != nil {
|
if err := trainingExtractFrame(videoPath, framePath, second); err != nil {
|
||||||
|
publishStep(2, sourceFile, "Bild wird erneut bei Sekunde 0 extrahiert…")
|
||||||
|
|
||||||
second = 0
|
second = 0
|
||||||
id = trainingMakeSampleID(videoPath, second)
|
id = trainingMakeSampleID(videoPath, second)
|
||||||
framePath = filepath.Join(root, "frames", id+".jpg")
|
framePath = filepath.Join(root, "frames", id+".jpg")
|
||||||
@ -2036,7 +2093,7 @@ func trainingCreateNextSampleWithProgress(startedAtMs int64) (*TrainingSample, e
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
publishAnalysisStep(startedAtMs, 3, 4, filepath.Base(videoPath), "Bild wird analysiert…")
|
publishStep(3, sourceFile, "Bild wird analysiert…")
|
||||||
|
|
||||||
prediction := trainingPredictFrame(framePath)
|
prediction := trainingPredictFrame(framePath)
|
||||||
|
|
||||||
@ -2048,7 +2105,7 @@ func trainingCreateNextSampleWithProgress(startedAtMs int64) (*TrainingSample, e
|
|||||||
sample := &TrainingSample{
|
sample := &TrainingSample{
|
||||||
SampleID: id,
|
SampleID: id,
|
||||||
FrameURL: "/api/training/frame?id=" + id,
|
FrameURL: "/api/training/frame?id=" + id,
|
||||||
SourceFile: filepath.Base(videoPath),
|
SourceFile: sourceFile,
|
||||||
SourcePath: videoPath,
|
SourcePath: videoPath,
|
||||||
SourceSizeBytes: sourceSizeBytes,
|
SourceSizeBytes: sourceSizeBytes,
|
||||||
Second: second,
|
Second: second,
|
||||||
@ -2056,7 +2113,7 @@ func trainingCreateNextSampleWithProgress(startedAtMs int64) (*TrainingSample, e
|
|||||||
Prediction: prediction,
|
Prediction: prediction,
|
||||||
}
|
}
|
||||||
|
|
||||||
publishAnalysisStep(startedAtMs, 4, 4, filepath.Base(videoPath), "Analyse-Ergebnis wird gespeichert…")
|
publishStep(4, sourceFile, "Analyse-Ergebnis wird gespeichert…")
|
||||||
|
|
||||||
if err := trainingWriteSample(root, sample); err != nil {
|
if err := trainingWriteSample(root, sample); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@ -2741,7 +2741,6 @@ export default function App() {
|
|||||||
if (!authed) return
|
if (!authed) return
|
||||||
|
|
||||||
let es: EventSource | null = null
|
let es: EventSource | null = null
|
||||||
let autostartEs: EventSource | null = null
|
|
||||||
let fallbackTimer: number | null = null
|
let fallbackTimer: number | null = null
|
||||||
|
|
||||||
const stopFallbackPoll = () => {
|
const stopFallbackPoll = () => {
|
||||||
@ -2844,6 +2843,12 @@ export default function App() {
|
|||||||
|
|
||||||
if (msg?.type !== 'analysis_progress') return
|
if (msg?.type !== 'analysis_progress') return
|
||||||
|
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent('app:sse:analysis', {
|
||||||
|
detail: msg,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
const file = baseName(String(msg?.file ?? '').trim())
|
const file = baseName(String(msg?.file ?? '').trim())
|
||||||
if (!file) return
|
if (!file) return
|
||||||
|
|
||||||
@ -2929,6 +2934,12 @@ export default function App() {
|
|||||||
if (data?.type !== 'training_status') return
|
if (data?.type !== 'training_status') return
|
||||||
|
|
||||||
setTrainingTabRunning(Boolean(data?.training?.running))
|
setTrainingTabRunning(Boolean(data?.training?.running))
|
||||||
|
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent('app:sse:training', {
|
||||||
|
detail: data,
|
||||||
|
})
|
||||||
|
)
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
@ -2944,20 +2955,6 @@ export default function App() {
|
|||||||
eventSourceRef.current = es
|
eventSourceRef.current = es
|
||||||
modelEventNamesRef.current = new Set()
|
modelEventNamesRef.current = new Set()
|
||||||
|
|
||||||
autostartEs = new EventSource('/api/autostart/state/stream')
|
|
||||||
|
|
||||||
autostartEs.addEventListener('autostart', (ev) => {
|
|
||||||
try {
|
|
||||||
applyAutostartState(JSON.parse(String((ev as MessageEvent).data ?? 'null')))
|
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
autostartEs.onerror = () => {
|
|
||||||
// Falls der separate Stream ausfällt, wenigstens beim Fokus/Visibility wieder korrekt ziehen.
|
|
||||||
}
|
|
||||||
|
|
||||||
es.onopen = () => {
|
es.onopen = () => {
|
||||||
stopFallbackPoll()
|
stopFallbackPoll()
|
||||||
void loadTaskStateOnce()
|
void loadTaskStateOnce()
|
||||||
@ -3010,10 +3007,6 @@ export default function App() {
|
|||||||
es.close()
|
es.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (autostartEs) {
|
|
||||||
autostartEs.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
eventSourceRef.current = null
|
eventSourceRef.current = null
|
||||||
modelEventNamesRef.current = new Set()
|
modelEventNamesRef.current = new Set()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2326,10 +2326,10 @@ export default function TrainingTab(props: {
|
|||||||
|
|
||||||
const url = `/api/training/next${params.toString() ? `?${params.toString()}` : ''}`
|
const url = `/api/training/next${params.toString() ? `?${params.toString()}` : ''}`
|
||||||
|
|
||||||
setAnalysisProgress(25)
|
setAnalysisProgress(uncertainMode ? 5 : 25)
|
||||||
setAnalysisStep(
|
setAnalysisStep(
|
||||||
uncertainMode
|
uncertainMode
|
||||||
? 'Mehrere Kandidaten werden bewertet…'
|
? 'Mehrere Kandidaten werden vorbereitet…'
|
||||||
: 'Bild wird vorbereitet…'
|
: 'Bild wird vorbereitet…'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -2441,11 +2441,9 @@ export default function TrainingTab(props: {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const es = new EventSource('/api/events/stream')
|
const onTraining = (event: Event) => {
|
||||||
|
|
||||||
const onTraining = (ev: MessageEvent) => {
|
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(String(ev.data ?? 'null'))
|
const data = (event as CustomEvent<any>).detail
|
||||||
|
|
||||||
if (data?.type !== 'training_status') return
|
if (data?.type !== 'training_status') return
|
||||||
|
|
||||||
@ -2457,18 +2455,69 @@ export default function TrainingTab(props: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
es.addEventListener('training', onTraining as EventListener)
|
window.addEventListener('app:sse:training', onTraining as EventListener)
|
||||||
|
|
||||||
es.onerror = () => {
|
|
||||||
// Optional: Polling-Fallback bleibt separat bestehen.
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
es.removeEventListener('training', onTraining as EventListener)
|
window.removeEventListener('app:sse:training', onTraining as EventListener)
|
||||||
es.close()
|
|
||||||
}
|
}
|
||||||
}, [applyTrainingStatus])
|
}, [applyTrainingStatus])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onAnalysis = (event: Event) => {
|
||||||
|
try {
|
||||||
|
const data = (event as CustomEvent<any>).detail
|
||||||
|
|
||||||
|
const message = String(
|
||||||
|
data?.message ||
|
||||||
|
data?.step ||
|
||||||
|
data?.title ||
|
||||||
|
''
|
||||||
|
).trim()
|
||||||
|
|
||||||
|
const current = Number(data?.current ?? data?.stepIndex ?? data?.index)
|
||||||
|
const total = Number(data?.total ?? data?.steps ?? data?.stepTotal)
|
||||||
|
|
||||||
|
const rawProgress = Number(
|
||||||
|
data?.progress ??
|
||||||
|
data?.percent ??
|
||||||
|
data?.analysis?.progress
|
||||||
|
)
|
||||||
|
|
||||||
|
let nextProgress: number | null = null
|
||||||
|
|
||||||
|
if (
|
||||||
|
Number.isFinite(current) &&
|
||||||
|
Number.isFinite(total) &&
|
||||||
|
total > 0
|
||||||
|
) {
|
||||||
|
nextProgress = (current / total) * 100
|
||||||
|
} else if (Number.isFinite(rawProgress)) {
|
||||||
|
nextProgress = rawProgress <= 1
|
||||||
|
? rawProgress * 100
|
||||||
|
: rawProgress
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message) {
|
||||||
|
setAnalysisStep(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextProgress !== null) {
|
||||||
|
setAnalysisProgress((prev) =>
|
||||||
|
Math.max(prev, clampPercent(nextProgress))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('app:sse:analysis', onAnalysis as EventListener)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('app:sse:analysis', onAnalysis as EventListener)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const draggingBox = Boolean(drawingBox || boxInteraction)
|
const draggingBox = Boolean(drawingBox || boxInteraction)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user