/**
|
* 统一的配置模型管理类
|
* 用一个 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()
|