bugfixes
This commit is contained in:
parent
9e1e41fe04
commit
93b51978c5
BIN
backend/dist/nsfwapp-linux-amd64
vendored
BIN
backend/dist/nsfwapp-linux-amd64
vendored
Binary file not shown.
BIN
backend/dist/nsfwapp.exe
vendored
BIN
backend/dist/nsfwapp.exe
vendored
Binary file not shown.
BIN
backend/dist/nsfwapp_amd64.deb
vendored
BIN
backend/dist/nsfwapp_amd64.deb
vendored
Binary file not shown.
@ -2548,6 +2548,31 @@ func trainingPruneImportVideoJobsLocked(now time.Time) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func trainingLatestRunningImportVideoJobRequestID() string {
|
||||||
|
now := time.Now().UTC()
|
||||||
|
|
||||||
|
trainingImportVideoJobs.Lock()
|
||||||
|
defer trainingImportVideoJobs.Unlock()
|
||||||
|
|
||||||
|
trainingPruneImportVideoJobsLocked(now)
|
||||||
|
|
||||||
|
requestID := ""
|
||||||
|
var latestStarted time.Time
|
||||||
|
|
||||||
|
for id, job := range trainingImportVideoJobs.items {
|
||||||
|
if job == nil || !job.running {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if requestID == "" || job.startedAt.After(latestStarted) {
|
||||||
|
requestID = id
|
||||||
|
latestStarted = job.startedAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestID
|
||||||
|
}
|
||||||
|
|
||||||
func trainingStartImportVideoJob(req TrainingImportVideoRequest) {
|
func trainingStartImportVideoJob(req TrainingImportVideoRequest) {
|
||||||
requestID := strings.TrimSpace(req.AnalysisRequestID)
|
requestID := strings.TrimSpace(req.AnalysisRequestID)
|
||||||
if requestID == "" {
|
if requestID == "" {
|
||||||
@ -2591,7 +2616,11 @@ func trainingStartImportVideoJob(req TrainingImportVideoRequest) {
|
|||||||
func trainingImportVideoJobResponse(requestID string) (TrainingImportVideoResponse, int) {
|
func trainingImportVideoJobResponse(requestID string) (TrainingImportVideoResponse, int) {
|
||||||
requestID = strings.TrimSpace(requestID)
|
requestID = strings.TrimSpace(requestID)
|
||||||
if requestID == "" {
|
if requestID == "" {
|
||||||
return TrainingImportVideoResponse{OK: false, Error: "requestId missing"}, http.StatusBadRequest
|
requestID = trainingLatestRunningImportVideoJobRequestID()
|
||||||
|
}
|
||||||
|
|
||||||
|
if requestID == "" {
|
||||||
|
return TrainingImportVideoResponse{OK: false, Error: "Kein laufender Import-Job gefunden."}, http.StatusNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
trainingImportVideoJobs.Lock()
|
trainingImportVideoJobs.Lock()
|
||||||
@ -3083,6 +3112,31 @@ func trainingPruneNextJobsLocked(now time.Time) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func trainingLatestRunningNextJobRequestID() string {
|
||||||
|
now := time.Now().UTC()
|
||||||
|
|
||||||
|
trainingNextJobs.Lock()
|
||||||
|
defer trainingNextJobs.Unlock()
|
||||||
|
|
||||||
|
trainingPruneNextJobsLocked(now)
|
||||||
|
|
||||||
|
requestID := ""
|
||||||
|
var latestStarted time.Time
|
||||||
|
|
||||||
|
for id, job := range trainingNextJobs.items {
|
||||||
|
if job == nil || !job.running {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if requestID == "" || job.startedAt.After(latestStarted) {
|
||||||
|
requestID = id
|
||||||
|
latestStarted = job.startedAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestID
|
||||||
|
}
|
||||||
|
|
||||||
func trainingStartNextJob(req TrainingNextRequest) {
|
func trainingStartNextJob(req TrainingNextRequest) {
|
||||||
requestID := strings.TrimSpace(req.AnalysisRequestID)
|
requestID := strings.TrimSpace(req.AnalysisRequestID)
|
||||||
if requestID == "" {
|
if requestID == "" {
|
||||||
@ -3127,7 +3181,11 @@ func trainingStartNextJob(req TrainingNextRequest) {
|
|||||||
func trainingNextJobResponse(requestID string) (TrainingNextResponse, int) {
|
func trainingNextJobResponse(requestID string) (TrainingNextResponse, int) {
|
||||||
requestID = strings.TrimSpace(requestID)
|
requestID = strings.TrimSpace(requestID)
|
||||||
if requestID == "" {
|
if requestID == "" {
|
||||||
return TrainingNextResponse{OK: false, Error: "requestId missing"}, http.StatusBadRequest
|
requestID = trainingLatestRunningNextJobRequestID()
|
||||||
|
}
|
||||||
|
|
||||||
|
if requestID == "" {
|
||||||
|
return TrainingNextResponse{OK: false, Error: "Kein laufender Analyse-Job gefunden."}, http.StatusNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
trainingNextJobs.Lock()
|
trainingNextJobs.Lock()
|
||||||
|
|||||||
@ -22,7 +22,7 @@ const trainingNoSexPositionLabel = "keine"
|
|||||||
|
|
||||||
func isNoSexPositionLabel(label string) bool {
|
func isNoSexPositionLabel(label string) bool {
|
||||||
switch strings.ToLower(strings.TrimSpace(label)) {
|
switch strings.ToLower(strings.TrimSpace(label)) {
|
||||||
case "", trainingNoSexPositionLabel:
|
case "", trainingNoSexPositionLabel, "unknown", "unbekannt", "none", "no_position", "no-position", "no position", "n/a", "na":
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
|||||||
@ -132,6 +132,17 @@ func TestTrainingEffectiveCorrectionClearsNegativeAnnotation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNoSexPositionAliasesTreatUnknownAsNoPosition(t *testing.T) {
|
||||||
|
for _, value := range []string{"Unknown", "unknown", "unbekannt", "none", "no_position"} {
|
||||||
|
if !isNoSexPositionLabel(value) {
|
||||||
|
t.Fatalf("%q should be treated as no sex position", value)
|
||||||
|
}
|
||||||
|
if got := normalizeSexPositionLabel(value); got != trainingNoSexPositionLabel {
|
||||||
|
t.Fatalf("normalizeSexPositionLabel(%q) = %q, want %q", value, got, trainingNoSexPositionLabel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func trainingTestPoseKeypoints(overrides map[string]TrainingKeypoint) []TrainingKeypoint {
|
func trainingTestPoseKeypoints(overrides map[string]TrainingKeypoint) []TrainingKeypoint {
|
||||||
names := []string{
|
names := []string{
|
||||||
"nose",
|
"nose",
|
||||||
|
|||||||
@ -1587,6 +1587,14 @@ const NO_SEX_POSITION_LABEL = 'keine'
|
|||||||
const NO_SEX_POSITION_ALIASES = new Set([
|
const NO_SEX_POSITION_ALIASES = new Set([
|
||||||
'',
|
'',
|
||||||
NO_SEX_POSITION_LABEL,
|
NO_SEX_POSITION_LABEL,
|
||||||
|
'unknown',
|
||||||
|
'unbekannt',
|
||||||
|
'none',
|
||||||
|
'no_position',
|
||||||
|
'no-position',
|
||||||
|
'no position',
|
||||||
|
'n/a',
|
||||||
|
'na',
|
||||||
])
|
])
|
||||||
|
|
||||||
function normalizeSexPositionValue(value?: string | null) {
|
function normalizeSexPositionValue(value?: string | null) {
|
||||||
@ -3980,6 +3988,49 @@ const TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY = 'training:active-import-video'
|
|||||||
const TRAINING_ACTIVE_NEXT_STORAGE_KEY = 'training:active-next'
|
const TRAINING_ACTIVE_NEXT_STORAGE_KEY = 'training:active-next'
|
||||||
const ALL_TRAINING_TARGETS: TrainingTargetKey[] = ['detector', 'pose', 'videomae']
|
const ALL_TRAINING_TARGETS: TrainingTargetKey[] = ['detector', 'pose', 'videomae']
|
||||||
|
|
||||||
|
function readTrainingActiveJobStorage(key: string) {
|
||||||
|
try {
|
||||||
|
const raw = window.localStorage.getItem(key)
|
||||||
|
if (raw) return raw
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return window.sessionStorage.getItem(key) || ''
|
||||||
|
} catch {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeTrainingActiveJobStorage(key: string, value: string) {
|
||||||
|
try {
|
||||||
|
window.localStorage.setItem(key, value)
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
window.sessionStorage.setItem(key, value)
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeTrainingActiveJobStorage(key: string) {
|
||||||
|
try {
|
||||||
|
window.localStorage.removeItem(key)
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
window.sessionStorage.removeItem(key)
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default function TrainingTab(props: {
|
export default function TrainingTab(props: {
|
||||||
active?: boolean
|
active?: boolean
|
||||||
onTrainingRunningChange?: (running: boolean) => void
|
onTrainingRunningChange?: (running: boolean) => void
|
||||||
@ -4715,6 +4766,7 @@ export default function TrainingTab(props: {
|
|||||||
return correction.posePersons ?? []
|
return correction.posePersons ?? []
|
||||||
}, [correction.posePersons])
|
}, [correction.posePersons])
|
||||||
|
|
||||||
|
const poseTrainingAllowed = !isNoSexPositionValue(correction.sexPosition)
|
||||||
const hasPosePersons = posePersons.some((person) =>
|
const hasPosePersons = posePersons.some((person) =>
|
||||||
hasVisiblePoseBox(person) || person.keypoints?.some(isPoseKeypointVisible)
|
hasVisiblePoseBox(person) || person.keypoints?.some(isPoseKeypointVisible)
|
||||||
)
|
)
|
||||||
@ -4909,6 +4961,9 @@ export default function TrainingTab(props: {
|
|||||||
|
|
||||||
const running = Boolean(data?.running)
|
const running = Boolean(data?.running)
|
||||||
if (running) {
|
if (running) {
|
||||||
|
if (!loadingRef.current) {
|
||||||
|
resetPoseUiToBoxes()
|
||||||
|
}
|
||||||
loadingRef.current = true
|
loadingRef.current = true
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
}
|
}
|
||||||
@ -4959,7 +5014,7 @@ export default function TrainingTab(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}, [setLoadingPreviewCandidate])
|
}, [resetPoseUiToBoxes, setLoadingPreviewCandidate])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!imageSrc) {
|
if (!imageSrc) {
|
||||||
@ -5740,11 +5795,11 @@ export default function TrainingTab(props: {
|
|||||||
|
|
||||||
const previewUrl = String(opts?.previewUrl ?? '').trim()
|
const previewUrl = String(opts?.previewUrl ?? '').trim()
|
||||||
|
|
||||||
|
resetPoseUiToBoxes()
|
||||||
setLoadingPreviewFallbackUrl(previewUrl)
|
setLoadingPreviewFallbackUrl(previewUrl)
|
||||||
setLoadingPreviewCandidate(previewUrl)
|
setLoadingPreviewCandidate(previewUrl)
|
||||||
loadingRef.current = true
|
loadingRef.current = true
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
resetPoseUiToBoxes()
|
|
||||||
setAnalysisSourceFile('')
|
setAnalysisSourceFile('')
|
||||||
setAnalysisProgress(8)
|
setAnalysisProgress(8)
|
||||||
setAnalysisStep(
|
setAnalysisStep(
|
||||||
@ -5776,7 +5831,7 @@ export default function TrainingTab(props: {
|
|||||||
if (uncertainMode) params.set('mode', 'uncertain')
|
if (uncertainMode) params.set('mode', 'uncertain')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
window.sessionStorage.setItem(
|
writeTrainingActiveJobStorage(
|
||||||
TRAINING_ACTIVE_NEXT_STORAGE_KEY,
|
TRAINING_ACTIVE_NEXT_STORAGE_KEY,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
requestId,
|
requestId,
|
||||||
@ -5839,7 +5894,7 @@ export default function TrainingTab(props: {
|
|||||||
setMessage('Analyse läuft im Backend weiter. Beim Zurückkehren wird das nächste offene Trainingsbild wieder geladen.')
|
setMessage('Analyse läuft im Backend weiter. Beim Zurückkehren wird das nächste offene Trainingsbild wieder geladen.')
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
window.sessionStorage.removeItem(TRAINING_ACTIVE_NEXT_STORAGE_KEY)
|
removeTrainingActiveJobStorage(TRAINING_ACTIVE_NEXT_STORAGE_KEY)
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
@ -5853,7 +5908,7 @@ export default function TrainingTab(props: {
|
|||||||
|
|
||||||
if (completed) {
|
if (completed) {
|
||||||
try {
|
try {
|
||||||
window.sessionStorage.removeItem(TRAINING_ACTIVE_NEXT_STORAGE_KEY)
|
removeTrainingActiveJobStorage(TRAINING_ACTIVE_NEXT_STORAGE_KEY)
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
@ -5889,6 +5944,7 @@ export default function TrainingTab(props: {
|
|||||||
])
|
])
|
||||||
|
|
||||||
const reloadCurrentImage = useCallback(async () => {
|
const reloadCurrentImage = useCallback(async () => {
|
||||||
|
resetPoseUiToBoxes()
|
||||||
setDrawingBox(null)
|
setDrawingBox(null)
|
||||||
setBoxInteraction(null)
|
setBoxInteraction(null)
|
||||||
setTouchMagnifier(null)
|
setTouchMagnifier(null)
|
||||||
@ -5899,7 +5955,7 @@ export default function TrainingTab(props: {
|
|||||||
previewUrl: imageSrc,
|
previewUrl: imageSrc,
|
||||||
})
|
})
|
||||||
setImageReloadKey((value) => value + 1)
|
setImageReloadKey((value) => value + 1)
|
||||||
}, [imageSrc, loadNext])
|
}, [imageSrc, loadNext, resetPoseUiToBoxes])
|
||||||
|
|
||||||
const loadTrainingStatus = useCallback(async () => {
|
const loadTrainingStatus = useCallback(async () => {
|
||||||
const res = await fetch('/api/training/status', { cache: 'no-store' })
|
const res = await fetch('/api/training/status', { cache: 'no-store' })
|
||||||
@ -5956,6 +6012,10 @@ export default function TrainingTab(props: {
|
|||||||
)
|
)
|
||||||
const data = await res.json().catch(() => null)
|
const data = await res.json().catch(() => null)
|
||||||
|
|
||||||
|
if (data?.analysis) {
|
||||||
|
applyTrainingAnalysisEvent(data.analysis)
|
||||||
|
}
|
||||||
|
|
||||||
if (res.status === 202 || data?.running) {
|
if (res.status === 202 || data?.running) {
|
||||||
await new Promise((resolve) => window.setTimeout(resolve, 1200))
|
await new Promise((resolve) => window.setTimeout(resolve, 1200))
|
||||||
continue
|
continue
|
||||||
@ -5967,7 +6027,7 @@ export default function TrainingTab(props: {
|
|||||||
|
|
||||||
return completeVideoImportFromData(data)
|
return completeVideoImportFromData(data)
|
||||||
}
|
}
|
||||||
}, [completeVideoImportFromData])
|
}, [applyTrainingAnalysisEvent, completeVideoImportFromData])
|
||||||
|
|
||||||
const importVideoIntoTraining = useCallback(async (raw: any) => {
|
const importVideoIntoTraining = useCallback(async (raw: any) => {
|
||||||
const output = String(raw?.output || '').trim()
|
const output = String(raw?.output || '').trim()
|
||||||
@ -5990,7 +6050,7 @@ export default function TrainingTab(props: {
|
|||||||
|
|
||||||
const importKey = `${detail.jobId || ''}|${detail.output}|${detail.count || TRAINING_IMPORT_FRAME_COUNT}`
|
const importKey = `${detail.jobId || ''}|${detail.output}|${detail.count || TRAINING_IMPORT_FRAME_COUNT}`
|
||||||
|
|
||||||
// Verhindert Doppelimport durch sessionStorage + CustomEvent.
|
// Verhindert Doppelimport durch persistent gemerkte aktive Jobs + CustomEvent.
|
||||||
if (videoImportInFlightKeyRef.current === importKey) {
|
if (videoImportInFlightKeyRef.current === importKey) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -6002,6 +6062,7 @@ export default function TrainingTab(props: {
|
|||||||
activeAnalysisRequestIdRef.current = requestId
|
activeAnalysisRequestIdRef.current = requestId
|
||||||
loadingRef.current = true
|
loadingRef.current = true
|
||||||
|
|
||||||
|
resetPoseUiToBoxes()
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
setAnalysisSourceFile(detail.sourceFile || detail.output.split(/[\\/]/).pop() || '')
|
setAnalysisSourceFile(detail.sourceFile || detail.output.split(/[\\/]/).pop() || '')
|
||||||
setAnalysisProgress(5)
|
setAnalysisProgress(5)
|
||||||
@ -6009,10 +6070,13 @@ export default function TrainingTab(props: {
|
|||||||
setError(null)
|
setError(null)
|
||||||
setMessage(null)
|
setMessage(null)
|
||||||
|
|
||||||
|
let keepActiveJob = false
|
||||||
|
let completed = false
|
||||||
|
|
||||||
try {
|
try {
|
||||||
try {
|
try {
|
||||||
window.sessionStorage.removeItem(TRAINING_PENDING_IMPORT_VIDEO_STORAGE_KEY)
|
window.sessionStorage.removeItem(TRAINING_PENDING_IMPORT_VIDEO_STORAGE_KEY)
|
||||||
window.sessionStorage.setItem(
|
writeTrainingActiveJobStorage(
|
||||||
TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY,
|
TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY,
|
||||||
JSON.stringify({ requestId, importKey, detail })
|
JSON.stringify({ requestId, importKey, detail })
|
||||||
)
|
)
|
||||||
@ -6040,9 +6104,10 @@ export default function TrainingTab(props: {
|
|||||||
|
|
||||||
if (res.status === 202 || data?.accepted || data?.running) {
|
if (res.status === 202 || data?.accepted || data?.running) {
|
||||||
await waitForVideoImportResult(String(data?.requestId || requestId))
|
await waitForVideoImportResult(String(data?.requestId || requestId))
|
||||||
|
completed = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
window.sessionStorage.removeItem(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
removeTrainingActiveJobStorage(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
@ -6086,6 +6151,7 @@ export default function TrainingTab(props: {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
completed = true
|
||||||
return true
|
return true
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const msg = e instanceof Error ? e.message : String(e)
|
const msg = e instanceof Error ? e.message : String(e)
|
||||||
@ -6093,10 +6159,11 @@ export default function TrainingTab(props: {
|
|||||||
/load failed|failed to fetch|networkerror|network error/i.test(msg)
|
/load failed|failed to fetch|networkerror|network error/i.test(msg)
|
||||||
|
|
||||||
if (mayStillRun) {
|
if (mayStillRun) {
|
||||||
|
keepActiveJob = true
|
||||||
setMessage('Video-Import läuft im Backend weiter und wird beim Zurückkehren wieder aufgenommen.')
|
setMessage('Video-Import läuft im Backend weiter und wird beim Zurückkehren wieder aufgenommen.')
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
window.sessionStorage.removeItem(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
removeTrainingActiveJobStorage(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
@ -6104,6 +6171,14 @@ export default function TrainingTab(props: {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
} finally {
|
} finally {
|
||||||
|
if (completed) {
|
||||||
|
removeTrainingActiveJobStorage(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keepActiveJob) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
setAnalysisProgress(100)
|
setAnalysisProgress(100)
|
||||||
setAnalysisStep('Video-Import abgeschlossen.')
|
setAnalysisStep('Video-Import abgeschlossen.')
|
||||||
|
|
||||||
@ -6127,6 +6202,7 @@ export default function TrainingTab(props: {
|
|||||||
}, [
|
}, [
|
||||||
loadPriorityTrainingSamples,
|
loadPriorityTrainingSamples,
|
||||||
loadTrainingStatus,
|
loadTrainingStatus,
|
||||||
|
resetPoseUiToBoxes,
|
||||||
setLoadingPreviewCandidate,
|
setLoadingPreviewCandidate,
|
||||||
trainingRunning,
|
trainingRunning,
|
||||||
waitForVideoImportResult,
|
waitForVideoImportResult,
|
||||||
@ -6480,6 +6556,163 @@ export default function TrainingTab(props: {
|
|||||||
})
|
})
|
||||||
}, [activeBoxIndex])
|
}, [activeBoxIndex])
|
||||||
|
|
||||||
|
const resumeLatestServerAnalysisJob = useCallback(async () => {
|
||||||
|
if (
|
||||||
|
activeAnalysisRequestIdRef.current ||
|
||||||
|
nextAnalysisInFlightRequestIdRef.current ||
|
||||||
|
videoImportInFlightKeyRef.current
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const resumeImport = (data: any) => {
|
||||||
|
const requestId = String(data?.requestId || '').trim()
|
||||||
|
if (!requestId) return false
|
||||||
|
|
||||||
|
const importKey = `server|${requestId}`
|
||||||
|
videoImportStartedRef.current = true
|
||||||
|
videoImportInFlightKeyRef.current = importKey
|
||||||
|
activeAnalysisRequestIdRef.current = requestId
|
||||||
|
loadingRef.current = true
|
||||||
|
|
||||||
|
writeTrainingActiveJobStorage(
|
||||||
|
TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY,
|
||||||
|
JSON.stringify({ requestId, importKey, detail: {} })
|
||||||
|
)
|
||||||
|
|
||||||
|
resetPoseUiToBoxes()
|
||||||
|
setLoading(true)
|
||||||
|
setAnalysisSourceFile('')
|
||||||
|
setAnalysisProgress(5)
|
||||||
|
setAnalysisStep('Video-Import läuft im Backend…')
|
||||||
|
setError(null)
|
||||||
|
|
||||||
|
if (data?.analysis) {
|
||||||
|
applyTrainingAnalysisEvent(data.analysis)
|
||||||
|
}
|
||||||
|
|
||||||
|
void waitForVideoImportResult(requestId)
|
||||||
|
.then(() => {
|
||||||
|
removeTrainingActiveJobStorage(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
const msg = e instanceof Error ? e.message : String(e)
|
||||||
|
const mayStillRun =
|
||||||
|
/load failed|failed to fetch|networkerror|network error/i.test(msg)
|
||||||
|
|
||||||
|
if (mayStillRun) {
|
||||||
|
setMessage('Video-Import läuft im Backend weiter und wird beim Zurückkehren wieder aufgenommen.')
|
||||||
|
} else {
|
||||||
|
removeTrainingActiveJobStorage(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
||||||
|
setError(msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (activeAnalysisRequestIdRef.current === requestId) {
|
||||||
|
activeAnalysisRequestIdRef.current = null
|
||||||
|
loadingRef.current = false
|
||||||
|
setLoading(false)
|
||||||
|
setAnalysisSourceFile('')
|
||||||
|
setAnalysisProgress(0)
|
||||||
|
setAnalysisStep('')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoImportInFlightKeyRef.current === importKey) {
|
||||||
|
videoImportInFlightKeyRef.current = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const resumeNext = (data: any) => {
|
||||||
|
const requestId = String(data?.requestId || '').trim()
|
||||||
|
if (!requestId) return false
|
||||||
|
|
||||||
|
activeAnalysisRequestIdRef.current = requestId
|
||||||
|
nextAnalysisInFlightRequestIdRef.current = requestId
|
||||||
|
loadingRef.current = true
|
||||||
|
|
||||||
|
writeTrainingActiveJobStorage(
|
||||||
|
TRAINING_ACTIVE_NEXT_STORAGE_KEY,
|
||||||
|
JSON.stringify({ requestId, opts: {} })
|
||||||
|
)
|
||||||
|
|
||||||
|
resetPoseUiToBoxes()
|
||||||
|
setLoading(true)
|
||||||
|
setAnalysisSourceFile('')
|
||||||
|
setAnalysisProgress(5)
|
||||||
|
setAnalysisStep('Analyse läuft im Backend…')
|
||||||
|
setError(null)
|
||||||
|
|
||||||
|
if (data?.analysis) {
|
||||||
|
applyTrainingAnalysisEvent(data.analysis)
|
||||||
|
}
|
||||||
|
|
||||||
|
void waitForNextResult(requestId)
|
||||||
|
.then(() => {
|
||||||
|
removeTrainingActiveJobStorage(TRAINING_ACTIVE_NEXT_STORAGE_KEY)
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
const msg = e instanceof Error ? e.message : String(e)
|
||||||
|
const mayStillRun =
|
||||||
|
/load failed|failed to fetch|networkerror|network error/i.test(msg)
|
||||||
|
|
||||||
|
if (mayStillRun) {
|
||||||
|
setMessage('Analyse läuft im Backend weiter und wird beim Zurückkehren wieder aufgenommen.')
|
||||||
|
} else {
|
||||||
|
removeTrainingActiveJobStorage(TRAINING_ACTIVE_NEXT_STORAGE_KEY)
|
||||||
|
setError(msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (activeAnalysisRequestIdRef.current === requestId) {
|
||||||
|
activeAnalysisRequestIdRef.current = null
|
||||||
|
nextAnalysisInFlightRequestIdRef.current = null
|
||||||
|
loadingRef.current = false
|
||||||
|
setLoading(false)
|
||||||
|
setAnalysisSourceFile('')
|
||||||
|
setAnalysisProgress(0)
|
||||||
|
setAnalysisStep('')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const importRes = await fetch('/api/training/import-video/status', { cache: 'no-store' })
|
||||||
|
const importData = await importRes.json().catch(() => null)
|
||||||
|
|
||||||
|
if (
|
||||||
|
(importRes.status === 202 || importData?.running || importData?.accepted) &&
|
||||||
|
importData?.ok &&
|
||||||
|
resumeImport(importData)
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const nextRes = await fetch('/api/training/next/status', { cache: 'no-store' })
|
||||||
|
const nextData = await nextRes.json().catch(() => null)
|
||||||
|
|
||||||
|
if (
|
||||||
|
(nextRes.status === 202 || nextData?.running || nextData?.accepted) &&
|
||||||
|
nextData?.ok &&
|
||||||
|
resumeNext(nextData)
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}, [applyTrainingAnalysisEvent, resetPoseUiToBoxes, waitForNextResult, waitForVideoImportResult])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onImportVideo = (event: Event) => {
|
const onImportVideo = (event: Event) => {
|
||||||
const detail = (event as CustomEvent<any>).detail
|
const detail = (event as CustomEvent<any>).detail
|
||||||
@ -6492,7 +6725,7 @@ export default function TrainingTab(props: {
|
|||||||
let resumedActiveNext = false
|
let resumedActiveNext = false
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const activeRaw = window.sessionStorage.getItem(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
const activeRaw = readTrainingActiveJobStorage(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
||||||
|
|
||||||
if (activeRaw) {
|
if (activeRaw) {
|
||||||
const active = JSON.parse(activeRaw)
|
const active = JSON.parse(activeRaw)
|
||||||
@ -6506,6 +6739,7 @@ export default function TrainingTab(props: {
|
|||||||
|
|
||||||
activeAnalysisRequestIdRef.current = requestId
|
activeAnalysisRequestIdRef.current = requestId
|
||||||
loadingRef.current = true
|
loadingRef.current = true
|
||||||
|
resetPoseUiToBoxes()
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
setAnalysisSourceFile(String(active?.detail?.sourceFile || active?.detail?.output || '').split(/[\\/]/).pop() || '')
|
setAnalysisSourceFile(String(active?.detail?.sourceFile || active?.detail?.output || '').split(/[\\/]/).pop() || '')
|
||||||
setAnalysisProgress(5)
|
setAnalysisProgress(5)
|
||||||
@ -6514,7 +6748,7 @@ export default function TrainingTab(props: {
|
|||||||
|
|
||||||
void waitForVideoImportResult(requestId)
|
void waitForVideoImportResult(requestId)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
window.sessionStorage.removeItem(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
removeTrainingActiveJobStorage(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
const msg = e instanceof Error ? e.message : String(e)
|
const msg = e instanceof Error ? e.message : String(e)
|
||||||
@ -6524,7 +6758,7 @@ export default function TrainingTab(props: {
|
|||||||
if (mayStillRun) {
|
if (mayStillRun) {
|
||||||
setMessage('Video-Import läuft im Backend weiter und wird beim Zurückkehren wieder aufgenommen.')
|
setMessage('Video-Import läuft im Backend weiter und wird beim Zurückkehren wieder aufgenommen.')
|
||||||
} else {
|
} else {
|
||||||
window.sessionStorage.removeItem(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
removeTrainingActiveJobStorage(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
||||||
setError(msg)
|
setError(msg)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -6549,7 +6783,7 @@ export default function TrainingTab(props: {
|
|||||||
|
|
||||||
const activeNextRaw = resumedActiveImport
|
const activeNextRaw = resumedActiveImport
|
||||||
? ''
|
? ''
|
||||||
: window.sessionStorage.getItem(TRAINING_ACTIVE_NEXT_STORAGE_KEY)
|
: readTrainingActiveJobStorage(TRAINING_ACTIVE_NEXT_STORAGE_KEY)
|
||||||
|
|
||||||
if (activeNextRaw) {
|
if (activeNextRaw) {
|
||||||
const active = JSON.parse(activeNextRaw)
|
const active = JSON.parse(activeNextRaw)
|
||||||
@ -6560,6 +6794,7 @@ export default function TrainingTab(props: {
|
|||||||
activeAnalysisRequestIdRef.current = requestId
|
activeAnalysisRequestIdRef.current = requestId
|
||||||
nextAnalysisInFlightRequestIdRef.current = requestId
|
nextAnalysisInFlightRequestIdRef.current = requestId
|
||||||
loadingRef.current = true
|
loadingRef.current = true
|
||||||
|
resetPoseUiToBoxes()
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
setAnalysisSourceFile('')
|
setAnalysisSourceFile('')
|
||||||
setAnalysisProgress(5)
|
setAnalysisProgress(5)
|
||||||
@ -6584,7 +6819,7 @@ export default function TrainingTab(props: {
|
|||||||
deferCurrentSampleToQueueEnd: Boolean(activeOpts?.deferCurrentSampleToQueueEnd),
|
deferCurrentSampleToQueueEnd: Boolean(activeOpts?.deferCurrentSampleToQueueEnd),
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
window.sessionStorage.removeItem(TRAINING_ACTIVE_NEXT_STORAGE_KEY)
|
removeTrainingActiveJobStorage(TRAINING_ACTIVE_NEXT_STORAGE_KEY)
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
const msg = e instanceof Error ? e.message : String(e)
|
const msg = e instanceof Error ? e.message : String(e)
|
||||||
@ -6594,7 +6829,7 @@ export default function TrainingTab(props: {
|
|||||||
if (mayStillRun) {
|
if (mayStillRun) {
|
||||||
setMessage('Analyse läuft im Backend weiter und wird beim Zurückkehren wieder aufgenommen.')
|
setMessage('Analyse läuft im Backend weiter und wird beim Zurückkehren wieder aufgenommen.')
|
||||||
} else {
|
} else {
|
||||||
window.sessionStorage.removeItem(TRAINING_ACTIVE_NEXT_STORAGE_KEY)
|
removeTrainingActiveJobStorage(TRAINING_ACTIVE_NEXT_STORAGE_KEY)
|
||||||
setError(msg)
|
setError(msg)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -6630,7 +6865,7 @@ export default function TrainingTab(props: {
|
|||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('training:import-video', onImportVideo as EventListener)
|
window.removeEventListener('training:import-video', onImportVideo as EventListener)
|
||||||
}
|
}
|
||||||
}, [applyTrainingAnalysisEvent, importVideoIntoTraining, waitForNextResult, waitForVideoImportResult])
|
}, [applyTrainingAnalysisEvent, importVideoIntoTraining, resetPoseUiToBoxes, waitForNextResult, waitForVideoImportResult])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!tabActive || initializedRef.current) return
|
if (!tabActive || initializedRef.current) return
|
||||||
@ -6645,6 +6880,15 @@ export default function TrainingTab(props: {
|
|||||||
|
|
||||||
if (cancelled || initRunIdRef.current !== runId) return
|
if (cancelled || initRunIdRef.current !== runId) return
|
||||||
|
|
||||||
|
const resumedServerJob = await resumeLatestServerAnalysisJob()
|
||||||
|
|
||||||
|
if (cancelled || initRunIdRef.current !== runId) return
|
||||||
|
|
||||||
|
if (resumedServerJob) {
|
||||||
|
initializedRef.current = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Wichtig:
|
// Wichtig:
|
||||||
// Wenn gerade ein Video-Import über "Video ins Training übernehmen" läuft,
|
// Wenn gerade ein Video-Import über "Video ins Training übernehmen" läuft,
|
||||||
// darf loadNext() nicht danach ein zufälliges/letztes Sample darüberlegen.
|
// darf loadNext() nicht danach ein zufälliges/letztes Sample darüberlegen.
|
||||||
@ -6669,7 +6913,7 @@ export default function TrainingTab(props: {
|
|||||||
return () => {
|
return () => {
|
||||||
cancelled = true
|
cancelled = true
|
||||||
}
|
}
|
||||||
}, [tabActive, loadLabels, loadNext, loadTrainingStatus])
|
}, [tabActive, loadLabels, loadNext, loadTrainingStatus, resumeLatestServerAnalysisJob])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!tabActive || !initializedRef.current) return
|
if (!tabActive || !initializedRef.current) return
|
||||||
@ -6693,7 +6937,7 @@ export default function TrainingTab(props: {
|
|||||||
let importRequestId = activeAnalysisRequestIdRef.current || ''
|
let importRequestId = activeAnalysisRequestIdRef.current || ''
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const activeNextRaw = window.sessionStorage.getItem(TRAINING_ACTIVE_NEXT_STORAGE_KEY)
|
const activeNextRaw = readTrainingActiveJobStorage(TRAINING_ACTIVE_NEXT_STORAGE_KEY)
|
||||||
if (!nextRequestId && activeNextRaw) {
|
if (!nextRequestId && activeNextRaw) {
|
||||||
nextRequestId = String(JSON.parse(activeNextRaw)?.requestId || '').trim()
|
nextRequestId = String(JSON.parse(activeNextRaw)?.requestId || '').trim()
|
||||||
}
|
}
|
||||||
@ -6702,7 +6946,7 @@ export default function TrainingTab(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const activeImportRaw = window.sessionStorage.getItem(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
const activeImportRaw = readTrainingActiveJobStorage(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
||||||
if (activeImportRaw) {
|
if (activeImportRaw) {
|
||||||
importRequestId = String(JSON.parse(activeImportRaw)?.requestId || importRequestId || '').trim()
|
importRequestId = String(JSON.parse(activeImportRaw)?.requestId || importRequestId || '').trim()
|
||||||
}
|
}
|
||||||
@ -6869,6 +7113,7 @@ export default function TrainingTab(props: {
|
|||||||
) => {
|
) => {
|
||||||
if (!sample || trainingRunning) return
|
if (!sample || trainingRunning) return
|
||||||
|
|
||||||
|
resetPoseUiToBoxes()
|
||||||
setSavingOverlayText(editingFeedback ? 'Feedback wird aktualisiert…' : 'Feedback wird gespeichert…')
|
setSavingOverlayText(editingFeedback ? 'Feedback wird aktualisiert…' : 'Feedback wird gespeichert…')
|
||||||
setSaving(true)
|
setSaving(true)
|
||||||
setError(null)
|
setError(null)
|
||||||
@ -7029,6 +7274,7 @@ export default function TrainingTab(props: {
|
|||||||
loadTrainingStatus,
|
loadTrainingStatus,
|
||||||
loadQueuedTrainingSample,
|
loadQueuedTrainingSample,
|
||||||
loadNextImportedQueuedSample,
|
loadNextImportedQueuedSample,
|
||||||
|
resetPoseUiToBoxes,
|
||||||
trainingRunning,
|
trainingRunning,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@ -7053,6 +7299,7 @@ export default function TrainingTab(props: {
|
|||||||
|
|
||||||
const skippedSampleId = sample.sampleId
|
const skippedSampleId = sample.sampleId
|
||||||
|
|
||||||
|
resetPoseUiToBoxes()
|
||||||
setSavingOverlayText('Bild wird übersprungen…')
|
setSavingOverlayText('Bild wird übersprungen…')
|
||||||
setSaving(true)
|
setSaving(true)
|
||||||
setError(null)
|
setError(null)
|
||||||
@ -7101,6 +7348,7 @@ export default function TrainingTab(props: {
|
|||||||
loadNext,
|
loadNext,
|
||||||
loadQueuedTrainingSample,
|
loadQueuedTrainingSample,
|
||||||
loadNextImportedQueuedSample,
|
loadNextImportedQueuedSample,
|
||||||
|
resetPoseUiToBoxes,
|
||||||
])
|
])
|
||||||
|
|
||||||
const openTrainingStartModal = useCallback(() => {
|
const openTrainingStartModal = useCallback(() => {
|
||||||
@ -7363,6 +7611,16 @@ export default function TrainingTab(props: {
|
|||||||
latestGestureBoxRef.current = null
|
latestGestureBoxRef.current = null
|
||||||
}, [cancelPointerMoveFrame])
|
}, [cancelPointerMoveFrame])
|
||||||
|
|
||||||
|
const resetBoxDrawingMode = useCallback(() => {
|
||||||
|
releaseActivePointerCapture()
|
||||||
|
clearBoxGestureRefs()
|
||||||
|
setDrawingBox(null)
|
||||||
|
setBoxInteraction(null)
|
||||||
|
setTouchMagnifier(null)
|
||||||
|
setBoxLabel('')
|
||||||
|
setActiveBoxIndex(null)
|
||||||
|
}, [clearBoxGestureRefs, releaseActivePointerCapture])
|
||||||
|
|
||||||
const applyPointerMoveSnapshot = useCallback((snapshot: {
|
const applyPointerMoveSnapshot = useCallback((snapshot: {
|
||||||
clientX: number
|
clientX: number
|
||||||
clientY: number
|
clientY: number
|
||||||
@ -8071,7 +8329,7 @@ export default function TrainingTab(props: {
|
|||||||
}, [markManualCorrection])
|
}, [markManualCorrection])
|
||||||
|
|
||||||
const addPosePerson = useCallback(() => {
|
const addPosePerson = useCallback(() => {
|
||||||
if (uiLocked) return
|
if (uiLocked || !poseTrainingAllowed) return
|
||||||
|
|
||||||
markManualCorrection()
|
markManualCorrection()
|
||||||
|
|
||||||
@ -8089,8 +8347,7 @@ export default function TrainingTab(props: {
|
|||||||
})
|
})
|
||||||
|
|
||||||
setShowPoseSkeleton(true)
|
setShowPoseSkeleton(true)
|
||||||
setActiveBoxIndex(null)
|
resetBoxDrawingMode()
|
||||||
setBoxLabel('')
|
|
||||||
setActivePosePersonIndex(nextPersonIndex)
|
setActivePosePersonIndex(nextPersonIndex)
|
||||||
setPendingPoseKeypoint(null)
|
setPendingPoseKeypoint(null)
|
||||||
setSelectedPoseKeypointAction(null)
|
setSelectedPoseKeypointAction(null)
|
||||||
@ -8103,7 +8360,7 @@ export default function TrainingTab(props: {
|
|||||||
behavior: 'smooth',
|
behavior: 'smooth',
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}, [markManualCorrection, switchPoseKeypointMapView, uiLocked])
|
}, [markManualCorrection, poseTrainingAllowed, resetBoxDrawingMode, switchPoseKeypointMapView, uiLocked])
|
||||||
|
|
||||||
const removePoseKeypoint = useCallback((personIndex: number, keypointIndex: number) => {
|
const removePoseKeypoint = useCallback((personIndex: number, keypointIndex: number) => {
|
||||||
markManualCorrection()
|
markManualCorrection()
|
||||||
@ -8311,7 +8568,8 @@ export default function TrainingTab(props: {
|
|||||||
setLoadingPreviewFailed(false)
|
setLoadingPreviewFailed(false)
|
||||||
}, [loading, frameBusy, loadingPreviewFallbackUrl, loadingPreviewUrl])
|
}, [loading, frameBusy, loadingPreviewFallbackUrl, loadingPreviewUrl])
|
||||||
|
|
||||||
const poseModeAvailable = Boolean(sample) || hasPosePersons || originalPosePersonCount > 0
|
const poseModeAvailable =
|
||||||
|
poseTrainingAllowed && (Boolean(sample) || hasPosePersons || originalPosePersonCount > 0)
|
||||||
const poseModeActive = showPoseSkeleton && poseModeAvailable
|
const poseModeActive = showPoseSkeleton && poseModeAvailable
|
||||||
const annotationLayerHidden = frameBusy || trainingRunning || saving
|
const annotationLayerHidden = frameBusy || trainingRunning || saving
|
||||||
const poseSkeletonVisible = poseModeActive && hasPosePersons && !annotationLayerHidden
|
const poseSkeletonVisible = poseModeActive && hasPosePersons && !annotationLayerHidden
|
||||||
@ -8322,6 +8580,15 @@ export default function TrainingTab(props: {
|
|||||||
focusedPoseOverlayPersonIndex === null || personIndex === focusedPoseOverlayPersonIndex,
|
focusedPoseOverlayPersonIndex === null || personIndex === focusedPoseOverlayPersonIndex,
|
||||||
[focusedPoseOverlayPersonIndex]
|
[focusedPoseOverlayPersonIndex]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (poseTrainingAllowed || !showPoseSkeleton) return
|
||||||
|
|
||||||
|
setShowPoseSkeleton(false)
|
||||||
|
setActivePosePersonIndex(null)
|
||||||
|
setPendingPoseKeypoint(null)
|
||||||
|
setSelectedPoseKeypointAction(null)
|
||||||
|
}, [poseTrainingAllowed, showPoseSkeleton])
|
||||||
const poseFaceImageZoomActive =
|
const poseFaceImageZoomActive =
|
||||||
poseModeActive &&
|
poseModeActive &&
|
||||||
poseKeypointMapView === 'head' &&
|
poseKeypointMapView === 'head' &&
|
||||||
@ -8829,8 +9096,8 @@ export default function TrainingTab(props: {
|
|||||||
className={modeButtonClass(poseModeActive)}
|
className={modeButtonClass(poseModeActive)}
|
||||||
aria-pressed={poseModeActive}
|
aria-pressed={poseModeActive}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
resetBoxDrawingMode()
|
||||||
setShowPoseSkeleton(true)
|
setShowPoseSkeleton(true)
|
||||||
setActiveBoxIndex(null)
|
|
||||||
}}
|
}}
|
||||||
title="Pose-Skelett korrigieren"
|
title="Pose-Skelett korrigieren"
|
||||||
>
|
>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user