fix terminal and runs dir
This commit is contained in:
parent
1a8d1678d2
commit
212d03da67
9 changed files with 35 additions and 163 deletions
|
|
@ -28,6 +28,17 @@ logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(me
|
||||||
logger = logging.getLogger("yolo_webui")
|
logger = logging.getLogger("yolo_webui")
|
||||||
SESSION_NAME_PATTERN = re.compile(r"^[A-Za-z0-9_-]{1,64}$")
|
SESSION_NAME_PATTERN = re.compile(r"^[A-Za-z0-9_-]{1,64}$")
|
||||||
|
|
||||||
|
try:
|
||||||
|
from ultralytics import settings as _ultralytics_settings
|
||||||
|
_workspace_root = Path.cwd().resolve()
|
||||||
|
_ultralytics_settings.update({
|
||||||
|
"runs_dir": str((_workspace_root / "runs").resolve()),
|
||||||
|
"datasets_dir": str((_workspace_root / "datasets").resolve()),
|
||||||
|
"weights_dir": str((_workspace_root / "models").resolve()),
|
||||||
|
})
|
||||||
|
except Exception as _exc:
|
||||||
|
logger.debug(f"Ultralytics settings init skipped: {_exc}")
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class LiveState:
|
class LiveState:
|
||||||
|
|
|
||||||
|
|
@ -364,6 +364,10 @@ class TrainingConfig:
|
||||||
|
|
||||||
def train_kwargs(self) -> dict[str, str | int | float | bool]:
|
def train_kwargs(self) -> dict[str, str | int | float | bool]:
|
||||||
"""Convert the form values to arguments accepted by YOLO.train()."""
|
"""Convert the form values to arguments accepted by YOLO.train()."""
|
||||||
|
project_path = Path(self.project.strip() or "runs/train")
|
||||||
|
if not project_path.is_absolute():
|
||||||
|
project_path = (Path.cwd() / project_path).resolve()
|
||||||
|
|
||||||
values: dict[str, str | int | float | bool] = {
|
values: dict[str, str | int | float | bool] = {
|
||||||
"data": self.dataset.strip(),
|
"data": self.dataset.strip(),
|
||||||
"epochs": self.epochs,
|
"epochs": self.epochs,
|
||||||
|
|
@ -371,7 +375,7 @@ class TrainingConfig:
|
||||||
"batch": self.batch_size,
|
"batch": self.batch_size,
|
||||||
"workers": self.workers,
|
"workers": self.workers,
|
||||||
"patience": self.patience,
|
"patience": self.patience,
|
||||||
"project": self.project.strip() or "runs/train",
|
"project": str(project_path),
|
||||||
# Enable verbose output so users see active progress and losses in the log console.
|
# Enable verbose output so users see active progress and losses in the log console.
|
||||||
"verbose": True,
|
"verbose": True,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -125,10 +125,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
const autoscrollCheck = document.getElementById('autoscroll');
|
const autoscrollCheck = document.getElementById('autoscroll');
|
||||||
const clearLogBtn = document.getElementById('clear-log-btn');
|
const clearLogBtn = document.getElementById('clear-log-btn');
|
||||||
|
|
||||||
// Chart
|
|
||||||
const ctx = document.getElementById('metricsChart').getContext('2d');
|
|
||||||
let metricsChart = null;
|
|
||||||
|
|
||||||
// State variables
|
// State variables
|
||||||
let trainingTimer = null;
|
let trainingTimer = null;
|
||||||
let secondsElapsed = 0;
|
let secondsElapsed = 0;
|
||||||
|
|
@ -268,78 +264,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
logContainer.innerHTML = '';
|
logContainer.innerHTML = '';
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- Chart.js Integration ---
|
|
||||||
function initChart(datasets = []) {
|
|
||||||
if (metricsChart) {
|
|
||||||
metricsChart.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
metricsChart = new Chart(ctx, {
|
|
||||||
type: 'line',
|
|
||||||
data: {
|
|
||||||
labels: [],
|
|
||||||
datasets: datasets
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
responsive: true,
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
title: { display: true, text: 'Эпоха', color: '#a1a1aa' },
|
|
||||||
grid: { color: '#27272a' },
|
|
||||||
ticks: { color: '#a1a1aa' }
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
title: { display: true, text: 'Значение', color: '#a1a1aa' },
|
|
||||||
grid: { color: '#27272a' },
|
|
||||||
ticks: { color: '#a1a1aa' }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
legend: {
|
|
||||||
labels: { color: '#f4f4f5', font: { family: 'Outfit' } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateChart(epoch, metrics) {
|
|
||||||
if (!metricsChart) {
|
|
||||||
initChart();
|
|
||||||
}
|
|
||||||
|
|
||||||
let labelIndex = metricsChart.data.labels.indexOf(epoch);
|
|
||||||
if (labelIndex === -1) {
|
|
||||||
metricsChart.data.labels.push(epoch);
|
|
||||||
labelIndex = metricsChart.data.labels.length - 1;
|
|
||||||
metricsChart.data.datasets.forEach(dataset => dataset.data.push(null));
|
|
||||||
}
|
|
||||||
|
|
||||||
const colors = ['#f97316', '#10b981', '#3b82f6', '#eab308', '#a855f7'];
|
|
||||||
Object.entries(metrics).forEach(([key, value]) => {
|
|
||||||
if (key === 'epoch') return;
|
|
||||||
|
|
||||||
let dataset = metricsChart.data.datasets.find(item => item.label === key);
|
|
||||||
if (!dataset) {
|
|
||||||
const color = colors[metricsChart.data.datasets.length % colors.length];
|
|
||||||
dataset = {
|
|
||||||
label: key,
|
|
||||||
data: Array(metricsChart.data.labels.length).fill(null),
|
|
||||||
borderColor: color,
|
|
||||||
backgroundColor: color + '22',
|
|
||||||
tension: 0.15,
|
|
||||||
fill: false
|
|
||||||
};
|
|
||||||
metricsChart.data.datasets.push(dataset);
|
|
||||||
}
|
|
||||||
|
|
||||||
dataset.data[labelIndex] = value;
|
|
||||||
});
|
|
||||||
|
|
||||||
metricsChart.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Timer UI ---
|
// --- Timer UI ---
|
||||||
function startTimer() {
|
function startTimer() {
|
||||||
stopTimer();
|
stopTimer();
|
||||||
|
|
@ -427,14 +351,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
addLogLine(msg, level);
|
addLogLine(msg, level);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Draw initial chart points
|
|
||||||
initChart();
|
|
||||||
if (Array.isArray(data.metrics) && data.metrics.length > 0) {
|
|
||||||
data.metrics.forEach(m => {
|
|
||||||
updateChart(m.epoch, m);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sync progress
|
// Sync progress
|
||||||
if (data.status === 'training' || data.status === 'stopping') {
|
if (data.status === 'training' || data.status === 'stopping') {
|
||||||
updateProgress(data.epoch, data.total_epochs);
|
updateProgress(data.epoch, data.total_epochs);
|
||||||
|
|
@ -468,9 +384,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
}
|
}
|
||||||
} else if (data.type === 'progress') {
|
} else if (data.type === 'progress') {
|
||||||
updateProgress(data.epoch, data.total_epochs, data.message);
|
updateProgress(data.epoch, data.total_epochs, data.message);
|
||||||
if (data.metrics) {
|
|
||||||
updateChart(data.epoch, data.metrics);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -496,7 +409,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
startBtn.disabled = true;
|
startBtn.disabled = true;
|
||||||
stopBtn.disabled = false;
|
stopBtn.disabled = false;
|
||||||
startTimer();
|
startTimer();
|
||||||
initChart();
|
|
||||||
break;
|
break;
|
||||||
case 'training':
|
case 'training':
|
||||||
statusTitle.textContent = 'ОБУЧЕНИЕ';
|
statusTitle.textContent = 'ОБУЧЕНИЕ';
|
||||||
|
|
@ -1337,6 +1249,5 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
loadSessionsList();
|
loadSessionsList();
|
||||||
connectWebSocket();
|
connectWebSocket();
|
||||||
connectExportWebSocket();
|
connectExportWebSocket();
|
||||||
initChart();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,6 @@
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||||
<!-- Chart.js -->
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
||||||
<link rel="stylesheet" href="/static/style.css">
|
<link rel="stylesheet" href="/static/style.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
@ -377,17 +375,6 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Live Chart -->
|
|
||||||
<div class="chart-container-card">
|
|
||||||
<div class="chart-header">
|
|
||||||
<h3>График обучения (Live)</h3>
|
|
||||||
<div class="chart-legend" id="chart-legend"></div>
|
|
||||||
</div>
|
|
||||||
<div class="chart-canvas-wrapper">
|
|
||||||
<canvas id="metricsChart"></canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Logs Box -->
|
<!-- Logs Box -->
|
||||||
<div class="log-card">
|
<div class="log-card">
|
||||||
<div class="log-header">
|
<div class="log-header">
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,7 @@ body {
|
||||||
|
|
||||||
.workspace-view {
|
.workspace-view {
|
||||||
display: none;
|
display: none;
|
||||||
grid-template-columns: 46% 1fr;
|
grid-template-columns: 380px 1fr;
|
||||||
gap: 1.5rem;
|
gap: 1.5rem;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
@ -754,40 +754,6 @@ body {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Charts Card */
|
|
||||||
.chart-container-card {
|
|
||||||
background-color: var(--bg-secondary);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 1rem;
|
|
||||||
height: 320px;
|
|
||||||
min-height: 320px;
|
|
||||||
flex: none;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chart-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chart-header h3 {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
font-weight: 700;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
color: var(--text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.chart-canvas-wrapper {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 240px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Console Logs Box */
|
/* Console Logs Box */
|
||||||
.log-card {
|
.log-card {
|
||||||
background-color: #0d0d0f;
|
background-color: #0d0d0f;
|
||||||
|
|
@ -795,9 +761,8 @@ body {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 280px;
|
flex: 1;
|
||||||
min-height: 280px;
|
min-height: 480px;
|
||||||
flex: none;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -845,6 +810,7 @@ body {
|
||||||
|
|
||||||
.log-body {
|
.log-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
overflow-x: auto;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 0.75rem 1rem;
|
padding: 0.75rem 1rem;
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
|
|
@ -857,8 +823,8 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.log-line {
|
.log-line {
|
||||||
white-space: pre-wrap;
|
white-space: pre;
|
||||||
word-break: break-all;
|
word-break: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.log-level-info { color: var(--text-muted); }
|
.log-level-info { color: var(--text-muted); }
|
||||||
|
|
|
||||||
|
|
@ -191,6 +191,16 @@ class TrainingRunner:
|
||||||
from ultralytics import YOLO, settings
|
from ultralytics import YOLO, settings
|
||||||
from .ultralytics_trainers import trainer_for_task
|
from .ultralytics_trainers import trainer_for_task
|
||||||
|
|
||||||
|
workspace_root = Path.cwd().resolve()
|
||||||
|
try:
|
||||||
|
settings.update({
|
||||||
|
"runs_dir": str((workspace_root / "runs").resolve()),
|
||||||
|
"datasets_dir": str((workspace_root / "datasets").resolve()),
|
||||||
|
"weights_dir": str((workspace_root / "models").resolve()),
|
||||||
|
})
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
previous_mlflow_setting = settings["mlflow"]
|
previous_mlflow_setting = settings["mlflow"]
|
||||||
setting_changed = previous_mlflow_setting is not config.mlflow.enabled
|
setting_changed = previous_mlflow_setting is not config.mlflow.enabled
|
||||||
if setting_changed:
|
if setting_changed:
|
||||||
|
|
|
||||||
|
|
@ -96,19 +96,7 @@ global.clearTimeout = id => {
|
||||||
if (timer) timer.cancelled = true;
|
if (timer) timer.cancelled = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
class FakeChart {
|
|
||||||
static instances = [];
|
|
||||||
|
|
||||||
constructor(_context, config) {
|
|
||||||
this.data = config.data;
|
|
||||||
this.options = config.options;
|
|
||||||
FakeChart.instances.push(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {}
|
|
||||||
update() {}
|
|
||||||
}
|
|
||||||
global.Chart = FakeChart;
|
|
||||||
|
|
||||||
class FakeWebSocket {
|
class FakeWebSocket {
|
||||||
static instances = [];
|
static instances = [];
|
||||||
|
|
@ -236,12 +224,6 @@ function runTimer(timer) {
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const chart = FakeChart.instances.at(-1);
|
|
||||||
assert.deepEqual(chart.data.labels, [1, 2]);
|
|
||||||
assert.deepEqual(chart.data.datasets.map(item => item.label), ['mAP50', 'loss']);
|
|
||||||
assert.deepEqual(chart.data.datasets[0].data, [0.5, null]);
|
|
||||||
assert.deepEqual(chart.data.datasets[1].data, [null, 0.2]);
|
|
||||||
|
|
||||||
socket.onclose();
|
socket.onclose();
|
||||||
socket.onclose();
|
socket.onclose();
|
||||||
let activeReconnectTimers = scheduledTimers.filter(
|
let activeReconnectTimers = scheduledTimers.filter(
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ def test_train_kwargs_omit_optional_empty_values() -> None:
|
||||||
augmentation=AugmentationConfig(enabled=False),
|
augmentation=AugmentationConfig(enabled=False),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
assert config.train_kwargs() == {
|
assert config.train_kwargs() == {
|
||||||
"data": "dataset.yaml",
|
"data": "dataset.yaml",
|
||||||
"epochs": 100,
|
"epochs": 100,
|
||||||
|
|
@ -22,7 +23,7 @@ def test_train_kwargs_omit_optional_empty_values() -> None:
|
||||||
"batch": 16,
|
"batch": 16,
|
||||||
"workers": 8,
|
"workers": 8,
|
||||||
"patience": 100,
|
"patience": 100,
|
||||||
"project": "runs/train",
|
"project": str(Path("runs/train").resolve()),
|
||||||
"verbose": True,
|
"verbose": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ def test_runner_wires_yolo_callbacks_and_returns_output(
|
||||||
|
|
||||||
assert output == tmp_path / "run"
|
assert output == tmp_path / "run"
|
||||||
assert constructed == [("models/model.pt", "pose")]
|
assert constructed == [("models/model.pt", "pose")]
|
||||||
assert settings_updates == [{"mlflow": False}, {"mlflow": True}]
|
assert [u for u in settings_updates if "mlflow" in u] == [{"mlflow": False}, {"mlflow": True}]
|
||||||
assert train_arguments[0]["data"] == "dataset.yaml"
|
assert train_arguments[0]["data"] == "dataset.yaml"
|
||||||
assert train_arguments[0]["verbose"] is True
|
assert train_arguments[0]["verbose"] is True
|
||||||
assert train_arguments[0]["trainer"] is FakeTrainer
|
assert train_arguments[0]["trainer"] is FakeTrainer
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue