fixed object detection
This commit is contained in:
parent
b6ad070ee4
commit
13ec4cca54
@ -164,9 +164,14 @@ def main():
|
|||||||
print(json.dumps(pred, ensure_ascii=False))
|
print(json.dumps(pred, ensure_ascii=False))
|
||||||
|
|
||||||
def predict_boxes(root: Path, image_path: Path):
|
def predict_boxes(root: Path, image_path: Path):
|
||||||
script = Path(__file__).parent / "predict_detector_model.py"
|
candidates = [
|
||||||
|
Path(__file__).parent / "predict_detector_model.py",
|
||||||
|
Path.cwd() / "backend" / "ml" / "predict_detector_model.py",
|
||||||
|
Path.cwd() / "ml" / "predict_detector_model.py",
|
||||||
|
]
|
||||||
|
|
||||||
if not script.exists():
|
script = next((p for p in candidates if p.exists()), None)
|
||||||
|
if script is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -181,7 +186,7 @@ def predict_boxes(root: Path, image_path: Path):
|
|||||||
],
|
],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
timeout=20,
|
timeout=60,
|
||||||
)
|
)
|
||||||
|
|
||||||
if proc.returncode != 0:
|
if proc.returncode != 0:
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
"useMyFreeCamsWatcher": true,
|
"useMyFreeCamsWatcher": true,
|
||||||
"autoDeleteSmallDownloads": true,
|
"autoDeleteSmallDownloads": true,
|
||||||
"autoDeleteSmallDownloadsBelowMB": 300,
|
"autoDeleteSmallDownloadsBelowMB": 300,
|
||||||
"autoDeleteSmallDownloadsKeepFavorites": true,
|
"autoDeleteSmallDownloadsKeepFavorites": false,
|
||||||
"lowDiskPauseBelowGB": 5,
|
"lowDiskPauseBelowGB": 5,
|
||||||
"blurPreviews": false,
|
"blurPreviews": false,
|
||||||
"teaserPlayback": "all",
|
"teaserPlayback": "all",
|
||||||
|
|||||||
@ -255,6 +255,11 @@ func trainingLatestOpenSample(root string) (*TrainingSample, bool, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wichtig: Prediction aktualisieren, damit alte "model_missing"-Samples
|
||||||
|
// nach einem Training nicht stale bleiben.
|
||||||
|
sample.Prediction = trainingPredictFrame(framePath)
|
||||||
|
_ = trainingWriteSample(root, sample)
|
||||||
|
|
||||||
return sample, true, nil
|
return sample, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -874,6 +879,7 @@ func trainingExtractFrame(videoPath string, framePath string, second float64) er
|
|||||||
func trainingPredictFrame(framePath string) TrainingPrediction {
|
func trainingPredictFrame(framePath string) TrainingPrediction {
|
||||||
root, err := trainingRootDir()
|
root, err := trainingRootDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
fmt.Println("⚠️ training predict root error:", err)
|
||||||
return trainingEmptyPrediction("root_error")
|
return trainingEmptyPrediction("root_error")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -893,14 +899,16 @@ func trainingPredictFrame(framePath string) TrainingPrediction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
|
outText := strings.TrimSpace(string(out))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("⚠️ training predict failed:", err, strings.TrimSpace(string(out)))
|
fmt.Println("⚠️ training predict failed:", err, outText)
|
||||||
return trainingEmptyPrediction("predict_failed")
|
return trainingEmptyPrediction("predict_failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
var pred TrainingPrediction
|
var pred TrainingPrediction
|
||||||
if err := json.Unmarshal(out, &pred); err != nil {
|
if err := json.Unmarshal(out, &pred); err != nil {
|
||||||
fmt.Println("⚠️ training predict json failed:", err, strings.TrimSpace(string(out)))
|
fmt.Println("⚠️ training predict json failed:", err, outText)
|
||||||
return trainingEmptyPrediction("predict_json_failed")
|
return trainingEmptyPrediction("predict_json_failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1079,7 +1087,15 @@ func trainingScriptPath(name string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) Dev-Fallback: Projektstruktur.
|
// 2) App-/Backend-relativ wie record_paths.go.
|
||||||
|
if backendRoot, err := trainingBackendRootDir(); err == nil {
|
||||||
|
p := filepath.Join(backendRoot, "ml", name)
|
||||||
|
if _, err := os.Stat(p); err == nil {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Dev-Fallback.
|
||||||
root := trainingProjectRoot()
|
root := trainingProjectRoot()
|
||||||
|
|
||||||
p := filepath.Join(root, "backend", "ml", name)
|
p := filepath.Join(root, "backend", "ml", name)
|
||||||
@ -1095,8 +1111,97 @@ func trainingScriptPath(name string) string {
|
|||||||
return filepath.Join(root, "backend", "ml", name)
|
return filepath.Join(root, "backend", "ml", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isTempBuildDir(dir string) bool {
|
||||||
|
low := strings.ToLower(filepath.Clean(dir))
|
||||||
|
|
||||||
|
return strings.Contains(low, `\appdata\local\temp`) ||
|
||||||
|
strings.Contains(low, `\temp\`) ||
|
||||||
|
strings.Contains(low, `\tmp\`) ||
|
||||||
|
strings.Contains(low, `\go-build`) ||
|
||||||
|
strings.Contains(low, `/tmp/`) ||
|
||||||
|
strings.Contains(low, `/go-build`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func trainingBackendRootDir() (string, error) {
|
||||||
|
// Fall 1:
|
||||||
|
// App läuft aus /backend oder EXE liegt neben /ml.
|
||||||
|
// Dann ist "ml/predict_scene_model.py" app-relativ korrekt.
|
||||||
|
if script, err := resolvePathRelativeToApp(filepath.Join("ml", "predict_scene_model.py")); err == nil {
|
||||||
|
if st, statErr := os.Stat(script); statErr == nil && !st.IsDir() {
|
||||||
|
return filepath.Dir(filepath.Dir(script)), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall 2:
|
||||||
|
// Dev-Start aus Projekt-Root.
|
||||||
|
// Dann ist "backend/ml/predict_scene_model.py" app-relativ korrekt.
|
||||||
|
if script, err := resolvePathRelativeToApp(filepath.Join("backend", "ml", "predict_scene_model.py")); err == nil {
|
||||||
|
if st, statErr := os.Stat(script); statErr == nil && !st.IsDir() {
|
||||||
|
return filepath.Dir(filepath.Dir(script)), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall 3:
|
||||||
|
// Gebaute App mit embedded ML-Scripts.
|
||||||
|
// Dann gibt es ggf. kein /ml neben der EXE.
|
||||||
|
// In diesem Fall nehmen wir den EXE-Ordner als App/Backend-Root,
|
||||||
|
// solange es nicht go-run Temp ist.
|
||||||
|
if dir, err := exeDir(); err == nil && strings.TrimSpace(dir) != "" && !isTempBuildDir(dir) {
|
||||||
|
return dir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall 4:
|
||||||
|
// Dev-Fallback über Working Directory.
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wenn wir direkt in /backend laufen.
|
||||||
|
if _, err := os.Stat(filepath.Join(wd, "ml", "predict_scene_model.py")); err == nil {
|
||||||
|
return wd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wenn wir im Projekt-Root laufen.
|
||||||
|
if _, err := os.Stat(filepath.Join(wd, "backend", "ml", "predict_scene_model.py")); err == nil {
|
||||||
|
return filepath.Join(wd, "backend"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Letzter Fallback: bisherige Projekterkennung.
|
||||||
|
projectRoot := trainingProjectRoot()
|
||||||
|
return filepath.Join(projectRoot, "backend"), nil
|
||||||
|
}
|
||||||
|
|
||||||
func trainingRootDir() (string, error) {
|
func trainingRootDir() (string, error) {
|
||||||
root := filepath.Join("generated", "training")
|
// Optionaler Override, falls du später explizit einen anderen Speicherort willst.
|
||||||
|
// Relative Pfade werden wie in record_paths.go app-relativ aufgelöst.
|
||||||
|
if override := strings.TrimSpace(os.Getenv("TRAINING_ROOT")); override != "" {
|
||||||
|
root, err := resolvePathRelativeToApp(override)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
root, err = filepath.Abs(root)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(root, 0755); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return root, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
backendRoot, err := trainingBackendRootDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
root, err := filepath.Abs(filepath.Join(backendRoot, "generated", "training"))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
if err := os.MkdirAll(root, 0755); err != nil {
|
if err := os.MkdirAll(root, 0755); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|||||||
@ -191,6 +191,44 @@ function predictionToCorrection(sample: TrainingSample | null): CorrectionState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyBoxLabelToCorrection(
|
||||||
|
state: CorrectionState,
|
||||||
|
label: string,
|
||||||
|
labels: TrainingLabels
|
||||||
|
): CorrectionState {
|
||||||
|
const clean = String(label || '').trim()
|
||||||
|
if (!clean) return state
|
||||||
|
|
||||||
|
if (labels.bodyParts.includes(clean)) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
bodyPartsPresent: state.bodyPartsPresent.includes(clean)
|
||||||
|
? state.bodyPartsPresent
|
||||||
|
: [...state.bodyPartsPresent, clean],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (labels.objects.includes(clean)) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
objectsPresent: state.objectsPresent.includes(clean)
|
||||||
|
? state.objectsPresent
|
||||||
|
: [...state.objectsPresent, clean],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (labels.clothing.includes(clean)) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
clothingPresent: state.clothingPresent.includes(clean)
|
||||||
|
? state.clothingPresent
|
||||||
|
: [...state.clothingPresent, clean],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
function sortLabelList(values?: string[], opts?: { keepUnknownFirst?: boolean }) {
|
function sortLabelList(values?: string[], opts?: { keepUnknownFirst?: boolean }) {
|
||||||
const list = [...(values ?? [])].sort((a, b) =>
|
const list = [...(values ?? [])].sort((a, b) =>
|
||||||
a.localeCompare(b, undefined, { sensitivity: 'base' })
|
a.localeCompare(b, undefined, { sensitivity: 'base' })
|
||||||
@ -244,6 +282,26 @@ function TrainingOverlay(props: { step: string; progress: number }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function LoadingImageOverlay(props: { text?: string }) {
|
||||||
|
return (
|
||||||
|
<div className="absolute inset-0 z-20 flex flex-col items-center justify-center bg-black/45 text-center text-white backdrop-blur-[1px]">
|
||||||
|
<LoadingSpinner
|
||||||
|
size="lg"
|
||||||
|
className="text-white"
|
||||||
|
srLabel="Bild wird geladen…"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="mt-3 text-sm font-semibold">
|
||||||
|
Bild wird geladen…
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-1 max-w-[260px] px-4 text-xs text-white/80">
|
||||||
|
{props.text || 'Frame wird erstellt und analysiert. Bitte warten.'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default function TrainingTab() {
|
export default function TrainingTab() {
|
||||||
const [labels, setLabels] = useState<TrainingLabels>(emptyLabels)
|
const [labels, setLabels] = useState<TrainingLabels>(emptyLabels)
|
||||||
const [sample, setSample] = useState<TrainingSample | null>(null)
|
const [sample, setSample] = useState<TrainingSample | null>(null)
|
||||||
@ -257,6 +315,7 @@ export default function TrainingTab() {
|
|||||||
const [trainingStep, setTrainingStep] = useState('')
|
const [trainingStep, setTrainingStep] = useState('')
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
const [message, setMessage] = useState<string | null>(null)
|
const [message, setMessage] = useState<string | null>(null)
|
||||||
|
const wasTrainingRunningRef = useRef(false)
|
||||||
|
|
||||||
const imageBoxRef = useRef<HTMLDivElement | null>(null)
|
const imageBoxRef = useRef<HTMLDivElement | null>(null)
|
||||||
|
|
||||||
@ -375,6 +434,16 @@ export default function TrainingTab() {
|
|||||||
setBoxLabel(boxLabels[0] || '')
|
setBoxLabel(boxLabels[0] || '')
|
||||||
}, [boxLabel, boxLabels])
|
}, [boxLabel, boxLabels])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const wasRunning = wasTrainingRunningRef.current
|
||||||
|
|
||||||
|
if (wasRunning && !trainingRunning && trainingStatus?.training?.finishedAt) {
|
||||||
|
void loadNext({ forceNew: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
wasTrainingRunningRef.current = trainingRunning
|
||||||
|
}, [trainingRunning, trainingStatus?.training?.finishedAt, loadNext])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false
|
let cancelled = false
|
||||||
|
|
||||||
@ -527,6 +596,10 @@ export default function TrainingTab() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await loadTrainingStatus()
|
await loadTrainingStatus()
|
||||||
|
|
||||||
|
// WICHTIG:
|
||||||
|
// Hier NICHT direkt loadNext() aufrufen.
|
||||||
|
// Das Training läuft im Backend asynchron weiter.
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setTraining(false)
|
setTraining(false)
|
||||||
setTrainingProgress(0)
|
setTrainingProgress(0)
|
||||||
@ -636,11 +709,15 @@ export default function TrainingTab() {
|
|||||||
|
|
||||||
if (box.w < 0.01 || box.h < 0.01) return
|
if (box.w < 0.01 || box.h < 0.01) return
|
||||||
|
|
||||||
setCorrection((prev) => ({
|
setCorrection((prev) => {
|
||||||
|
const next: CorrectionState = {
|
||||||
...prev,
|
...prev,
|
||||||
boxes: [...(prev.boxes ?? []), box],
|
boxes: [...(prev.boxes ?? []), box],
|
||||||
}))
|
}
|
||||||
}, [drawingBox])
|
|
||||||
|
return applyBoxLabelToCorrection(next, box.label, labels)
|
||||||
|
})
|
||||||
|
}, [drawingBox, labels])
|
||||||
|
|
||||||
const removeBox = useCallback((index: number) => {
|
const removeBox = useCallback((index: number) => {
|
||||||
setCorrection((prev) => ({
|
setCorrection((prev) => ({
|
||||||
@ -883,7 +960,7 @@ export default function TrainingTab() {
|
|||||||
ref={imageBoxRef}
|
ref={imageBoxRef}
|
||||||
className={[
|
className={[
|
||||||
'relative inline-block max-h-[72dvh] max-w-full select-none transition',
|
'relative inline-block max-h-[72dvh] max-w-full select-none transition',
|
||||||
trainingRunning ? 'pointer-events-none blur-sm' : '',
|
trainingRunning || loading ? 'pointer-events-none blur-sm' : '',
|
||||||
].join(' ')}
|
].join(' ')}
|
||||||
onPointerDown={startDrawBox}
|
onPointerDown={startDrawBox}
|
||||||
onPointerMove={moveDrawBox}
|
onPointerMove={moveDrawBox}
|
||||||
@ -962,6 +1039,8 @@ export default function TrainingTab() {
|
|||||||
step={shownTrainingStep}
|
step={shownTrainingStep}
|
||||||
progress={shownTrainingProgress}
|
progress={shownTrainingProgress}
|
||||||
/>
|
/>
|
||||||
|
) : loading ? (
|
||||||
|
<LoadingImageOverlay />
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
) : trainingRunning ? (
|
) : trainingRunning ? (
|
||||||
@ -970,12 +1049,7 @@ export default function TrainingTab() {
|
|||||||
progress={shownTrainingProgress}
|
progress={shownTrainingProgress}
|
||||||
/>
|
/>
|
||||||
) : loading ? (
|
) : loading ? (
|
||||||
<LoadingSpinner
|
<LoadingImageOverlay />
|
||||||
size="lg"
|
|
||||||
center
|
|
||||||
className="text-white/80"
|
|
||||||
srLabel="Frame wird geladen…"
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<div className="text-sm text-white/80">Kein Frame geladen</div>
|
<div className="text-sm text-white/80">Kein Frame geladen</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user