diff --git a/backend/app/modules/results/service.py b/backend/app/modules/results/service.py index f52ffb6..86637f5 100644 --- a/backend/app/modules/results/service.py +++ b/backend/app/modules/results/service.py @@ -183,7 +183,7 @@ def parse_training_curve(path: Path, max_points: int = 300) -> dict | None: y = _as_float(row.get(name)) if y is not None: points.append({"x": x, "y": y}) - if len(points) >= 2: + if points: values = [point["y"] for point in points] series.append({"name": name, "points": points, "last": values[-1], "min": min(values), "max": max(values)}) if not series: diff --git a/backend/tests/test_results_service.py b/backend/tests/test_results_service.py index 4f5ad7a..56290e3 100644 --- a/backend/tests/test_results_service.py +++ b/backend/tests/test_results_service.py @@ -28,6 +28,26 @@ def test_parse_yolo_results_curve(tmp_path: Path): assert all("lr/" not in name for name in names) +def test_parse_single_epoch_yolo_results_curve(tmp_path: Path): + csv_path = tmp_path / "results.csv" + csv_path.write_text( + "\n".join( + [ + "epoch,time,train/box_loss,train/seg_loss,metrics/mAP50(M),lr/pg0", + "1,1.0,1.5,3.0,0.12,0.001", + ] + ), + encoding="utf-8", + ) + + curve = parse_training_curve(csv_path) + + assert curve is not None + assert curve["row_count"] == 1 + seg_loss = next(item for item in curve["series"] if item["name"] == "train/seg_loss") + assert seg_loss["points"] == [{"x": 1.0, "y": 3.0}] + + def test_results_api_honors_limit(monkeypatch): calls = {} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 034e15f..1e58846 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1764,17 +1764,24 @@ function MiniCurvePlot({ series }: { series: CurveSeries[] }) { {[0.25, 0.5, 0.75].map((tick) => ( ))} - {series.map((item, index) => ( - `${scaleX(point.x).toFixed(2)},${scaleY(point.y).toFixed(2)}`).join(" ")} - fill="none" - stroke={curveColor(index)} - strokeWidth="2.4" - strokeLinejoin="round" - strokeLinecap="round" - /> - ))} + {series.map((item, index) => { + const color = curveColor(index); + const scaled = item.points.map((point) => ({ x: scaleX(point.x), y: scaleY(point.y) })); + if (scaled.length === 1) { + return ; + } + return ( + `${point.x.toFixed(2)},${point.y.toFixed(2)}`).join(" ")} + fill="none" + stroke={color} + strokeWidth="2.4" + strokeLinejoin="round" + strokeLinecap="round" + /> + ); + })} ); }