/** * 统一的配置模型管理类 * 用一个 model 贯穿整个配置流程,只更新 value 不改变结构 */ import { getParameterLabel } from './parameterMapping' export class ConfigModel { constructor() { this.model = null this.baseConfig = null } /** * 初始化配置模型(从 base_config.json 创建) * @param {Object} baseConfig - 基础配置对象 */ async initialize(baseConfig) { if (!baseConfig) { throw new Error('基础配置不能为空') } // 保存原始基础配置 this.baseConfig = JSON.parse(JSON.stringify(baseConfig)) // 创建工作模型(深拷贝) this.model = JSON.parse(JSON.stringify(baseConfig)) console.log('[ConfigModel] 配置模型初始化完成') return this.model } /** * 根据模型类型更新配置值(只更新 value,不改变结构) * @param {string} modelType - 模型类型(如 'MSDN', 'DINO' 等) * @param {Object} modelConfig - 模型特定配置 */ updateWithModelConfig(modelType, modelConfig) { if (!this.model) { throw new Error('配置模型未初始化') } if (!modelConfig) { console.warn('[ConfigModel] 模型配置为空,跳过更新') return this.model } console.log(`[ConfigModel] 应用 ${modelType} 模型配置`) // 遍历模型配置,只更新已存在的键的值 this._updateValues(this.model, modelConfig) // 特殊处理:确保模型特定的配置节点存在且更新 if (modelConfig[modelType] && typeof modelConfig[modelType] === 'object') { if (!this.model[modelType]) { // 如果基础配置中没有该模型的配置节点,创建它 this.model[modelType] = {} } // 更新模型特定配置 this._updateValues(this.model[modelType], modelConfig[modelType]) } console.log(`[ConfigModel] ${modelType} 配置更新完成`) return this.model } /** * 更新单个配置值 * @param {string} key - 配置键(支持嵌套,如 'MSDN.backbone_weight') * @param {any} value - 新值 */ updateValue(key, value) { if (!this.model) { throw new Error('配置模型未初始化') } const keys = key.split('.') let target = this.model // 导航到目标位置 for (let i = 0; i < keys.length - 1; i++) { const k = keys[i] if (!target[k]) { // 如果路径不存在,创建对象 target[k] = {} } target = target[k] } // 设置值 const lastKey = keys[keys.length - 1] target[lastKey] = value console.log(`[ConfigModel] 更新配置: ${key} = ${value}`) return this.model } /** * 批量更新配置值 * @param {Object} updates - 要更新的键值对 */ updateValues(updates) { if (!this.model) { throw new Error('配置模型未初始化') } Object.entries(updates).forEach(([key, value]) => { this.updateValue(key, value) }) return this.model } /** * 从用户界面参数更新配置 * @param {Object} uiParams - UI参数对象 */ updateFromUIParams(uiParams) { if (!this.model) { throw new Error('配置模型未初始化') } const { trainingConfig, deviceConfig, neuralNetworkConfig, modelParams, fixedModelParams, imageEnhancementConfig, splitModel } = uiParams // 更新训练参数 if (trainingConfig) { this.updateValues({ 'epochs': trainingConfig.epochs, 'lr': trainingConfig.lr, 'batch_size': trainingConfig.batch_size || trainingConfig.batchSize, 'val_interval': trainingConfig.val_interval || trainingConfig.valInterval, 'checkpoint_interval': trainingConfig.checkpoint_interval || trainingConfig.checkpointInterval, 'random_seed': trainingConfig.random_seed || trainingConfig.randomSeed, 'optimizer_name': trainingConfig.optimizerName, 'loss_name': trainingConfig.lossName }) } // 更新设备配置 if (deviceConfig) { this.updateValues({ 'use_gpu': deviceConfig.use_gpu, 'gpu_ids': deviceConfig.gpu_ids, 'num_workers': deviceConfig.num_workers }) } // 更新神经网络配置 if (neuralNetworkConfig) { this.updateValues({ 'input_width': neuralNetworkConfig.input_width, 'input_height': neuralNetworkConfig.input_height, 'crop_width': neuralNetworkConfig.crop_width, 'crop_height': neuralNetworkConfig.crop_height }) } // 更新固定模型参数 if (fixedModelParams) { this.updateValues({ 'resume_checkpoint': fixedModelParams.resume_checkpoint, 'output_dir': fixedModelParams.output_dir, 'checkpoint_path': fixedModelParams.checkpoint_path, 'val_interval': fixedModelParams.val_interval, 'checkpoint_interval': fixedModelParams.checkpoint_interval }) } // 更新动态模型参数 if (modelParams && Array.isArray(modelParams)) { modelParams.forEach(param => { if (param.key && param.value !== undefined) { this.updateValue(param.key, param.value) } }) } // 更新图像增强配置 if (imageEnhancementConfig && Array.isArray(imageEnhancementConfig)) { imageEnhancementConfig.forEach(config => { if (config.key) { // 启用/禁用 this.updateValue(config.key, config.enabled) // 更新概率参数(ratio) if (config.probability !== undefined && config.probability !== null) { const ratioKey = config.key + '_ratio' // UI显示的是百分比(0-100),需要转换为小数(0-1) const ratioValue = parseFloat(config.probability) / 100 this.updateValue(ratioKey, ratioValue) } // 更新范围参数 if (config.range !== undefined && config.range !== null) { if (config.key === 'random_flip') { // 翻转类型直接保存字符串 this.updateValue('random_flip_type', config.range) } else { // 其他范围参数(UI显示的是百分比,需要转换为小数) const rangeKey = config.key + '_range' const rangeValue = parseFloat(config.range) / 100 this.updateValue(rangeKey, rangeValue) } } } }) } // 更新拆分模型 if (splitModel) { this.updateValue('split_id', splitModel) } return this.model } /** * 获取当前配置模型 */ getModel() { if (!this.model) { throw new Error('配置模型未初始化') } return JSON.parse(JSON.stringify(this.model)) } /** * 获取用于保存的配置(清理不必要的字段) * @param {string} currentModelType - 当前模型类型,只保留此模型的特定配置 */ getConfigForSave(currentModelType = null) { if (!this.model) { throw new Error('配置模型未初始化') } const config = JSON.parse(JSON.stringify(this.model)) // 过滤其他模型的配置,只保留当前模型的配置 if (currentModelType) { const modelTypes = ['MSDN', 'SupportLabeler', 'DINO', 'Mask2Former', 'TDGN', 'MGCN'] modelTypes.forEach(modelType => { if (modelType !== currentModelType && config[modelType]) { delete config[modelType] console.log(`[ConfigModel] 已过滤 ${modelType} 模型配置`) } }) } return config } /** * 重置到基础配置 */ reset() { if (!this.baseConfig) { throw new Error('基础配置不存在') } this.model = JSON.parse(JSON.stringify(this.baseConfig)) console.log('[ConfigModel] 配置已重置到基础状态') return this.model } /** * 私有方法:递归更新值(只更新已存在的键) */ _updateValues(target, source) { Object.keys(source).forEach(key => { // 如果源值是对象且目标也有这个键且也是对象,递归更新 if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key]) && target[key] && typeof target[key] === 'object' && !Array.isArray(target[key])) { this._updateValues(target[key], source[key]) } else { // 直接更新值(包括数组) target[key] = source[key] } }) } /** * 获取特定模型的参数(用于UI展示) * @param {string} modelType - 模型类型 */ getModelSpecificParams(modelType) { if (!this.model || !modelType) { return [] } const modelConfig = this.model[modelType] if (!modelConfig || typeof modelConfig !== 'object') { return [] } // 将模型特定配置转换为UI参数数组 return Object.entries(modelConfig).map(([key, value]) => ({ key: `${modelType}.${key}`, label: getParameterLabel(key) || this._formatLabel(key), value: value, type: this._inferType(value) })) } /** * 格式化标签(备用方法,当 parameterMapping.js 中没有映射时使用) */ _formatLabel(key) { return key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()) } /** * 推断值的类型 */ _inferType(value) { if (typeof value === 'boolean') return 'boolean' if (typeof value === 'number') { return Number.isInteger(value) ? 'integer' : 'float' } if (typeof value === 'string') { if (value.includes('path') || value.includes('weight') || value.includes('.pth') || value.includes('.pt')) { return 'path' } } if (Array.isArray(value)) return 'array' return 'string' } } // 单例实例 export const configModel = new ConfigModel()