From 2d5ef7741a4bd6c185f68289c7d95c259398a06f Mon Sep 17 00:00:00 2001 From: Linrador <68631622+Linrador@users.noreply.github.com> Date: Sat, 2 May 2026 10:03:26 +0200 Subject: [PATCH] updated --- backend/ml/detection_labels.json | 1 - backend/ml/predict_detector_model.py | 16 +- backend/training.go | 21 +- frontend/src/App.css | 70 ++ frontend/src/App.tsx | 68 +- frontend/src/components/ui/Icons.tsx | 365 +++++- frontend/src/components/ui/TrainingTab.tsx | 1329 +++++++++++++------- 7 files changed, 1403 insertions(+), 467 deletions(-) diff --git a/backend/ml/detection_labels.json b/backend/ml/detection_labels.json index feeaeb1..f1ddad0 100644 --- a/backend/ml/detection_labels.json +++ b/backend/ml/detection_labels.json @@ -27,7 +27,6 @@ "bodyParts": [ "anus", "ass", - "back", "breasts", "penis", "tongue", diff --git a/backend/ml/predict_detector_model.py b/backend/ml/predict_detector_model.py index 96b9e4e..d1206a6 100644 --- a/backend/ml/predict_detector_model.py +++ b/backend/ml/predict_detector_model.py @@ -79,10 +79,18 @@ def main(): x1, y1, x2, y2 = [float(v) for v in b.xyxy[0].tolist()] - x = clamp01(x1 / img_w) - y = clamp01(y1 / img_h) - w = clamp01((x2 - x1) / img_w) - h = clamp01((y2 - y1) / img_h) + x1 = max(0.0, min(float(img_w), x1)) + y1 = max(0.0, min(float(img_h), y1)) + x2 = max(0.0, min(float(img_w), x2)) + y2 = max(0.0, min(float(img_h), y2)) + + if x2 <= x1 or y2 <= y1: + continue + + x = x1 / img_w + y = y1 / img_h + w = (x2 - x1) / img_w + h = (y2 - y1) / img_h if w <= 0 or h <= 0: continue diff --git a/backend/training.go b/backend/training.go index 73e0bdb..640bac1 100644 --- a/backend/training.go +++ b/backend/training.go @@ -187,8 +187,11 @@ func trainingNextHandler(w http.ResponseWriter, r *http.Request) { return } + refreshPrediction := r.URL.Query().Get("refresh") == "1" || + strings.EqualFold(r.URL.Query().Get("refresh"), "true") + if !forceNew { - if sample, ok, err := trainingLatestOpenSample(root); err != nil { + if sample, ok, err := trainingLatestOpenSample(root, refreshPrediction); err != nil { trainingWriteError(w, http.StatusInternalServerError, err.Error()) return } else if ok { @@ -206,7 +209,7 @@ func trainingNextHandler(w http.ResponseWriter, r *http.Request) { trainingWriteJSON(w, http.StatusOK, sample) } -func trainingLatestOpenSample(root string) (*TrainingSample, bool, error) { +func trainingLatestOpenSample(root string, refreshPrediction bool) (*TrainingSample, bool, error) { answered, err := trainingAnsweredSampleIDs(root) if err != nil { return nil, false, err @@ -272,10 +275,10 @@ func trainingLatestOpenSample(root string) (*TrainingSample, bool, error) { continue } - // Wichtig: Prediction aktualisieren, damit alte "model_missing"-Samples - // nach einem Training nicht stale bleiben. - sample.Prediction = trainingPredictFrame(framePath) - _ = trainingWriteSample(root, sample) + if refreshPrediction { + sample.Prediction = trainingPredictFrame(framePath) + _ = trainingWriteSample(root, sample) + } return sample, true, nil } @@ -1372,6 +1375,10 @@ func trainingPredictionFromDetector(det TrainingDetectorPrediction) TrainingPred visibleBoxes := []TrainingBox{} for _, box := range rawBoxes { + if box.Score > 0 && box.Score < 0.25 { + continue + } + label := strings.TrimSpace(box.Label) if label == "" { continue @@ -1428,7 +1435,7 @@ func trainingPredictDetector(root string, framePath string) TrainingDetectorPred } } - confValues := []string{"0.30", "0.10", "0.03", "0.01"} + confValues := []string{"0.40", "0.30", "0.20"} best := TrainingDetectorPrediction{ Available: true, diff --git a/frontend/src/App.css b/frontend/src/App.css index e69de29..d3b5a4c 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -0,0 +1,70 @@ +.training-beaker-icon > svg { + animation: training-beaker-pulse 1.6s ease-in-out infinite; +} + +.training-beaker-bubble { + position: absolute; + bottom: 34%; + width: 0.22em; + height: 0.22em; + border-radius: 9999px; + background: rgb(255 255 255 / 0.95); + box-shadow: + 0 0 0 1px rgb(0 0 0 / 0.16), + 0 0 8px currentColor; + opacity: 0; + pointer-events: none; + animation: training-beaker-bubble-rise 1.45s ease-in-out infinite; +} + +.training-beaker-bubble-1 { + left: 40%; + animation-delay: 0s; +} + +.training-beaker-bubble-2 { + left: 52%; + width: 0.18em; + height: 0.18em; + animation-delay: 0.35s; +} + +.training-beaker-bubble-3 { + left: 47%; + width: 0.14em; + height: 0.14em; + animation-delay: 0.7s; +} + +@keyframes training-beaker-bubble-rise { + 0% { + transform: translate(-50%, 0) scale(0.45); + opacity: 0; + } + + 18% { + opacity: 0.95; + } + + 72% { + opacity: 0.7; + } + + 100% { + transform: translate(-50%, -0.72em) scale(1); + opacity: 0; + } +} + +@keyframes training-beaker-pulse { + 0%, + 100% { + transform: scale(1); + filter: drop-shadow(0 0 0 rgb(16 185 129 / 0)); + } + + 50% { + transform: scale(1.04); + filter: drop-shadow(0 0 6px rgb(16 185 129 / 0.55)); + } +} \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ca31ab4..a937e60 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -621,8 +621,29 @@ function upsertJobById(list: RecordJob[], incoming: RecordJob): RecordJob[] { }) } +function TrainingBeakerIcon(props: React.ComponentProps) { + const { className } = props + + return ( +