Skip to content

Commit 07f850a

Browse files
committed
fixed test image output issue
1 parent 3900d04 commit 07f850a

1 file changed

Lines changed: 73 additions & 28 deletions

File tree

docs/script.js

Lines changed: 73 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,21 @@ class TrainingDashboard {
77
this.metricsGrid = document.querySelector('.metrics-grid');
88
this.lossChart = document.getElementById('lossChart');
99
this.accChart = document.getElementById('accuracyChart');
10+
this.testChart = document.getElementById('testAccuracyChart');
1011
this.predictionsGrid = document.getElementById('predictionsGrid');
1112
this.imagesGrid = document.getElementById('imagesGrid');
1213
this.statusBadge = document.getElementById('trainingStatus');
14+
this.lastUpdateElem = document.getElementById('lastUpdate');
1315
this.state = {
1416
testAccuracy: null,
1517
testLoss: null,
1618
totalEpochs: null,
1719
finalTrainAccuracy: null,
1820
};
21+
this.predictionData = [];
22+
this.maxSampleImages = 5;
23+
this.refreshTimer = null;
24+
this.refreshIntervalMs = 300000; // 5 minutes
1925
this.init();
2026
}
2127

@@ -64,9 +70,11 @@ class TrainingDashboard {
6470
this.statusBadge.textContent = dataLoaded ? 'Data Loaded' : 'No Data';
6571
this.statusBadge.classList.toggle('error', !dataLoaded);
6672
}
73+
this.updateLastUpdateTime();
6774
} catch (err) {
6875
console.error('Init error:', err);
6976
this.failBadge('Data Load Error');
77+
this.setLastUpdateFallback();
7078
}
7179
// Always hide preloader after 3 seconds, regardless of data loading
7280
setTimeout(() => {
@@ -106,15 +114,20 @@ class TrainingDashboard {
106114

107115
// predictions table
108116
const lines = md.split('\n');
109-
const predRows = lines.filter(line => /^\|\s*\d+\s*\|\s*\d+\s*\|\s*\d+\s*\|/i.test(line)).slice(0, 8);
110-
const preds = predRows.map(r => {
111-
const cells = r.split('|').map(c => c.trim()).filter(Boolean);
112-
const idx = parseInt(cells[0], 10);
113-
const t = parseInt(cells[1], 10);
114-
const p = parseInt(cells[2], 10);
115-
return { index: idx, true_label: t, predicted_label: p, correct: t === p };
117+
const preds = [];
118+
lines.forEach(line => {
119+
const match = line.match(/^\|\s*(\d+)\s*\|\s*(\d+)\s*\|\s*(\d+)\s*\|/);
120+
if (match) {
121+
const idx = parseInt(match[1], 10);
122+
const t = parseInt(match[2], 10);
123+
const p = parseInt(match[3], 10);
124+
preds.push({ index: idx, true_label: t, predicted_label: p, correct: t === p });
125+
}
116126
});
117-
this.renderPredictions(preds);
127+
if (preds.length) {
128+
this.predictionData = preds;
129+
this.renderPredictions(preds);
130+
}
118131
}
119132

120133
renderMetrics() {
@@ -154,19 +167,10 @@ class TrainingDashboard {
154167
};
155168
const lossSrc = outputs?.train_loss_image || 'images/train_loss.png';
156169
const accSrc = outputs?.train_accuracy_image || 'images/train_accuracy.png';
170+
const testSrc = outputs?.test_accuracy_image || 'images/test_accuracy.png';
157171
ensureImg(this.lossChart, lossSrc, 'Training Loss');
158172
ensureImg(this.accChart, accSrc, 'Training Accuracy');
159-
// Optional: render test accuracy image above images grid
160-
const testAccImg = document.createElement('img');
161-
testAccImg.src = outputs?.test_accuracy_image || 'images/train_accuracy.png';
162-
testAccImg.alt = 'Test Accuracy';
163-
testAccImg.className = 'chart-image';
164-
testAccImg.style.maxWidth = '320px';
165-
testAccImg.onerror = () => { testAccImg.remove(); };
166-
const section = document.querySelector('.sample-images h2');
167-
if (section && section.parentElement) {
168-
section.parentElement.insertBefore(testAccImg, section.nextSibling);
169-
}
173+
ensureImg(this.testChart, testSrc, 'Test Accuracy');
170174
}
171175

172176
parseKV(text) {
@@ -201,7 +205,10 @@ class TrainingDashboard {
201205
preds.push({ index: i, true_label: ti, predicted_label: pi, correct: ti === pi });
202206
}
203207
}
204-
if (preds.length) this.renderPredictions(preds);
208+
if (preds.length) {
209+
this.predictionData = preds;
210+
this.renderPredictions(preds);
211+
}
205212

206213
// Render sample images directly if provided
207214
const grid = this.imagesGrid;
@@ -220,17 +227,24 @@ class TrainingDashboard {
220227
renderPredictions(preds) {
221228
const grid = this.predictionsGrid;
222229
if (!grid) return;
230+
const data = preds && preds.length ? preds : this.predictionData;
223231
grid.innerHTML = '';
224-
if (!preds || preds.length === 0) {
232+
if (!data || data.length === 0) {
225233
grid.innerHTML = '<div class="no-data">No prediction data available</div>';
226234
return;
227235
}
228-
preds.forEach(pred => {
236+
data.slice(0, 8).forEach(pred => {
229237
const div = document.createElement('div');
230238
div.className = `prediction-item ${pred.correct ? 'correct' : 'incorrect'}`;
231239
div.innerHTML = `
232-
<div class="prediction-digit">${pred.predicted_label}</div>
233-
<div class="prediction-labels">True: ${pred.true_label}<br>${pred.correct ? 'Correct' : 'Wrong'}</div>
240+
<div class="prediction-header">
241+
<span class="prediction-id">#${pred.index}</span>
242+
<span class="prediction-status">${pred.correct ? 'Correct' : 'Wrong'}</span>
243+
</div>
244+
<div class="prediction-body">
245+
<span class="prediction-true">True: ${pred.true_label}</span>
246+
<span class="prediction-pred">Pred: ${pred.predicted_label}</span>
247+
</div>
234248
`;
235249
grid.appendChild(div);
236250
});
@@ -245,23 +259,54 @@ class TrainingDashboard {
245259
grid.innerHTML = '<div class="no-data">No images listed</div>';
246260
return;
247261
}
248-
imgLines.forEach(line => {
249-
const match = line.match(/!\[[^\]]*\]\((images\/[^)]+)\).*True:\s*(\d)[^\d]+Pred:\s*(\d)/i);
262+
imgLines.slice(0, this.maxSampleImages).forEach(line => {
263+
const match = line.match(/!\[[^\]]*\]\((images\/[^)]+)\).*True:\s*(\d+)\D+Pred:\s*(\d+)/i);
250264
const src = match ? match[1] : null;
251265
const t = match ? parseInt(match[2], 10) : null;
252266
const p = match ? parseInt(match[3], 10) : null;
267+
const trueLabel = Number.isInteger(t) ? t : '—';
268+
const predLabel = Number.isInteger(p) ? p : '—';
253269
const card = document.createElement('div');
254270
card.className = 'image-card';
255271
card.innerHTML = src ? `
256272
<img src="${src}" alt="sample" onerror="this.replaceWith(document.createTextNode('Image missing'))">
257-
<div class="image-caption">True: ${t} · Pred: ${p}</div>
273+
<div class="image-caption">True: ${trueLabel} · Pred: ${predLabel}</div>
258274
` : '<div class="image-caption">Image reference invalid</div>';
259275
grid.appendChild(card);
260276
});
261277
}
262278

263279
startAutoRefresh() {
264-
setInterval(() => this.init(), 300000); // 5 minutes
280+
if (this.refreshTimer) return;
281+
this.refreshTimer = setInterval(() => this.init(), this.refreshIntervalMs);
282+
}
283+
284+
async updateLastUpdateTime() {
285+
if (!this.lastUpdateElem) return;
286+
try {
287+
const resp = await fetch('train_output.md', { method: 'HEAD', cache: 'no-store' });
288+
if (!resp.ok) throw new Error('Failed to fetch headers');
289+
const lastMod = resp.headers.get('last-modified');
290+
if (lastMod) {
291+
const date = new Date(lastMod);
292+
if (!Number.isNaN(date.getTime())) {
293+
this.lastUpdateElem.textContent = date.toLocaleString(undefined, {
294+
dateStyle: 'medium',
295+
timeStyle: 'short',
296+
});
297+
return;
298+
}
299+
}
300+
this.setLastUpdateFallback();
301+
} catch (err) {
302+
console.warn('Last update fetch failed', err);
303+
this.setLastUpdateFallback();
304+
}
305+
}
306+
307+
setLastUpdateFallback() {
308+
if (!this.lastUpdateElem) return;
309+
this.lastUpdateElem.textContent = 'Unavailable';
265310
}
266311
}
267312

0 commit comments

Comments
 (0)