// 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); }); }); });