补充软著核心功能图文素材
- 重拍创建多边形、AI自动推理、AI智能分割的高清截图与分段视频素材。 - 更新软著说明书,补充多边形绘制、传播范围、传播执行、AI点选分割和候选结果的图文说明。 - 清理说明书中的重复图片引用,修复不存在的工作区截图路径。 - 更新功能验证与素材清单,登记新增截图、MP4/WebM 视频和验证说明。 - 增加线上核心功能素材录制脚本,便于后续重新生成软著截图与视频。
This commit is contained in:
189
scripts/capture_core_feature_materials.mjs
Normal file
189
scripts/capture_core_feature_materials.mjs
Normal file
@@ -0,0 +1,189 @@
|
||||
import { chromium } from 'playwright';
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
|
||||
const BASE_URL = process.env.SEG_DEMO_URL || 'https://seg.huijutec.cn/';
|
||||
const USERNAME = process.env.SEG_DEMO_USER || 'admin';
|
||||
const PASSWORD = process.env.SEG_DEMO_PASSWORD || '123456';
|
||||
const OUT_ROOT = process.env.SEG_DEMO_OUT_DIR || path.resolve('新撰写软著文档');
|
||||
const IMAGE_DIR = path.join(OUT_ROOT, 'images');
|
||||
const VIDEO_DIR = path.join(OUT_ROOT, '系统使用视频');
|
||||
const viewport = { width: 1920, height: 1080 };
|
||||
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
async function ensureDirs() {
|
||||
await fs.mkdir(IMAGE_DIR, { recursive: true });
|
||||
await fs.mkdir(VIDEO_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
async function login(page) {
|
||||
await page.goto(BASE_URL, { waitUntil: 'networkidle', timeout: 60000 });
|
||||
await page.locator('input[type="text"]').first().fill(USERNAME);
|
||||
await page.locator('input[type="password"]').first().fill(PASSWORD);
|
||||
await page.getByRole('button', { name: /安全登录|登录/ }).click();
|
||||
await page.waitForLoadState('networkidle').catch(() => {});
|
||||
await sleep(1800);
|
||||
}
|
||||
|
||||
async function shot(page, filename) {
|
||||
await page.screenshot({
|
||||
path: path.join(IMAGE_DIR, filename),
|
||||
fullPage: false,
|
||||
animations: 'disabled',
|
||||
});
|
||||
console.log(`screenshot ${filename}`);
|
||||
}
|
||||
|
||||
async function clickTitle(page, title, settle = 1000) {
|
||||
await page.getByTitle(title).first().click();
|
||||
await page.waitForLoadState('networkidle').catch(() => {});
|
||||
await sleep(settle);
|
||||
}
|
||||
|
||||
async function openWorkspace(page) {
|
||||
await clickTitle(page, '项目库', 800);
|
||||
await page.getByText('演视LC视频序列').first().click();
|
||||
await page.waitForLoadState('networkidle').catch(() => {});
|
||||
await sleep(2200);
|
||||
}
|
||||
|
||||
async function canvasBox(page) {
|
||||
const canvas = page.locator('.konvajs-content canvas').first();
|
||||
const box = await canvas.boundingBox();
|
||||
if (!box) throw new Error('Canvas is not visible.');
|
||||
return box;
|
||||
}
|
||||
|
||||
async function drawPolygonDraft(page) {
|
||||
await page.getByRole('button', { name: /肿瘤\/结节|胆囊/ }).first().click().catch(() => {});
|
||||
await page.getByTitle('创建多边形 (P)').click();
|
||||
await sleep(400);
|
||||
const box = await canvasBox(page);
|
||||
const points = [
|
||||
[box.x + box.width * 0.38, box.y + box.height * 0.38],
|
||||
[box.x + box.width * 0.57, box.y + box.height * 0.40],
|
||||
[box.x + box.width * 0.52, box.y + box.height * 0.60],
|
||||
[box.x + box.width * 0.35, box.y + box.height * 0.55],
|
||||
];
|
||||
for (const [x, y] of points) {
|
||||
await page.mouse.click(x, y);
|
||||
await sleep(250);
|
||||
}
|
||||
}
|
||||
|
||||
async function completePolygon(page) {
|
||||
await page.keyboard.press('Enter');
|
||||
await sleep(1000);
|
||||
}
|
||||
|
||||
async function captureCoreScreenshots() {
|
||||
const browser = await chromium.launch({ headless: true, args: ['--window-size=1920,1080'] });
|
||||
const context = await browser.newContext({ viewport, deviceScaleFactor: 1 });
|
||||
const page = await context.newPage();
|
||||
|
||||
await login(page);
|
||||
await openWorkspace(page);
|
||||
await shot(page, '24-workspace-current-frame-timeline.png');
|
||||
|
||||
await drawPolygonDraft(page);
|
||||
await shot(page, '25-create-polygon-vertices.png');
|
||||
await completePolygon(page);
|
||||
await shot(page, '26-create-polygon-completed.png');
|
||||
|
||||
await page.getByTitle('AI自动推理').click();
|
||||
await sleep(800);
|
||||
await page.getByLabel('传播起始帧').fill('1').catch(() => {});
|
||||
await page.getByLabel('传播结束帧').fill('3').catch(() => {});
|
||||
await shot(page, '27-ai-auto-inference-range.png');
|
||||
await page.getByRole('button', { name: '开始传播' }).click();
|
||||
await sleep(1800);
|
||||
await shot(page, '28-ai-auto-inference-running.png');
|
||||
await page.waitForFunction(() => document.body.innerText.includes('已自动传播') || document.body.innerText.includes('没有生成新'), null, { timeout: 90000 }).catch(() => {});
|
||||
await sleep(1000);
|
||||
await shot(page, '29-ai-auto-inference-completed.png');
|
||||
|
||||
await clickTitle(page, 'AI智能分割', 1800);
|
||||
await page.getByRole('button', { name: '正向选点' }).click();
|
||||
const box = await canvasBox(page);
|
||||
await page.mouse.click(box.x + box.width * 0.48, box.y + box.height * 0.48);
|
||||
await sleep(700);
|
||||
await shot(page, '30-ai-segmentation-positive-point.png');
|
||||
await page.getByRole('button', { name: '执行高精度语义分割' }).click();
|
||||
await page.waitForFunction(() => {
|
||||
const text = document.body.innerText;
|
||||
return text.includes('AI Mask') || text.includes('候选') || text.includes('推送至工作区编辑');
|
||||
}, null, { timeout: 90000 }).catch(() => {});
|
||||
await sleep(1200);
|
||||
await shot(page, '31-ai-segmentation-result.png');
|
||||
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
async function recordVideo(name, action) {
|
||||
const browser = await chromium.launch({ headless: true, args: ['--window-size=1920,1080'] });
|
||||
const context = await browser.newContext({
|
||||
viewport,
|
||||
deviceScaleFactor: 1,
|
||||
recordVideo: { dir: VIDEO_DIR, size: viewport },
|
||||
});
|
||||
const page = await context.newPage();
|
||||
await action(page);
|
||||
await sleep(800);
|
||||
const video = page.video();
|
||||
await context.close();
|
||||
await browser.close();
|
||||
const videoPath = await video?.path();
|
||||
if (!videoPath) return;
|
||||
const finalPath = path.join(VIDEO_DIR, `${name}.webm`);
|
||||
await fs.rm(finalPath, { force: true });
|
||||
await fs.rename(videoPath, finalPath);
|
||||
console.log(`video ${path.basename(finalPath)}`);
|
||||
}
|
||||
|
||||
async function captureVideos() {
|
||||
await recordVideo('05-创建多边形标注演示', async (page) => {
|
||||
await login(page);
|
||||
await openWorkspace(page);
|
||||
await drawPolygonDraft(page);
|
||||
await sleep(900);
|
||||
await completePolygon(page);
|
||||
await sleep(1600);
|
||||
});
|
||||
|
||||
await recordVideo('06-AI自动推理传播演示', async (page) => {
|
||||
await login(page);
|
||||
await openWorkspace(page);
|
||||
await drawPolygonDraft(page);
|
||||
await completePolygon(page);
|
||||
await page.getByTitle('AI自动推理').click();
|
||||
await sleep(900);
|
||||
await page.getByLabel('传播起始帧').fill('1').catch(() => {});
|
||||
await page.getByLabel('传播结束帧').fill('3').catch(() => {});
|
||||
await sleep(900);
|
||||
await page.getByRole('button', { name: '开始传播' }).click();
|
||||
await sleep(5000);
|
||||
});
|
||||
|
||||
await recordVideo('07-AI智能分割点选推理演示', async (page) => {
|
||||
await login(page);
|
||||
await openWorkspace(page);
|
||||
await clickTitle(page, 'AI智能分割', 1800);
|
||||
await page.getByRole('button', { name: '正向选点' }).click();
|
||||
const box = await canvasBox(page);
|
||||
await page.mouse.click(box.x + box.width * 0.48, box.y + box.height * 0.48);
|
||||
await sleep(1000);
|
||||
await page.getByRole('button', { name: '执行高精度语义分割' }).click();
|
||||
await sleep(5000);
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await ensureDirs();
|
||||
await captureCoreScreenshots();
|
||||
await captureVideos();
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user