training fix. add global settings
This commit is contained in:
@@ -1,272 +1,272 @@
|
||||
// Render helper descriptions for YOLOX settings and handle form submission
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// Get the form element at the top
|
||||
const form = document.getElementById('settings-form');
|
||||
|
||||
// Base config state
|
||||
let currentBaseConfig = null;
|
||||
let baseConfigFields = [];
|
||||
// Define which fields are protected by base config
|
||||
const protectedFields = [
|
||||
'depth', 'width', 'act', 'max_epoch', 'warmup_epochs', 'warmup_lr',
|
||||
'scheduler', 'no_aug_epochs', 'min_lr_ratio', 'ema', 'weight_decay',
|
||||
'momentum', 'input_size', 'mosaic_scale', 'test_size', 'enable_mixup',
|
||||
'mosaic_prob', 'mixup_prob', 'hsv_prob', 'flip_prob', 'degrees',
|
||||
'translate', 'shear', 'mixup_scale', 'print_interval', 'eval_interval'
|
||||
];
|
||||
|
||||
// Map backend field names to frontend field names
|
||||
const fieldNameMap = {
|
||||
'activation': 'act', // Backend uses 'activation', frontend uses 'act'
|
||||
'nms_thre': 'nmsthre'
|
||||
};
|
||||
|
||||
// Function to load base config for selected model
|
||||
function loadBaseConfig(modelName) {
|
||||
if (!modelName) return Promise.resolve(null);
|
||||
|
||||
return fetch(`/api/base-config/${modelName}`)
|
||||
.then(res => {
|
||||
if (!res.ok) throw new Error('Base config not found');
|
||||
return res.json();
|
||||
})
|
||||
.catch(err => {
|
||||
console.warn(`Could not load base config for ${modelName}:`, err);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
// Function to apply base config to form fields
|
||||
function applyBaseConfig(config, isCocoMode) {
|
||||
const infoBanner = document.getElementById('base-config-info');
|
||||
const modelNameSpan = document.getElementById('base-config-model');
|
||||
|
||||
if (!config || !isCocoMode) {
|
||||
// Hide info banner
|
||||
if (infoBanner) infoBanner.style.display = 'none';
|
||||
|
||||
// Remove grey styling and enable all fields
|
||||
protectedFields.forEach(fieldName => {
|
||||
const input = form.querySelector(`[name="${fieldName}"]`);
|
||||
if (input) {
|
||||
input.disabled = false;
|
||||
input.style.backgroundColor = '#f8f8f8';
|
||||
input.style.color = '#333';
|
||||
input.style.cursor = 'text';
|
||||
input.title = '';
|
||||
}
|
||||
});
|
||||
baseConfigFields = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Show info banner
|
||||
if (infoBanner) {
|
||||
infoBanner.style.display = 'block';
|
||||
const modelName = form.querySelector('[name="select_model"]')?.value || 'selected model';
|
||||
if (modelNameSpan) modelNameSpan.textContent = modelName;
|
||||
}
|
||||
|
||||
// Apply base config values and grey out fields
|
||||
baseConfigFields = [];
|
||||
Object.entries(config).forEach(([key, value]) => {
|
||||
// Map backend field name to frontend field name if needed
|
||||
const frontendFieldName = fieldNameMap[key] || key;
|
||||
|
||||
if (protectedFields.includes(frontendFieldName)) {
|
||||
const input = form.querySelector(`[name="${frontendFieldName}"]`);
|
||||
if (input) {
|
||||
baseConfigFields.push(frontendFieldName);
|
||||
|
||||
// Set value based on type
|
||||
if (input.type === 'checkbox') {
|
||||
input.checked = Boolean(value);
|
||||
} else if (Array.isArray(value)) {
|
||||
input.value = value.join(',');
|
||||
} else {
|
||||
input.value = value;
|
||||
}
|
||||
|
||||
// Grey out and disable
|
||||
input.disabled = true;
|
||||
input.style.backgroundColor = '#d3d3d3';
|
||||
input.style.color = '#666';
|
||||
input.style.cursor = 'not-allowed';
|
||||
|
||||
// Add title tooltip
|
||||
const modelName = form.querySelector('[name="select_model"]')?.value || 'selected model';
|
||||
input.title = `Protected by base config for ${modelName}. Switch to "Train from sketch" to customize.`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`Applied base config. Protected fields: ${baseConfigFields.join(', ')}`);
|
||||
}
|
||||
|
||||
// Function to update form based on transfer learning mode
|
||||
function updateTransferLearningMode() {
|
||||
const transferLearning = document.getElementById('transfer-learning');
|
||||
const selectModel = document.getElementById('select-model');
|
||||
const selectModelRow = document.getElementById('select-model-row');
|
||||
|
||||
if (!transferLearning || !selectModel) return;
|
||||
|
||||
const isCocoMode = transferLearning.value === 'coco';
|
||||
const isCustomMode = transferLearning.value === 'custom';
|
||||
const isSketchMode = transferLearning.value === 'sketch';
|
||||
const modelName = selectModel.value;
|
||||
|
||||
// Show/hide select model based on transfer learning mode
|
||||
if (selectModelRow) {
|
||||
if (isSketchMode) {
|
||||
selectModelRow.style.display = 'none';
|
||||
} else {
|
||||
selectModelRow.style.display = '';
|
||||
}
|
||||
}
|
||||
|
||||
if (isCocoMode && modelName) {
|
||||
// Load and apply base config
|
||||
loadBaseConfig(modelName).then(config => {
|
||||
currentBaseConfig = config;
|
||||
applyBaseConfig(config, true);
|
||||
});
|
||||
} else {
|
||||
// Clear base config
|
||||
currentBaseConfig = null;
|
||||
applyBaseConfig(null, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for changes to transfer learning dropdown
|
||||
const transferLearningSelect = document.getElementById('transfer-learning');
|
||||
if (transferLearningSelect) {
|
||||
transferLearningSelect.addEventListener('change', updateTransferLearningMode);
|
||||
}
|
||||
|
||||
// Listen for changes to model selection
|
||||
const modelSelect = document.getElementById('select-model');
|
||||
if (modelSelect) {
|
||||
modelSelect.addEventListener('change', updateTransferLearningMode);
|
||||
}
|
||||
|
||||
// Initial update on page load
|
||||
setTimeout(updateTransferLearningMode, 100);
|
||||
|
||||
// Auto-set num_classes from training_project classes array
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const projectId = urlParams.get('id');
|
||||
if (projectId && form) {
|
||||
fetch('/api/training-projects')
|
||||
.then(res => res.json())
|
||||
.then(projects => {
|
||||
const project = projects.find(p => p.project_id == projectId || p.id == projectId);
|
||||
if (project && project.classes) {
|
||||
let classesArr = project.classes;
|
||||
// If classes is a stringified JSON, parse it
|
||||
if (typeof classesArr === 'string') {
|
||||
try {
|
||||
classesArr = JSON.parse(classesArr);
|
||||
} catch (e) {
|
||||
classesArr = [];
|
||||
}
|
||||
}
|
||||
let numClasses = 0;
|
||||
if (Array.isArray(classesArr)) {
|
||||
numClasses = classesArr.length;
|
||||
} else if (typeof classesArr === 'object' && classesArr !== null) {
|
||||
numClasses = Object.keys(classesArr).length;
|
||||
}
|
||||
// Fix: Only set num_classes if input exists
|
||||
const numClassesInput = form.querySelector('[name="num_classes"]');
|
||||
if (numClassesInput) {
|
||||
numClassesInput.value = numClasses;
|
||||
numClassesInput.readOnly = true;
|
||||
numClassesInput.dispatchEvent(new Event('input'));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Handle form submission
|
||||
form.addEventListener('submit', function(e) {
|
||||
console.log("Form submitted");
|
||||
e.preventDefault();
|
||||
|
||||
// Temporarily enable disabled fields so they get included in FormData
|
||||
const disabledInputs = [];
|
||||
form.querySelectorAll('input[disabled], select[disabled]').forEach(input => {
|
||||
input.disabled = false;
|
||||
disabledInputs.push(input);
|
||||
});
|
||||
|
||||
const formData = new FormData(form);
|
||||
const settings = {};
|
||||
let fileToUpload = null;
|
||||
|
||||
for (const [key, value] of formData.entries()) {
|
||||
if (key === 'model_upload' && form.elements[key].files.length > 0) {
|
||||
fileToUpload = form.elements[key].files[0];
|
||||
continue;
|
||||
}
|
||||
if (key === 'ema' || key === 'enable_mixup' || key === 'save_history_ckpt') {
|
||||
settings[key] = form.elements[key].checked;
|
||||
} else if (key === 'scale' || key === 'mosaic_scale' || key === 'mixup_scale' || key === 'input_size' || key === 'test_size') {
|
||||
settings[key] = value.split(',').map(v => parseFloat(v.trim()));
|
||||
} else if (!isNaN(value) && value !== '') {
|
||||
settings[key] = parseFloat(value);
|
||||
} else {
|
||||
settings[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Re-disable the inputs
|
||||
disabledInputs.forEach(input => {
|
||||
input.disabled = true;
|
||||
});
|
||||
|
||||
// Attach project id from URL
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const projectId = urlParams.get('id');
|
||||
if (projectId) settings.project_id = Number(projectId);
|
||||
|
||||
// First, send settings JSON (without file)
|
||||
fetch('/api/yolox-settings', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(settings)
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
// If file selected, send it as binary
|
||||
if (fileToUpload) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
fetch(`/api/yolox-settings/upload?project_id=${settings.project_id}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/octet-stream' },
|
||||
body: e.target.result
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data2 => {
|
||||
alert('YOLOX settings and model file saved!');
|
||||
window.location.href = `/overview-training.html?id=${settings.project_id}`;
|
||||
})
|
||||
.catch(err => {
|
||||
alert('Error uploading model file');
|
||||
console.error(err);
|
||||
});
|
||||
};
|
||||
reader.readAsArrayBuffer(fileToUpload);
|
||||
} else {
|
||||
alert('YOLOX settings saved!');
|
||||
window.location.href = `/overview-training.html?id=${settings.project_id}`;
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
alert('Error saving YOLOX settings');
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
// Render helper descriptions for YOLOX settings and handle form submission
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// Get the form element at the top
|
||||
const form = document.getElementById('settings-form');
|
||||
|
||||
// Base config state
|
||||
let currentBaseConfig = null;
|
||||
let baseConfigFields = [];
|
||||
// Define which fields are protected by base config
|
||||
const protectedFields = [
|
||||
'depth', 'width', 'act', 'max_epoch', 'warmup_epochs', 'warmup_lr',
|
||||
'scheduler', 'no_aug_epochs', 'min_lr_ratio', 'ema', 'weight_decay',
|
||||
'momentum', 'input_size', 'mosaic_scale', 'test_size', 'enable_mixup',
|
||||
'mosaic_prob', 'mixup_prob', 'hsv_prob', 'flip_prob', 'degrees',
|
||||
'translate', 'shear', 'mixup_scale', 'print_interval', 'eval_interval'
|
||||
];
|
||||
|
||||
// Map backend field names to frontend field names
|
||||
const fieldNameMap = {
|
||||
'activation': 'act', // Backend uses 'activation', frontend uses 'act'
|
||||
'nms_thre': 'nmsthre'
|
||||
};
|
||||
|
||||
// Function to load base config for selected model
|
||||
function loadBaseConfig(modelName) {
|
||||
if (!modelName) return Promise.resolve(null);
|
||||
|
||||
return fetch(`/api/base-config/${modelName}`)
|
||||
.then(res => {
|
||||
if (!res.ok) throw new Error('Base config not found');
|
||||
return res.json();
|
||||
})
|
||||
.catch(err => {
|
||||
console.warn(`Could not load base config for ${modelName}:`, err);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
// Function to apply base config to form fields
|
||||
function applyBaseConfig(config, isCocoMode) {
|
||||
const infoBanner = document.getElementById('base-config-info');
|
||||
const modelNameSpan = document.getElementById('base-config-model');
|
||||
|
||||
if (!config || !isCocoMode) {
|
||||
// Hide info banner
|
||||
if (infoBanner) infoBanner.style.display = 'none';
|
||||
|
||||
// Remove grey styling and enable all fields
|
||||
protectedFields.forEach(fieldName => {
|
||||
const input = form.querySelector(`[name="${fieldName}"]`);
|
||||
if (input) {
|
||||
input.disabled = false;
|
||||
input.style.backgroundColor = '#f8f8f8';
|
||||
input.style.color = '#333';
|
||||
input.style.cursor = 'text';
|
||||
input.title = '';
|
||||
}
|
||||
});
|
||||
baseConfigFields = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Show info banner
|
||||
if (infoBanner) {
|
||||
infoBanner.style.display = 'block';
|
||||
const modelName = form.querySelector('[name="select_model"]')?.value || 'selected model';
|
||||
if (modelNameSpan) modelNameSpan.textContent = modelName;
|
||||
}
|
||||
|
||||
// Apply base config values and grey out fields
|
||||
baseConfigFields = [];
|
||||
Object.entries(config).forEach(([key, value]) => {
|
||||
// Map backend field name to frontend field name if needed
|
||||
const frontendFieldName = fieldNameMap[key] || key;
|
||||
|
||||
if (protectedFields.includes(frontendFieldName)) {
|
||||
const input = form.querySelector(`[name="${frontendFieldName}"]`);
|
||||
if (input) {
|
||||
baseConfigFields.push(frontendFieldName);
|
||||
|
||||
// Set value based on type
|
||||
if (input.type === 'checkbox') {
|
||||
input.checked = Boolean(value);
|
||||
} else if (Array.isArray(value)) {
|
||||
input.value = value.join(',');
|
||||
} else {
|
||||
input.value = value;
|
||||
}
|
||||
|
||||
// Grey out and disable
|
||||
input.disabled = true;
|
||||
input.style.backgroundColor = '#d3d3d3';
|
||||
input.style.color = '#666';
|
||||
input.style.cursor = 'not-allowed';
|
||||
|
||||
// Add title tooltip
|
||||
const modelName = form.querySelector('[name="select_model"]')?.value || 'selected model';
|
||||
input.title = `Protected by base config for ${modelName}. Switch to "Train from sketch" to customize.`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`Applied base config. Protected fields: ${baseConfigFields.join(', ')}`);
|
||||
}
|
||||
|
||||
// Function to update form based on transfer learning mode
|
||||
function updateTransferLearningMode() {
|
||||
const transferLearning = document.getElementById('transfer-learning');
|
||||
const selectModel = document.getElementById('select-model');
|
||||
const selectModelRow = document.getElementById('select-model-row');
|
||||
|
||||
if (!transferLearning || !selectModel) return;
|
||||
|
||||
const isCocoMode = transferLearning.value === 'coco';
|
||||
const isCustomMode = transferLearning.value === 'custom';
|
||||
const isSketchMode = transferLearning.value === 'sketch';
|
||||
const modelName = selectModel.value;
|
||||
|
||||
// Show/hide select model based on transfer learning mode
|
||||
if (selectModelRow) {
|
||||
if (isSketchMode) {
|
||||
selectModelRow.style.display = 'none';
|
||||
} else {
|
||||
selectModelRow.style.display = '';
|
||||
}
|
||||
}
|
||||
|
||||
if (isCocoMode && modelName) {
|
||||
// Load and apply base config
|
||||
loadBaseConfig(modelName).then(config => {
|
||||
currentBaseConfig = config;
|
||||
applyBaseConfig(config, true);
|
||||
});
|
||||
} else {
|
||||
// Clear base config
|
||||
currentBaseConfig = null;
|
||||
applyBaseConfig(null, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for changes to transfer learning dropdown
|
||||
const transferLearningSelect = document.getElementById('transfer-learning');
|
||||
if (transferLearningSelect) {
|
||||
transferLearningSelect.addEventListener('change', updateTransferLearningMode);
|
||||
}
|
||||
|
||||
// Listen for changes to model selection
|
||||
const modelSelect = document.getElementById('select-model');
|
||||
if (modelSelect) {
|
||||
modelSelect.addEventListener('change', updateTransferLearningMode);
|
||||
}
|
||||
|
||||
// Initial update on page load
|
||||
setTimeout(updateTransferLearningMode, 100);
|
||||
|
||||
// Auto-set num_classes from training_project classes array
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const projectId = urlParams.get('id');
|
||||
if (projectId && form) {
|
||||
fetch('/api/training-projects')
|
||||
.then(res => res.json())
|
||||
.then(projects => {
|
||||
const project = projects.find(p => p.project_id == projectId || p.id == projectId);
|
||||
if (project && project.classes) {
|
||||
let classesArr = project.classes;
|
||||
// If classes is a stringified JSON, parse it
|
||||
if (typeof classesArr === 'string') {
|
||||
try {
|
||||
classesArr = JSON.parse(classesArr);
|
||||
} catch (e) {
|
||||
classesArr = [];
|
||||
}
|
||||
}
|
||||
let numClasses = 0;
|
||||
if (Array.isArray(classesArr)) {
|
||||
numClasses = classesArr.length;
|
||||
} else if (typeof classesArr === 'object' && classesArr !== null) {
|
||||
numClasses = Object.keys(classesArr).length;
|
||||
}
|
||||
// Fix: Only set num_classes if input exists
|
||||
const numClassesInput = form.querySelector('[name="num_classes"]');
|
||||
if (numClassesInput) {
|
||||
numClassesInput.value = numClasses;
|
||||
numClassesInput.readOnly = true;
|
||||
numClassesInput.dispatchEvent(new Event('input'));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Handle form submission
|
||||
form.addEventListener('submit', function(e) {
|
||||
console.log("Form submitted");
|
||||
e.preventDefault();
|
||||
|
||||
// Temporarily enable disabled fields so they get included in FormData
|
||||
const disabledInputs = [];
|
||||
form.querySelectorAll('input[disabled], select[disabled]').forEach(input => {
|
||||
input.disabled = false;
|
||||
disabledInputs.push(input);
|
||||
});
|
||||
|
||||
const formData = new FormData(form);
|
||||
const settings = {};
|
||||
let fileToUpload = null;
|
||||
|
||||
for (const [key, value] of formData.entries()) {
|
||||
if (key === 'model_upload' && form.elements[key].files.length > 0) {
|
||||
fileToUpload = form.elements[key].files[0];
|
||||
continue;
|
||||
}
|
||||
if (key === 'ema' || key === 'enable_mixup' || key === 'save_history_ckpt') {
|
||||
settings[key] = form.elements[key].checked;
|
||||
} else if (key === 'scale' || key === 'mosaic_scale' || key === 'mixup_scale' || key === 'input_size' || key === 'test_size') {
|
||||
settings[key] = value.split(',').map(v => parseFloat(v.trim()));
|
||||
} else if (!isNaN(value) && value !== '') {
|
||||
settings[key] = parseFloat(value);
|
||||
} else {
|
||||
settings[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Re-disable the inputs
|
||||
disabledInputs.forEach(input => {
|
||||
input.disabled = true;
|
||||
});
|
||||
|
||||
// Attach project id from URL
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const projectId = urlParams.get('id');
|
||||
if (projectId) settings.project_id = Number(projectId);
|
||||
|
||||
// First, send settings JSON (without file)
|
||||
fetch('/api/yolox-settings', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(settings)
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
// If file selected, send it as binary
|
||||
if (fileToUpload) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
fetch(`/api/yolox-settings/upload?project_id=${settings.project_id}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/octet-stream' },
|
||||
body: e.target.result
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data2 => {
|
||||
alert('YOLOX settings and model file saved!');
|
||||
window.location.href = `/overview-training.html?id=${settings.project_id}`;
|
||||
})
|
||||
.catch(err => {
|
||||
alert('Error uploading model file');
|
||||
console.error(err);
|
||||
});
|
||||
};
|
||||
reader.readAsArrayBuffer(fileToUpload);
|
||||
} else {
|
||||
alert('YOLOX settings saved!');
|
||||
window.location.href = `/overview-training.html?id=${settings.project_id}`;
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
alert('Error saving YOLOX settings');
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user