Pyodide里的Tensorflow目录
可以跑通基本的训练、使用模型过程
This commit is contained in:
@@ -2,5 +2,328 @@ import NavExt from './nav-ext';
|
||||
import * as tf from '@tensorflow/tfjs';
|
||||
import './tensorflow';
|
||||
|
||||
import * as Blockly from 'blockly/core';
|
||||
NavExt.init();
|
||||
window.tf = tf;
|
||||
window.tf = tf;
|
||||
|
||||
let featureExtractor;
|
||||
// featureExtractor = await tf.loadGraphModel("../common/media/tfmodel/model.json");
|
||||
// window.featureExtractor = featureExtractor;
|
||||
|
||||
function closeModal() {
|
||||
document.getElementById('modalOverlay').style.display = 'none';
|
||||
}
|
||||
|
||||
// 从IndexedDB删除单个模型
|
||||
async function deleteModel(modelName) {
|
||||
try {
|
||||
await tf.io.removeModel(`indexeddb://${modelName}`);
|
||||
// 从UI移除
|
||||
const modelItem = document.querySelector(`.model-item[data-model-name="${modelName}"]`);
|
||||
if (modelItem) modelItem.remove();
|
||||
} catch (error) {
|
||||
console.error('删除模型失败:', error);
|
||||
alert('删除模型失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 显示单个模型项
|
||||
function displayModelItem(modelName) {
|
||||
const modelsList = document.getElementById('imported-models');
|
||||
if ([...modelsList.children].some(item => item.dataset.modelName === modelName)) {
|
||||
return;
|
||||
}
|
||||
const modelItem = document.createElement('div');
|
||||
modelItem.className = 'model-item';
|
||||
modelItem.dataset.modelName = modelName;
|
||||
modelItem.innerHTML = `
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; padding: 8px 10px; border-bottom: 1px solid #eee;">
|
||||
<span style="font-size: 1em; color: #333;">${modelName}</span>
|
||||
<button class="delete-model" style="
|
||||
background: #f44336;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 4px 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
transition: background-color 0.2s;
|
||||
">删除</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 绑定删除事件
|
||||
modelItem.querySelector('.delete-model').addEventListener('click', () => {
|
||||
deleteModel(modelName);
|
||||
});
|
||||
|
||||
modelsList.appendChild(modelItem);
|
||||
}
|
||||
|
||||
// 清空所有模型
|
||||
async function clearAllModels() {
|
||||
try {
|
||||
const modelInfos = await tf.io.listModels();
|
||||
const deletePromises = Object.keys(modelInfos)
|
||||
.map(path => path.replace('indexeddb://', ''))
|
||||
.map(modelName => tf.io.removeModel(`indexeddb://${modelName}`));
|
||||
|
||||
await Promise.all(deletePromises);
|
||||
document.getElementById('imported-models').innerHTML = '';
|
||||
} catch (error) {
|
||||
console.error('清空模型失败:', error);
|
||||
alert('清空模型失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 加载并显示所有模型
|
||||
async function loadAndDisplayAllModels() {
|
||||
try {
|
||||
const modelInfos = await tf.io.listModels();
|
||||
document.getElementById('imported-models').innerHTML = '';
|
||||
for (const [path] of Object.entries(modelInfos)) {
|
||||
const modelName = path.replace('indexeddb://', '');
|
||||
displayModelItem(modelName);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载模型列表失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function createModal() {
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'modalOverlay';
|
||||
Object.assign(overlay.style, {
|
||||
display: 'none',
|
||||
position: 'fixed',
|
||||
top: '0',
|
||||
left: '0',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
zIndex: '20011216',
|
||||
pointerEvents: 'auto'
|
||||
});
|
||||
const content = document.createElement('div');
|
||||
Object.assign(content.style, {
|
||||
backgroundColor: 'white',
|
||||
width: '60%',
|
||||
maxHeight: '80%',
|
||||
margin: '12vh auto',
|
||||
padding: '20px 30px',
|
||||
borderRadius: '12px'
|
||||
});
|
||||
content.innerHTML = `
|
||||
<h2 style="margin-bottom: 20px;">选择本地模型</h2>
|
||||
<div style="margin-bottom: 25px; position: relative; min-height: 200px;"> <!-- 新增 min-height 和 position -->
|
||||
<input type="file" id="model-upload" accept=".json,.bin" multiple style="display: none;">
|
||||
<label for="model-upload" style="
|
||||
background: #1216ab;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
">选择模型文件</label>
|
||||
<div style="display: flex; gap: 10px; margin-top: 15px; align-items: center;">
|
||||
<span style="line-height: 36px;">导入模型名称:</span>
|
||||
<input type="text" id="model-name" placeholder="模型名称" value="my-model" style="
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
flex: 1;
|
||||
max-width: 200px;
|
||||
">
|
||||
<button id="model-handle" style="
|
||||
background: #1216ab;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
">保存模型</button>
|
||||
</div>
|
||||
<div style="margin-top: 15px; display: flex; gap: 20px;">
|
||||
<div id="json-status" style="
|
||||
background: #f5f5f5;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
flex: 1;
|
||||
">
|
||||
<span>❌ 模型结构描述文件(model.json)</span>
|
||||
|
||||
<div style="color: #666; font-size: 0.9em; margin-top: 5px;">未选择</div>
|
||||
</div>
|
||||
<div id="weights-status" style="
|
||||
background: #f5f5f5;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
flex: 1;
|
||||
">
|
||||
<span>❌ 权重文件(model.weights.bin)</span>
|
||||
<div style="color: #666; font-size: 0.9em; margin-top: 5px;">0 个已选择</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="output" style="margin-bottom: 15px; min-height: 40px;"></div>
|
||||
<div style="margin-top: 20px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
|
||||
<h3>已导入模型</h3>
|
||||
<div style="display: flex; gap: 10px;">
|
||||
<button id="refresh-models" style="
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
">刷新</button>
|
||||
<button id="clear-models" style="
|
||||
background: #f44336;
|
||||
color: white;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
">清空</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="imported-models" style="
|
||||
background: #f5f5f5;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
height: 200px;
|
||||
overflow-y: auto;
|
||||
">
|
||||
<div style="color: #666; font-style: italic;">加载中...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: flex-end; margin-top: 15px;">
|
||||
<button class="close-btn" style="
|
||||
background: #e0e0e0;
|
||||
border: none;
|
||||
padding: 8px 20px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
">关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
overlay.appendChild(content);
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
content.querySelector('.close-btn').addEventListener('click', closeModal);
|
||||
overlay.addEventListener('click', (e) => {
|
||||
if (e.target === overlay) closeModal();
|
||||
});
|
||||
// 获取DOM元素
|
||||
const modelUpload = document.getElementById('model-upload');
|
||||
const modelHandle = document.getElementById('model-handle');
|
||||
const outputDiv = document.getElementById('output');
|
||||
|
||||
let jsonFile = null;
|
||||
let weightFiles = [];
|
||||
|
||||
modelUpload.addEventListener('change', async (event) => {
|
||||
const files = event.target.files;
|
||||
|
||||
// 获取状态元素
|
||||
const jsonStatus = document.getElementById('json-status');
|
||||
const weightsStatus = document.getElementById('weights-status');
|
||||
|
||||
// 重置状态显示(保持完整文件名描述)
|
||||
jsonStatus.querySelector('span').textContent = '❌ 模型结构描述文件(model.json)';
|
||||
jsonStatus.querySelector('div').textContent = '未选择';
|
||||
weightsStatus.querySelector('span').textContent = '❌ 权重文件(model.weights.bin)';
|
||||
weightsStatus.querySelector('div').textContent = '0 个已选择';
|
||||
|
||||
// 分离 JSON 和权重文件
|
||||
weightFiles = [];
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
if (files[i].name.endsWith('.json')) {
|
||||
jsonFile = files[i];
|
||||
} else {
|
||||
weightFiles.push(files[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!jsonFile) {
|
||||
alert('未找到 model.json 文件');
|
||||
return;
|
||||
}
|
||||
|
||||
outputDiv.innerHTML = '正在处理上传的模型文件...';
|
||||
|
||||
if (jsonFile) {
|
||||
jsonStatus.querySelector('span').textContent = '✅ 模型结构描述文件(model.json)';
|
||||
jsonStatus.querySelector('div').textContent = '已选择';
|
||||
const modelName = jsonFile.name.replace('.json', '');
|
||||
document.getElementById('model-name').value = modelName;
|
||||
}
|
||||
|
||||
if (weightFiles.length > 0) {
|
||||
weightsStatus.querySelector('span').textContent = '✅ 权重文件(model.weights.bin)';
|
||||
weightsStatus.querySelector('div').textContent = `${weightFiles.length} 个已选择`;
|
||||
}
|
||||
});
|
||||
|
||||
modelHandle.addEventListener('click', async () => {
|
||||
try {
|
||||
const modelNameInput = document.getElementById('model-name');
|
||||
const modelName = modelNameInput.value || 'mixly-model';
|
||||
|
||||
const model = await tf.loadLayersModel(
|
||||
tf.io.browserFiles([jsonFile, ...weightFiles])
|
||||
);
|
||||
await model.save(`indexeddb://${modelName}`);
|
||||
loadAndDisplayAllModels();
|
||||
outputDiv.innerHTML = `模型已成功保存为 ${modelName}!`;
|
||||
} catch (error) {
|
||||
outputDiv.innerHTML = `保存模型出错: ${error.message}`;
|
||||
console.error(error);
|
||||
}
|
||||
})
|
||||
|
||||
content.querySelector('#refresh-models').addEventListener('click', loadAndDisplayAllModels);
|
||||
content.querySelector('#clear-models').addEventListener('click', async () => {
|
||||
if (confirm('确定要删除所有模型吗?此操作不可恢复!')) {
|
||||
await clearAllModels();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
createModal();
|
||||
|
||||
await loadAndDisplayAllModels();
|
||||
|
||||
function openModal() {
|
||||
loadAndDisplayAllModels();
|
||||
document.getElementById('modalOverlay').style.display = 'block';
|
||||
}
|
||||
|
||||
const workspace = Blockly.getMainWorkspace();
|
||||
workspace.registerButtonCallback('handleModels', function () {
|
||||
openModal();
|
||||
});
|
||||
|
||||
|
||||
async function prepare_qmyixtxi(imgTensor) {
|
||||
let net = null;
|
||||
|
||||
if (window.featureExtractor) {
|
||||
net = window.featureExtractor;
|
||||
} else {
|
||||
net = await tf.loadGraphModel("../common/media/tfmodel/model.json");
|
||||
window.featureExtractor = net;
|
||||
}
|
||||
const preprocessedImg = imgTensor
|
||||
.resizeBilinear([224, 224])
|
||||
.toFloat()
|
||||
.div(tf.scalar(127.5))
|
||||
.sub(tf.scalar(1))
|
||||
.expandDims(0);
|
||||
|
||||
const features = featureExtractor.predict(preprocessedImg);
|
||||
|
||||
let activation = features;
|
||||
return activation;
|
||||
}
|
||||
window.prepare_qmyixtxi = prepare_qmyixtxi;
|
||||
|
||||
Reference in New Issue
Block a user