Initial project baseline
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
# 生成算法说明
|
||||
|
||||
本文档描述当前代码实际算法。核心代码位于 `backend/core/pipeline.py`、`backend/core/layout.py`、`backend/core/weights.py`、`backend/EfficientWordCloud/efficient_wordcloud/src/ewc_core.cpp`。
|
||||
|
||||
## 总流程
|
||||
|
||||
1. 读取 Excel 名单:`pipeline.main()`
|
||||
2. 计算自动画幅:`mask.calculate_dynamic_dimensions()`
|
||||
3. 生成并归一化掩膜:`mask.prepare_mask()`
|
||||
4. 计算权重:`weights.extract_weights_from_df()` 和 `weights.get_stroke_complexity_batch()`
|
||||
5. 估算字号范围:`weights.calculate_font_by_area_model()`
|
||||
6. 小画布布局:`layout.OptimizedEfficientWordCloud.generate_from_frequencies()`
|
||||
7. C++ 找可放位置:`IntegralGrid.query_direct()`
|
||||
8. C++ 写入字形占用:`IntegralGrid.stamp_and_rebuild()`
|
||||
9. 计算填充率并必要时重试放大
|
||||
10. 将小画布 layout 放大到高清画布并输出 PNG/SVG/DB/metrics
|
||||
|
||||
## 名单和重复填充
|
||||
|
||||
`N_REPETITIONS` 决定目标词数:
|
||||
|
||||
```text
|
||||
total_target = len(names) * N_REPETITIONS
|
||||
```
|
||||
|
||||
布局序列由 `_build_layout_sequence()` 生成。当前行为是按原名单循环追加:
|
||||
|
||||
```text
|
||||
[A, B, C], N_REPETITIONS=4
|
||||
=> [A, B, C, A, B, C, A, B, C, A, B, C]
|
||||
```
|
||||
|
||||
所以当前队列顺序是“先填一轮名单,再填下一轮”,不是先放完同一个名字所有副本。
|
||||
|
||||
需要注意:队列顺序公平不等于最终字号完全一致。放置阶段如果某个词以目标字号找不到位置,会单独降字号继续尝试。因此后几轮词语通常比前几轮小。
|
||||
|
||||
## 权重逻辑
|
||||
|
||||
### Excel 权重
|
||||
|
||||
`WEIGHT_COL_NAME` 优先于 `WEIGHT_COL_INDEX`。有效权重必须是可转数字且大于 0。
|
||||
|
||||
`REMOVE_DUPLICATES = True` 时,同名权重取最大值。`REMOVE_DUPLICATES = False` 时,仍会按名字聚合权重映射,所以同名不同权重不会保留为不同权重实例。
|
||||
|
||||
### 笔画权重
|
||||
|
||||
`ENABLE_STROKE_WEIGHTS = True` 时,系统渲染每个字符到 64x64 灰度图,用像素占用量估算复杂度。一个名字的笔画权重取其中最复杂字符的值。
|
||||
|
||||
`ENABLE_STROKE_WEIGHTS = False` 时跳过笔画权重。若没有 Excel 权重,所有名字权重默认为 `10`。
|
||||
|
||||
## 字号范围估算
|
||||
|
||||
`calculate_font_by_area_model()` 使用可填充面积、目标填充率、packing efficiency、重复次数和名字长度估算 `min_font` / `max_font`。
|
||||
|
||||
公式思想:
|
||||
|
||||
- 可填区域越大,字号越大
|
||||
- 名字越多、重复次数越高,字号越小
|
||||
- 字符越多,总占用质量越高,字号越小
|
||||
- 权重越高,在 `log1p(weight)` 归一化后获得更高面积质量
|
||||
|
||||
最终 `max_font` 基于 `min_font * SIZE_RATIO` 计算。
|
||||
|
||||
## 字号打分
|
||||
|
||||
当前 `build_log_rank_scores(..., per_word=True)` 会按姓名权重计算固定分数,然后映射到展开后的重复序列。
|
||||
|
||||
这意味着:
|
||||
|
||||
- 同名副本的目标分数相同
|
||||
- 同名副本的初始目标字号相同
|
||||
- 权重相同时,所有姓名初始目标字号相同
|
||||
|
||||
但最终放置字号仍可能变小,因为放置失败时会逐词降字号。
|
||||
|
||||
## 放置策略
|
||||
|
||||
每个词的放置流程:
|
||||
|
||||
1. 根据目标分数得到目标字号
|
||||
2. 随机决定横排或竖排
|
||||
3. 用 PIL 测量文字包围盒
|
||||
4. 调用 C++ `query_direct(query_h, query_w, seed)` 找位置
|
||||
5. 如果找不到,字号减 2 后重试,最低到目标字号的 40% 或 `min_font_size`
|
||||
6. 放置成功后,取真实字形 bitmap 并调用 `stamp_and_rebuild()`
|
||||
7. 主循环放不下的词进入 gap filling,用更小字号再尝试一次
|
||||
|
||||
## C++ 积分图搜索
|
||||
|
||||
C++ `IntegralGrid` 维护两个核心结构:
|
||||
|
||||
- `canvas`:真实占用像素,`1` 表示已占用或掩膜阻挡
|
||||
- `data`:`canvas` 的积分图,用于 O(1) 判断矩形区域是否为空
|
||||
|
||||
`query_direct()` 的行为:
|
||||
|
||||
1. 如果画布完全空,随机返回一个位置
|
||||
2. 先随机探测最多 16 个位置
|
||||
3. 如果未命中,扫描所有可能位置
|
||||
4. 对每个候选位置用积分图判断包围盒是否为空
|
||||
5. 从所有可放位置中随机选一个
|
||||
|
||||
`stamp_and_rebuild()` 的行为:
|
||||
|
||||
1. 把真实字形像素写入 C++ `canvas`
|
||||
2. 从字形左上角开始局部重建积分图
|
||||
|
||||
当前碰撞检测是“矩形找位置 + 字形像素落图”。找位置阶段要求文字包围盒矩形完全空;实际占用阶段只写入字形像素。
|
||||
|
||||
## 填充率重试
|
||||
|
||||
一次布局完成后,`compute_fill_ratio_fast()` 重新渲染 layout 并计算填充率。
|
||||
|
||||
如果填充率低于 `MIN_ACCEPT_FILL_RATIO`,管线会尝试二分放大 `size_scale`,并可通过 `FILL_RETRY_RELAX_LARGE_CAP` 放宽大字号限制。
|
||||
|
||||
如果仍无法达到目标,会保留填充率最好的 layout。
|
||||
|
||||
## 已知算法限制
|
||||
|
||||
- 当前没有真正的“整轮统一降字号”机制。重复填充虽然按轮展开,但每个词可以独立降字号。
|
||||
- `LIMIT_LARGE_FONTS` 是全局计数,不区分姓名和轮次。
|
||||
- `LAYOUT_ORDER_MODE_INTERLEAVED_RANDOM` 只改变展开序列的顺序,不改变 C++ 的空间采样策略。
|
||||
- `ENABLE_STRATIFIED_SAMPLING` 调用的 `reorder_stratified()` 对 `query_direct()` 主路径无效。
|
||||
- C++ `batch_query()` 会用矩形 `update_rect_add()` 更新,不走真实字形 `stamp_and_rebuild()`;当前 Python 主路径没有使用它。
|
||||
|
||||
## 后续公平重复填充建议
|
||||
|
||||
如果目标是“每一轮名单整体公平变小”,建议新增独立模式,而不是继续微调当前逐词降字号:
|
||||
|
||||
- `REPEAT_FILL_MODE = "ROUND_ROBIN_FAIR"`
|
||||
- 以轮为单位生成任务
|
||||
- 同一轮使用统一字号或统一权重映射
|
||||
- 某一轮放不下时,整轮降低字号重试
|
||||
- 失败词统一进入下一档补位队列
|
||||
- 大字号限制按姓名或轮次计数
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
# 后端 API 说明
|
||||
|
||||
本文档按 `backend/service/app.py` 和 `backend/service/schemas.py` 当前代码整理。
|
||||
|
||||
## 基础约定
|
||||
|
||||
- 默认后端地址:`http://localhost:8000`
|
||||
- 请求体中上传文件使用 `multipart/form-data`
|
||||
- `params` 字段是 JSON 字符串,顶层必须是对象
|
||||
- 任务状态存在内存中,服务重启后状态会丢失
|
||||
|
||||
## Jobs
|
||||
|
||||
### GET `/api/health`
|
||||
|
||||
返回:
|
||||
|
||||
```json
|
||||
{"ok": true}
|
||||
```
|
||||
|
||||
### GET `/api/jobs`
|
||||
|
||||
返回内存中的任务状态列表,最新任务在前。
|
||||
|
||||
### POST `/api/jobs`
|
||||
|
||||
创建词云任务。
|
||||
|
||||
Form 字段:
|
||||
|
||||
| 字段 | 必填 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `name_list` | 是 | `.xlsx` 名单文件 |
|
||||
| `mask_image` | IMAGE 模式必填 | `.png` / `.jpg` / `.jpeg` 掩膜 |
|
||||
| `font_file` | 否 | 临时上传字体,支持后端 `_FONT_EXTENSIONS` 中的格式 |
|
||||
| `font_id` | 否 | 使用已上传字体 |
|
||||
| `params` | 否 | JSON 字符串,合并到任务配置 |
|
||||
|
||||
字体格式当前支持 `.ttf`、`.ttc`、`.otf`。
|
||||
|
||||
`params` 示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"MODE": "IMAGE",
|
||||
"DATA_COL_INDEX": 1,
|
||||
"SEED": 42,
|
||||
"N_REPETITIONS": 20,
|
||||
"ENABLE_STROKE_WEIGHTS": false,
|
||||
"FONT_COLOR": "#000000"
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{"job_id": "..." }
|
||||
```
|
||||
|
||||
### GET `/api/jobs/{job_id}`
|
||||
|
||||
返回 `JobStatus`:
|
||||
|
||||
```json
|
||||
{
|
||||
"job_id": "...",
|
||||
"status": "queued|running|success|failed",
|
||||
"stage": "...",
|
||||
"progress_percent": 0,
|
||||
"message": "...",
|
||||
"created_at": "...",
|
||||
"updated_at": "...",
|
||||
"artifacts": {},
|
||||
"error": ""
|
||||
}
|
||||
```
|
||||
|
||||
### GET `/api/jobs/{job_id}/detail`
|
||||
|
||||
返回状态和最近事件:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": {},
|
||||
"recent_events": []
|
||||
}
|
||||
```
|
||||
|
||||
### GET `/api/jobs/{job_id}/events`
|
||||
|
||||
SSE 事件流。事件数据模型:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "log|status",
|
||||
"stage": "placing_words",
|
||||
"progress_percent": 65,
|
||||
"message": "...",
|
||||
"timestamp": "..."
|
||||
}
|
||||
```
|
||||
|
||||
### GET `/api/jobs/{job_id}/result`
|
||||
|
||||
返回可下载产物 URL:
|
||||
|
||||
```json
|
||||
{
|
||||
"job_id": "...",
|
||||
"status": "success",
|
||||
"image_url": "/api/jobs/{job_id}/files/png",
|
||||
"svg_url": "/api/jobs/{job_id}/files/svg",
|
||||
"svg_stroke_url": "/api/jobs/{job_id}/files/svg_stroke",
|
||||
"db_url": "/api/jobs/{job_id}/files/db",
|
||||
"metrics_url": "/api/jobs/{job_id}/files/metrics"
|
||||
}
|
||||
```
|
||||
|
||||
### GET `/api/jobs/{job_id}/files/{kind}`
|
||||
|
||||
下载产物。`kind` 支持:
|
||||
|
||||
- `png`
|
||||
- `svg`
|
||||
- `svg_stroke`
|
||||
- `db`
|
||||
- `metrics`
|
||||
|
||||
### GET `/api/jobs/{job_id}/locations`
|
||||
|
||||
查询词语位置。查询参数:
|
||||
|
||||
| 参数 | 说明 |
|
||||
| --- | --- |
|
||||
| `name` | 可选;为空返回全部,非空精确匹配 |
|
||||
|
||||
返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"job_id": "...",
|
||||
"query": "",
|
||||
"total": 1,
|
||||
"canvas_width": 8000,
|
||||
"canvas_height": 4000,
|
||||
"matches": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "张三",
|
||||
"x": 100,
|
||||
"y": 200,
|
||||
"font_size": 64,
|
||||
"color": "#000000",
|
||||
"orientation": "horizontal",
|
||||
"box_x": 100,
|
||||
"box_y": 200,
|
||||
"box_width": 120,
|
||||
"box_height": 50
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### GET `/api/jobs/{job_id}/occupancy_mask`
|
||||
|
||||
返回 PNG,显示每个已放置词语的 bounding box。
|
||||
|
||||
### GET `/api/jobs/{job_id}/custom.svg`
|
||||
|
||||
按已生成 DB 重新导出 SVG。
|
||||
|
||||
查询参数:
|
||||
|
||||
| 参数 | 默认 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `fill` | `fill` | `fill` / `dot` / `line` / `ring` |
|
||||
| `stroke` | `0` | 是否描边 |
|
||||
| `spacing` | `10` | 点阵间距 |
|
||||
| `radius` | `2` | 点阵半径 |
|
||||
| `color` | `#000000` | 输出颜色 |
|
||||
| `line_spacing` | `6` | 线填充间距 |
|
||||
| `line_width` | `1` | 线宽 |
|
||||
| `line_angle` | `0` | 线角度 |
|
||||
| `ring_radius` | `3` | 环半径 |
|
||||
| `ring_width` | `1` | 环线宽 |
|
||||
| `ring_spacing` | `8` | 环间距 |
|
||||
|
||||
## Templates
|
||||
|
||||
### GET `/api/templates`
|
||||
|
||||
返回后端硬编码模板列表:
|
||||
|
||||
- `poster_1x2`
|
||||
- `poster_4x5`
|
||||
- `poster_1x1`
|
||||
- `poster_3x4`
|
||||
- `poster_16x9`
|
||||
|
||||
## Assets
|
||||
|
||||
### POST `/api/assets`
|
||||
|
||||
上传素材。Form 字段:
|
||||
|
||||
| 字段 | 必填 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `file` | 是 | 素材文件 |
|
||||
| `name` | 否 | 名称 |
|
||||
| `type` | 否 | 默认 `upload` |
|
||||
|
||||
### POST `/api/assets/from-job/{job_id}`
|
||||
|
||||
从任务产物导入素材。Form 字段:
|
||||
|
||||
| 字段 | 默认 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `kind` | `png` | 产物类型 |
|
||||
| `name` | 空 | 素材名称 |
|
||||
| `type` | `wordcloud` | 素材类型 |
|
||||
|
||||
### GET `/api/assets`
|
||||
|
||||
列出素材。支持查询参数:
|
||||
|
||||
- `type`
|
||||
- `job_id`
|
||||
|
||||
### GET `/api/assets/{asset_id}`
|
||||
|
||||
获取素材元数据。
|
||||
|
||||
### GET `/api/assets/{asset_id}/download`
|
||||
|
||||
下载素材文件。
|
||||
|
||||
### DELETE `/api/assets/{asset_id}`
|
||||
|
||||
删除素材。
|
||||
|
||||
## Projects
|
||||
|
||||
### POST `/api/projects`
|
||||
|
||||
Form 字段:
|
||||
|
||||
| 字段 | 必填 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `name` | 是 | 工程名 |
|
||||
| `template_id` | 是 | 模板 ID |
|
||||
| `background_color` | 是 | `#RRGGBB` |
|
||||
| `stickers` | 否 | JSON 数组字符串 |
|
||||
|
||||
### GET `/api/projects`
|
||||
|
||||
返回工程摘要列表。
|
||||
|
||||
### GET `/api/projects/{project_id}`
|
||||
|
||||
返回工程完整数据。
|
||||
|
||||
### PATCH `/api/projects/{project_id}`
|
||||
|
||||
按传入 Form 字段部分更新工程。
|
||||
|
||||
### DELETE `/api/projects/{project_id}`
|
||||
|
||||
删除工程。
|
||||
|
||||
## Fonts
|
||||
|
||||
### GET `/api/fonts`
|
||||
|
||||
返回字体列表,包含默认字体项。
|
||||
|
||||
### POST `/api/fonts`
|
||||
|
||||
上传字体。Form 字段:
|
||||
|
||||
| 字段 | 必填 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `file` | 是 | 字体文件 |
|
||||
| `name` | 否 | 字体名 |
|
||||
|
||||
### DELETE `/api/fonts/{font_id}`
|
||||
|
||||
删除已上传字体。默认字体不能删除。
|
||||
|
||||
## 常见错误
|
||||
|
||||
| 场景 | 状态码 | detail |
|
||||
| --- | --- | --- |
|
||||
| `params` 不是合法 JSON | 400 | `params must be valid JSON` |
|
||||
| `params` 不是对象 | 400 | `params must be JSON object` |
|
||||
| `name_list` 非 `.xlsx` | 400 | `name_list must be xlsx` |
|
||||
| IMAGE 模式缺少掩膜 | 400 | `mask_image is required when MODE=IMAGE` |
|
||||
| job 不存在 | 404 | `job not found` |
|
||||
| 产物未就绪 | 404 | `artifact not ready` |
|
||||
| 文件类型未知 | 404 | `unknown artifact kind` |
|
||||
@@ -0,0 +1,67 @@
|
||||
# 画布与贴纸功能
|
||||
|
||||
本文档描述当前前端代码中已经实现的画布设计功能。事实来源是 `frontend/src/App.tsx`、`frontend/src/pages/CanvasStudio.tsx`、`frontend/src/components/ExportPanel.tsx`、`frontend/src/lib/stickerLibrary.ts` 和 `frontend/src/types.ts`。
|
||||
|
||||
## 页面关系
|
||||
|
||||
- 应用默认进入画布设计页。
|
||||
- 画布页顶部的“添加词云”会切换到原有词云生成页。
|
||||
- 词云生成页顶部的“返回画布”会回到画布设计页。
|
||||
- 词云生成页的导出面板保留下载 SVG/位图功能,并新增“作为贴纸导入贴纸库”。
|
||||
|
||||
## 贴纸库
|
||||
|
||||
贴纸库是前端本地能力,不依赖后端接口:
|
||||
|
||||
- 存储位置:`localStorage` 的 `wordcloud-sticker-library`。
|
||||
- 数据类型:`StickerAsset`,当前支持 `svg` 和 `image` 两类,已实现入口主要使用 `svg`。
|
||||
- 用户导入 SVG:画布页左侧“贴纸”面板读取 `.svg` 文件文本,写入贴纸库,并立即插入画布。
|
||||
- 词云作为贴纸:导出面板按当前 SVG 导出参数请求 `/api/jobs/{job_id}/custom.svg`,读取返回的 SVG 文本后写入贴纸库。
|
||||
- 贴纸删除只删除本地贴纸库记录,不会删除已经导出的总图文件。
|
||||
|
||||
## 画布模型
|
||||
|
||||
画布文档保存在 `localStorage` 的 `wordcloud-canvas-document`。
|
||||
|
||||
当前模型字段:
|
||||
|
||||
- `width`:画布宽度,默认 `1600`。
|
||||
- `height`:画布高度,默认 `1000`。
|
||||
- `background`:画布背景色,默认 `#ffffff`。
|
||||
- `elements`:画布元素数组。
|
||||
|
||||
当前元素类型:
|
||||
|
||||
- `sticker`:引用贴纸库中的 SVG 或图片。
|
||||
- `text`:普通文字,支持内容、颜色、字号、字体、字重、位置、尺寸、旋转、透明度。
|
||||
- `rect`:矩形,支持填充、描边、描边宽度、位置、尺寸、旋转、透明度。
|
||||
- `ellipse`:椭圆,支持填充、描边、描边宽度、位置、尺寸、旋转、透明度。
|
||||
- `line`:线条,支持描边、描边宽度、位置、尺寸、旋转、透明度。
|
||||
|
||||
## 编辑行为
|
||||
|
||||
- 点击贴纸库中的贴纸会把该贴纸插入画布中央区域。
|
||||
- 画布元素可拖拽移动。
|
||||
- 选中元素后可通过右下角手柄调整大小。
|
||||
- 右侧属性面板可以精确编辑位置、尺寸、旋转、透明度和元素特有属性。
|
||||
- 右侧属性面板提供上移、下移和删除。
|
||||
- 画布面板支持修改画布宽高、背景色、导出 SVG、清空画布。
|
||||
|
||||
## SVG 导出
|
||||
|
||||
“导出总图 SVG”由前端序列化当前画布模型完成:
|
||||
|
||||
- 导出文件名:`canvas-design.svg`。
|
||||
- 背景输出为一个覆盖全画布的 `<rect>`。
|
||||
- 贴纸输出为 `<image>`,SVG 贴纸会以内联 `data:image/svg+xml` 的形式嵌入。
|
||||
- 文字输出为 `<text>`。
|
||||
- 基础形状输出为原生 SVG 的 `<rect>`、`<ellipse>`、`<line>`。
|
||||
- 元素的位移和旋转写入 SVG `transform`,透明度写入 `opacity`。
|
||||
|
||||
## 当前边界
|
||||
|
||||
- 贴纸库和画布文档只保存在当前浏览器本地,不会跨浏览器或跨设备同步。
|
||||
- 当前没有服务端素材库、项目文件格式或协作编辑接口。
|
||||
- SVG 导入按用户信任文件处理;编辑器预览使用图片方式加载,不在页面中直接执行 SVG 内容。
|
||||
- 当前缩放只影响编辑视图,不改变导出尺寸。
|
||||
- 当前导出目标是 SVG;没有在画布页实现 PNG/JPG 总图导出。
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
# 配置说明
|
||||
|
||||
本文档只覆盖当前代码中实际可用的配置。完整键集合以 `backend/core/config.py` 的 `KNOWN_CONFIG_KEYS` 为准。
|
||||
|
||||
## 配置来源和优先级
|
||||
|
||||
CLI 入口 `backend/wordcloud_generate_hybrid.py` 的顺序:
|
||||
|
||||
1. 加载 `backend/core/config.py` 默认值
|
||||
2. 如果传 `--config`,调用 `apply_json_config()`
|
||||
3. 调用 `apply_cli_overrides()`
|
||||
4. 调用 `finalize_runtime_config()` 派生路径、字体、输出路径
|
||||
5. 调用 `set_random_seed()`
|
||||
|
||||
服务模式下,`POST /api/jobs` 会生成任务配置并写入:
|
||||
|
||||
```text
|
||||
backend/service_workspace/{job_id}/config.json
|
||||
```
|
||||
|
||||
之后由子进程通过 `--config` 读取。
|
||||
|
||||
## 前端参数映射
|
||||
|
||||
当前前端 `TestWorkbench.tsx` 提交的关键字段:
|
||||
|
||||
| 前端字段 | 后端配置 |
|
||||
| --- | --- |
|
||||
| `dataColIndex` | `DATA_COL_INDEX` |
|
||||
| `seed` | `SEED` |
|
||||
| `weightColIndex` | `WEIGHT_COL_INDEX` |
|
||||
| `fontColor` | `FONT_COLOR` |
|
||||
| `nRepetitions` | `N_REPETITIONS` |
|
||||
| `strokeWeights=false` | `ENABLE_STROKE_WEIGHTS=false` |
|
||||
|
||||
前端只在重复次数大于 1 时传 `N_REPETITIONS`,只在关闭笔画权重时传 `ENABLE_STROKE_WEIGHTS=false`。
|
||||
|
||||
## 常用配置
|
||||
|
||||
| 键 | 默认值 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `MODE` | `IMAGE` | 掩膜模式,`IMAGE` 或 `TEXT` |
|
||||
| `MASK_IMAGE_PATH` | `7887.png` | IMAGE 模式掩膜路径,服务模式会覆盖为上传文件路径 |
|
||||
| `IMAGE_CANVAS_MODE` | `WIDTH` | 图片掩膜缩放模式 |
|
||||
| `FILL_ON` | `BLACK` | `BLACK` 表示黑色可填,`WHITE` 表示白色可填 |
|
||||
| `EXCEL_PATH` | `四个方向汇总录取名单.xlsx` | Excel 路径,服务模式会覆盖为上传文件路径 |
|
||||
| `DATA_COL_INDEX` | `1` | 名单列,0-based |
|
||||
| `WEIGHT_COL_INDEX` | `None` | 权重列,0-based |
|
||||
| `WEIGHT_COL_NAME` | `None` | 权重列名,优先于列索引 |
|
||||
| `REMOVE_DUPLICATES` | `False` | 是否对名单去重 |
|
||||
| `ENABLE_STROKE_WEIGHTS` | `True` | 是否在无 Excel 权重时使用笔画复杂度权重 |
|
||||
| `N_REPETITIONS` | `1` | 名单重复倍率 |
|
||||
| `SIZE_RATIO` | `2.0` | `max_font` 相对 `min_font` 的比例 |
|
||||
| `PACKING_EFFICIENCY` | `0.85` | 面积模型中的打包效率 |
|
||||
| `MIN_ACCEPT_FILL_RATIO` | `0.75` | 填充率重试阈值 |
|
||||
| `REQUIRE_ALL_WORDS` | `True` | 搜索阶段是否优先要求达到目标词数 |
|
||||
| `USER_MIN_FONT_SIZE` | `None` | 用户覆盖最小字号 |
|
||||
| `USER_MAX_FONT_SIZE` | `None` | 用户覆盖最大字号 |
|
||||
| `FONT_SCALE_MIN` | `0.5` | 二分搜索缩放下限 |
|
||||
| `FONT_SCALE_MAX` | `1.2` | 二分搜索缩放上限 |
|
||||
| `LIMIT_LARGE_FONTS` | `True` | 是否限制大字号数量 |
|
||||
| `LARGE_FONT_LIMIT_RATIO` | `0.2` | 大字号数量上限占比 |
|
||||
| `LARGE_FONT_THRESHOLD_RATIO` | `0.8` | 超过有效最大字号该比例视为大字号 |
|
||||
| `LARGE_FONT_CAP_RATIO` | `0.6` | 超过大字号限制后的降级比例 |
|
||||
| `ENABLE_DOT_MATRIX` | `False` | 是否用点阵补偿空白区域 |
|
||||
| `CANVAS_RETRY_MAX_ROUNDS` | `1` | 画布扩大重试轮数 |
|
||||
| `FONT_COLOR` | `#000000` | 固定字体颜色;为空时使用调色板 |
|
||||
| `SEED` | `None` | 随机种子 |
|
||||
| `LAYOUT_ORDER_MODE` | `SORTED` | 展开序列排序模式 |
|
||||
| `LAYOUT_SEED` | `None` | 布局顺序种子,默认继承 `SEED` |
|
||||
|
||||
## JSON 别名
|
||||
|
||||
`apply_json_config()` 支持部分小写别名:
|
||||
|
||||
| 别名 | 正式配置 |
|
||||
| --- | --- |
|
||||
| `seed` | `SEED` |
|
||||
| `layout_order_mode` | `LAYOUT_ORDER_MODE` |
|
||||
| `layout_seed` | `LAYOUT_SEED` |
|
||||
| `excel_path` | `EXCEL_PATH` |
|
||||
| `mask_image_path` | `MASK_IMAGE_PATH` |
|
||||
| `output_dir` | `OUTPUT_DIR` |
|
||||
| `output_prefix` | `OUTPUT_PREFIX` |
|
||||
| `mode` | `MODE` |
|
||||
| `work_scale` | `WORK_SCALE` |
|
||||
| `weight_col_index` | `WEIGHT_COL_INDEX` |
|
||||
| `weight_col_name` | `WEIGHT_COL_NAME` |
|
||||
| `min_font_size` | `USER_MIN_FONT_SIZE` |
|
||||
| `max_font_size` | `USER_MAX_FONT_SIZE` |
|
||||
| `font_color` | `FONT_COLOR` |
|
||||
| `stroke_weights` | `ENABLE_STROKE_WEIGHTS` |
|
||||
|
||||
CLI 覆盖只支持 `parse_args()` 中定义的参数,不支持 `font_color` 或 `stroke_weights` CLI 参数。
|
||||
|
||||
## 类型校验
|
||||
|
||||
`CRITICAL_TYPE_CHECKS` 中的配置类型错误会直接退出。未知配置键只告警,不会失败。
|
||||
|
||||
## 路径规则
|
||||
|
||||
相对路径会以 `backend` 目录作为基准解析。输出路径会在 `finalize_runtime_config()` 中创建。
|
||||
|
||||
字体会先尝试项目字体,再尝试 `FONT_FALLBACK_PATHS`。字体不可用会直接失败。
|
||||
@@ -0,0 +1,146 @@
|
||||
# 项目标准说明
|
||||
|
||||
本文档按当前代码整理,覆盖项目边界、运行方式、输入输出和维护约定。最后核对代码时间:2026-06-09。
|
||||
|
||||
## 项目目标
|
||||
|
||||
本项目生成基于名单和掩膜的词云图。后端负责读取 Excel 名单、处理掩膜、计算权重、布局、渲染和导出;前端提供参数面板、任务提交、结果查看、查找和导出入口。
|
||||
|
||||
当前项目不是通用设计平台。`Projects`、`Assets`、`Templates` 接口存在,但主要服务于当前工作台原型和素材管理,不代表完整生产级工程系统。
|
||||
|
||||
## 目录结构
|
||||
|
||||
| 路径 | 职责 |
|
||||
| --- | --- |
|
||||
| `backend/wordcloud_generate_hybrid.py` | CLI 入口,加载配置后调用生成管线 |
|
||||
| `backend/core/config.py` | 默认配置、JSON 配置合并、CLI 覆盖、路径和字体解析 |
|
||||
| `backend/core/pipeline.py` | 生成主流程:读数据、画布、掩膜、权重、布局、渲染、DB、metrics |
|
||||
| `backend/core/layout.py` | Python 布局调度、字号打分、逐词放置、SVG 导出 |
|
||||
| `backend/core/weights.py` | 笔画复杂度权重、Excel 权重、面积字号模型 |
|
||||
| `backend/core/mask.py` | 掩膜归一化、自动画布、边界安全 padding |
|
||||
| `backend/core/render.py` | 填充率计算和点阵补偿 |
|
||||
| `backend/EfficientWordCloud/` | C++ 扩展及其 Python 包装 |
|
||||
| `backend/service/` | FastAPI 服务、任务管理、文件存储 |
|
||||
| `frontend/src/` | React 工作台 |
|
||||
| `docs/` | 标准文档入口 |
|
||||
|
||||
## 运行方式
|
||||
|
||||
### 一键前后端联调
|
||||
|
||||
在项目根目录运行:
|
||||
|
||||
```bash
|
||||
./start-all.sh
|
||||
```
|
||||
|
||||
它会:
|
||||
|
||||
- 启动 `backend/start-dev.sh`
|
||||
- 启动前端 `npm run dev`
|
||||
- 默认后端端口为 `8000`
|
||||
- 前端 Vite 端口为 `3000`
|
||||
|
||||
### 单独启动后端
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
./start-dev.sh
|
||||
```
|
||||
|
||||
`start-dev.sh` 会检查 Python 依赖、必要时创建 `.venv`,并在 C++ 源码更新后重新编译 `ewc_core`。
|
||||
|
||||
### 单独启动前端
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm run dev
|
||||
```
|
||||
|
||||
前端通过 Vite 代理访问后端。代理配置见 `frontend/vite.config.ts`。
|
||||
|
||||
### CLI 生成
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
python wordcloud_generate_hybrid.py --config /path/to/config.json
|
||||
```
|
||||
|
||||
CLI 配置优先级:
|
||||
|
||||
1. `backend/core/config.py` 默认值
|
||||
2. JSON 配置文件
|
||||
3. CLI 参数覆盖
|
||||
|
||||
## 输入要求
|
||||
|
||||
### Excel 名单
|
||||
|
||||
服务接口只接受 `.xlsx`。默认名单列为 `DATA_COL_INDEX = 1`,也就是第 2 列,索引从 0 开始。
|
||||
|
||||
当 `REMOVE_DUPLICATES = False` 时,Excel 中重复姓名会保留。当前前端默认保留重复。
|
||||
|
||||
### 权重
|
||||
|
||||
权重来源按优先级合并:
|
||||
|
||||
1. Excel 权重列:`WEIGHT_COL_NAME` 优先于 `WEIGHT_COL_INDEX`
|
||||
2. 笔画复杂度权重:受 `ENABLE_STROKE_WEIGHTS` 控制
|
||||
3. 默认权重:没有权重时使用 `10`
|
||||
|
||||
关闭 `ENABLE_STROKE_WEIGHTS` 且不传 Excel 权重列时,所有姓名进入均等权重。
|
||||
|
||||
### 掩膜
|
||||
|
||||
`MODE = IMAGE` 时必须提供 PNG/JPG/JPEG 掩膜。后端会将图片转灰度并以阈值 `200` 二值化。
|
||||
|
||||
`FILL_ON = BLACK` 时,黑色区域可填充;`FILL_ON = WHITE` 时,白色区域可填充。
|
||||
|
||||
## 输出产物
|
||||
|
||||
每次服务任务会创建:
|
||||
|
||||
```text
|
||||
backend/service_workspace/{job_id}/
|
||||
input/
|
||||
mask.png
|
||||
names.xlsx
|
||||
output/
|
||||
Efficient_Result_HD_AutoResize.png
|
||||
Efficient_Result_HD_AutoResize.svg
|
||||
Efficient_Result_HD_AutoResize_stroke.svg
|
||||
wordcloud_hd.db
|
||||
metrics.json
|
||||
debug/
|
||||
mask_src.png
|
||||
mask_hd.png
|
||||
mask_small.png
|
||||
occ_fast.png
|
||||
config.json
|
||||
```
|
||||
|
||||
产物说明:
|
||||
|
||||
| 文件 | 含义 |
|
||||
| --- | --- |
|
||||
| PNG | 最终位图结果 |
|
||||
| SVG | 填充路径 SVG |
|
||||
| `_stroke.svg` | 描边 SVG,适合继续加工 |
|
||||
| SQLite DB | `word_locations` 表,记录词语位置、字号、颜色、方向、包围盒 |
|
||||
| metrics | 运行指标、画布尺寸、填充率、配置快照 |
|
||||
| debug | 调试图,受 `SAVE_DEBUG_IMAGES` 控制 |
|
||||
|
||||
## 当前限制
|
||||
|
||||
- 重复填充当前按名单轮次展开,但单个词在放置失败时会独立降字号;这会导致后几轮整体字号小于前几轮。
|
||||
- `reorder_stratified()` 当前对主路径 `query_direct()` 没有实际影响,因为 `query_direct()` 不使用 `valid_coords`。
|
||||
- C++ `Grid_query_direct` 的 GIL 释放包装没有包住实际扫描调用,性能并发上还有优化空间。
|
||||
- API 中的 Jobs 存储在进程内存,服务重启后历史任务状态会丢失;文件仍保留在 `service_workspace`。
|
||||
- 旧文档中的市场分析、路线图和性能宣传不作为当前能力承诺。
|
||||
|
||||
## 维护约定
|
||||
|
||||
- 修改算法行为时,同步更新 [ALGORITHM.md](ALGORITHM.md)。
|
||||
- 新增或删除配置项时,同步更新 [CONFIG.md](CONFIG.md)。
|
||||
- 改 HTTP 接口或响应模型时,同步更新 [API.md](API.md)。
|
||||
- 不再新增单次变更记录文档;短期变更应合并进标准文档。
|
||||
@@ -0,0 +1,32 @@
|
||||
# WordCloud 项目文档入口
|
||||
|
||||
本文档目录是当前项目的标准文档入口。除非某个历史文档被明确标注为“标准文档”,否则以这里列出的文档为准。
|
||||
|
||||
## 文档准则
|
||||
|
||||
- 以代码为准。文档只描述当前代码实际行为,不提前承诺未实现能力。
|
||||
- 以 `backend/core/config.py`、`backend/core/pipeline.py`、`backend/core/layout.py`、`backend/EfficientWordCloud/efficient_wordcloud/src/ewc_core.cpp` 为算法事实来源。
|
||||
- 以 `backend/service/app.py`、`backend/service/schemas.py` 为 HTTP API 事实来源。
|
||||
- 变更记录只记录历史,不作为使用说明。
|
||||
|
||||
## 标准文档
|
||||
|
||||
| 文档 | 用途 |
|
||||
| --- | --- |
|
||||
| [PROJECT_STANDARD.md](PROJECT_STANDARD.md) | 项目结构、运行方式、输入输出、工程约定 |
|
||||
| [ALGORITHM.md](ALGORITHM.md) | 词云生成算法、重复填充、权重、C++ 碰撞搜索 |
|
||||
| [CONFIG.md](CONFIG.md) | 配置项、优先级、前端参数到后端配置的映射 |
|
||||
| [API.md](API.md) | FastAPI 接口、请求格式、响应结构、产物下载 |
|
||||
| [CANVAS_STUDIO.md](CANVAS_STUDIO.md) | 画布设计、贴纸库、词云作为贴纸、总图 SVG 导出 |
|
||||
|
||||
## 非标准/历史文档
|
||||
|
||||
以下文档可能包含历史规划、阶段性设想或已经过期的实现描述,不再作为行为依据:
|
||||
|
||||
- `docs/PPT-EfficientWordCloud-详细大纲-v1.0.md`
|
||||
- `docs/stroke-weights-optional.md`
|
||||
- `backend/docs/*`
|
||||
- `backend/README.md`
|
||||
- `backend/README_zh.md`
|
||||
|
||||
需要确认行为时,优先查标准文档;标准文档仍不清楚时,直接查代码。
|
||||
Reference in New Issue
Block a user