ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Parsr WordsToLineNewModule 详解:基于自适应词间距统计的 Word 到 Line 合并算法

Parsr WordsToLineNewModule 详解:基于自适应词间距统计的 Word 到 Line 合并算法 后端数据工程【免费下载链接】ParsrTransforms PDF, Documents and Images into Enriched Structured Data项目地址https://gitcode.com/gh_mirrors/pa/Parsr点击查看免费下载Parsr 是一个将 PDF、文档和图片转换为结构化富数据Enriched Structured Data的开源项目其服务端通过一系列可配置的清洗模块Cleaner Module在文档对象模型上逐级构建层次。本文聚焦于其中负责把散落单词合成一行的核心模块——WordsToLineNewModule模块名words-to-line-new围绕其在 WordsToLineNewModule/README.md 中声明的职责与算法思路结合 WordsToLineNew.ts 的完整实现、defaultConfig.json 的配置规格与 words-to-line.spec.ts 的测试用例讲清它的工作原理、自适应词间距判定的底层机制、配置调参方法以及它在 Parsr 清洗流水线中的上下游关系。读完本文你将能理解该模块为何不依赖任何外部模块也能根据实际版面问题精准调整modifyAvgWordsSpace与modifyCommonWordsSpace两个参数。模块定位从单词到行的层次构建Purpose在页面内把单词聚合成行WordsToLineNewModule的唯一职责可以用一句话概括Create lines from a bunch of words inside pages——将页面内散落的Word元素聚合成Line元素。在 Parsr 的文档对象模型位于 DocumentRepresentation中层次结构大致是Document → Page → ... → Word而行Line介于段落与单词之间是后续行合并为段落LinesToParagraphModule乃至标题、列表、键值对等更高层语义检测的基础单元。该模块的核心产物是新建的 Line 元素其 content 为 Word 元素数组见 WordsToLineNew.ts。也就是说它不修改任何单词本身的内容或边界框只是把语义上应处于同一行的单词重新归组、包裹进Line容器并计算合并后的BoundingBox。Dependencies零依赖设计README 明确标注No dependencie无依赖。在 Parsr 的模块体系中依赖通过Module基类的静态属性dependencies声明并由 Cleaner.ts 的checkDependenciesAndAdd在装配时校验WordsToLineNewModule没有声明任何依赖WordsToLineNew.ts因此在清洗流水线中可以被安全地放置在任意位置、独立运行。这与它的前身WordsToLineModule模块名words-to-line形成对比——后者声明了[ReadingOrderDetectionModule]作为依赖见 WordsToLineModule/WordsToLineModule.ts。从源码结构可以推断New 版本通过自排序见下文排序摆脱了对阅读顺序模块的依赖这也是它被称为new的核心差异之一。工作原理垂直对齐分组 自适应水平切分README 对算法流程的描述是逐词取用页面中的每个单词先依据垂直对齐vertical alignment将其切分成若干候选行再根据词间平均间距与常见间距判定是否把一行拆成多行。这一描述在源码中对应main→getRootWords→mergeWordsInLines→groupWordsVertically→checkWordsSpace的调用链。入口与整体编排mainmain(doc)WordsToLineNew.ts按页处理幂等保护若页面中已存在Line元素则打日志警告该页已存在行不执行行合并直接返回原页避免重复处理。收集根级单词通过getRootWords(page)取得页面顶层非递归的Word元素过滤掉没有边界框的单词并按(top, left)位置排序。合并为行调用mergeWordsInLines(rootWords)对这些根级单词执行成行算法。处理嵌套单词对页面中其他根元素如段落容器内部的单词通过joinWordsInElements递归地在元素内部执行同样的成行合并并将新生成的行追加到page.elements。getRootWords使用的getElementsOfTypeWord(Word, false)第二个参数false表示不递归只取顶层单词——这样外层包裹元素如已有的段落容器内部的单词不会与页面顶层单词混淆。第一步垂直对齐分组groupWordsVertically单词先按top升序同 top 按left升序排列然后依次两两检查是否垂直对齐WordsToLineNew.ts。判断areVerticallyAligned(wordA, wordB)是否成立WordsToLineNew.ts满足任一即可case1两词top相等或bottom相等严格基线/顶线对齐case3wordA.height - |wordB.top - wordA.top| wordA.height * 0.5B 的顶线落在 A 垂直区间的上半段case4wordB.height - |wordB.top - wordA.top| wordB.height * 0.5A 的顶线落在 B 垂直区间的上半段。简言之只要两个词在垂直方向上有显著重叠重叠区超过各自高度的 50%就被视为同行候选。此外还有一个兜底条件inSameVerticalLine当前行中不存在top height 新词top的词即新词不能出现在所有已积累词的下方。每完成一组就按left重新排序保证组内词序从左到右。第二步水平切分checkWordsSpace 与 inSameLine垂直分组后的候选词仍可能包含真正的多行内容如两端对齐文本中上下两行的词被误判为垂直重叠。checkWordsSpaceWordsToLineNew.ts按从左到右的顺序逐个词判断是否与当前行最后一个词仍属同一行是否同行的判据inSameLine - 当前行为空 → 必然同一行返回 true - 当前行已有 2 个词 → word.left - lastWordEnd lastWord.height word.height - 其他情况 → Math.round(word.left - lastWordEnd) avgSpace * tolerance其中avgSpace是动态重算的每当一行被判定结束、开启新行时会用剩余未消费的词重新计算平均间距avgSpace this.avgWordsSpace(words.filter(w !currentLine.includes(w)))。这是自适应的体现——多列版面中不同列的字号与字距不同动态重算能让每列都获得贴合自身的阈值。tolerance与列表检测相关当当前行恰好只有一个词、且它与新词合并后能通过ListDetectionModule.isBullet或isNumbering判定为列表项如1. 、• 开头时容差放大为 5checkSpecialWordWordsToLineNew.ts。这是为了让序号 制表符 文本这种间距偏大的列表项不被误拆成两行否则容差为 1。第三步合并成 Line确定最终行内词序后调用utils.mergeElementsWord, Line(new Line(new BoundingBox(0,0,0,0)), ...wordsLine)utils.ts创建Line将所有词按order排序后作为content并用BoundingBox.merge计算覆盖所有词的合并边界框。随后为每行设置properties.order递增编号为每个词也设置properties.order通过wordsCounter全局递增保证后续模块能按阅读顺序消费。核心机制平均间距与常见间距的自适应统计avgWordsSpace行的平均间距avgWordsSpace(words)WordsToLineNew.ts是水平切分的核心统计量逻辑为对每对相邻词计算distance word.left - (prevWordEnd prev.left prev.width)即词间空隙若distance modifyAvgWordsSpace word.height * 0.2两词几乎粘连为避免一个近距词把平均值拉得过低导致每个词都变成单独一行改用commonWordsSpace计算的常见间距参与平均其余情况累加distance modifyAvgWordsSpace最终avg 总和 / (词数-1)返回Math.round(avg * 2.5) modifyAvgWordsSpace。注意末尾的* 2.5放大系数平均间距被放大 2.5 倍后作为切分阈值意味着只有明显大于统计常态的间隙才会触发断行从而容忍正常排版中的轻微字距抖动。modifyAvgWordsSpace参数在多个位置参与运算是一个直接作用于阈值的全局偏移量。commonWordsSpace行的常见间距commonWordsSpace(words, excludeIndex)WordsToLineNew.ts用于计算词间距的常见水平排除首词与被排除下标excludeIndex即触发近距分支的那个词后对其余相邻词对的distance modifyCommonWordsSpace取平均再放大* 1.2。它是avgWordsSpace的近距降级分支避免个别紧密词干扰整体统计同时也让两个可调参数分别影响平均与常见两个统计口径。两个配置参数的语义模块参数定义在 defaultConfig.json{ name: words-to-line-new, description: Create lines from a bunch of words., specs: { modifyAvgWordsSpace: { value: 0, range: { min: -100, max: 100 } }, modifyCommonWordsSpace: { value: 0, range: { min: -100, max: 100 } } } }参数默认值取值范围作用modifyAvgWordsSpace0-100 ~ 100对平均间距统计及最终切分阈值施加的全局偏移像素级正值放大阈值、负值收紧阈值modifyCommonWordsSpace0-100 ~ 100对常见间距统计施加的偏移影响近距降级分支间接影响平均值的稳定性参数通过 Module.ts 的构造器完成合并读取defaultConfig.json的specs结构用户传入的配置值覆盖默认值未传的键自动采用specs[key].value若传入未知键还会输出该键拼写有误或未知的日志提示。这正是 README Limitations 中提到的调参入口——在特定版面不生效时通过增减这两个值微调平均间距与常见间距的检测灵敏度版面字距普遍偏大导致误拆行时增大modifyAvgWordsSpace词间距过近导致该拆不拆时减小它甚至负值。modifyCommonWordsSpace则更多用于修正紧密排版下的统计稳定性。在 Parsr 流水线中的位置与装配注册与默认启用WordsToLineNewModule被注册进 Cleaner.ts 的cleaningToolRegister模块清单第 59 行模块名words-to-line-new它同时出现在服务端默认配置 server/defaultConfig.json 与 remoteModuleConfig.json 的 cleaner 列表中因此开箱即用。配置装配时Cleaner.parseLatestConfig支持两种条目形式纯字符串使用默认参数或[模块名, {参数对象}]。下游依赖它的模块从源码结构看以下模块把WordsToLineNewModule声明为依赖即它们期望输入中已经存在LineLinesToParagraphModule.tsdependencies [ReadingOrderDetectionModule, WordsToLineNewModule]行合并成段落的直接前提KeyValueDetectionModule.ts键值对检测需要行级文本TemplateModule.ts模板匹配基于行。因此在自定义 cleaner 配置时words-to-line-new必须出现在这些模块之前否则 Cleaner.ts 会抛出未解决的依赖错误。配置示例官方 configuration.md 给出的示例配置片段{ cleaner: [ out-of-page-removal, whitespace-removal, redundancy-detection, table-detection, [header-footer-detection, { maxMarginPercentage: 15 }], [reading-order-detection, { minColumnWidthInPagePercent: 15 }], link-detection, [words-to-line-new, { modifyAvgWordsSpace: 0, modifyCommonWordsSpace: 0 }], lines-to-paragraph, page-number-detection, hierarchy-detection ] }注意maximumSpaceBetweenWords是旧版words-to-line模块的参数见 WordsToLineModule/defaultConfig.json不是words-to-line-new的合法键若误用会被 Module 构造器以未知键日志提示。words-to-line-new只接受modifyAvgWordsSpace与modifyCommonWordsSpace。与旧版 WordsToLineModule 的差异同目录的旧版words-to-lineWordsToLineModule/WordsToLineModule.ts采用固定阈值策略其joinAlignedWords使用topUncertainty、lineHeightUncertainty、maximumSpaceBetweenWords等显式参数默认 0.4 / 0.2 / 100见其 defaultConfig.json两词水平距离 maximumSpaceBetweenWords即视为同行。而新版words-to-line-new的差异点在于无外部依赖不要求先跑reading-order-detection自行按(top, left)排序阈值自适应不再使用固定间距上限而是依据当前组词动态统计平均间距 × 2.5与常见间距 × 1.2作为切分阈值并随行的推进实时重算内置列表感知通过ListDetectionModule.isBullet/isNumbering识别列表项并把容差放大 5 倍防止序号 内容被拆开垂直判据以 50% 垂直重叠率 底线/顶线对齐替代简单的 top 近似容差。测试验证事实依据与正确性保证仓库为WordsToLineNewModule提供了多份测试证据见 words-to-line.spec.ts合并数量正确对line-merge.pdfminer.json、line-merge-2-unaligned.pdfminer.json轻微错位单词、one-paragraph.pdfminer.json三份输入断言合并后页面元素数量分别为 1、1、7 个块内容保真断言各 Line 拼接后的文本与原始单词内容完全一致如Im a sentence with multiple words.即合并过程不丢失、不改写任何文本结构完备断言页面顶层不再残留任何游离的Word全部被收入Line。测试通过getDocFromJson读取 test/assets 下的 pdfminer JSON 快照经runModules(sortWords(doc), [new WordsToLineNewModule()])串行执行见 helpers.ts。此外line-merge.spec.ts、paragraph-merge.spec.ts、input-extractors.spec.ts、json-export-import.spec.ts也均导入并使用WordsToLineNewModule构建流水线验证了它在端到端链路中的可用性。该模块自身在源码注释中被标注为Stability: StableREADME 亦给出 Almost perfect 的准确率评价——但注意这是项目文档的自我描述实际效果仍应结合自身版面与测试用例评估。调参实战指南根据 README 的 Limitations 与源码逻辑可按如下思路排查该拆的行没拆行内混入多行文本说明avgSpace × 2.5阈值过大把两个间隙误认为同一行。可**减小modifyAvgWordsSpace取负值**收紧阈值。不该拆的拆了一行被切成多段多因正常字距抖动或两端对齐产生的宽间隙触发断行。可增大modifyAvgWordsSpace放大阈值或增大modifyCommonWordsSpace稳定近距统计。列表项1. 文本被拆成两行此时应确认ListDetectionModule是否先于本模块运行并正确标注列表候选若列表样式特殊可先调高列表检测灵敏度。多列版面中某列异常由于阈值随行重算可在保证单列统计正常的前提下观察是平均还是常见口径失真分别微调对应参数。参数改动仅需修改 cleaner 配置中的模块条目如上节示例无需改动任何源码。完整配置说明可参阅 configuration.md模块在清洗流水线中的装配机制见 Cleaner.ts。赞分享后端数据工程【免费下载链接】ParsrTransforms PDF, Documents and Images into Enriched Structured Data项目地址https://gitcode.com/gh_mirrors/pa/Parsr点击查看免费下载相关推荐Parsr WordsToLineModule 深入解析基于阅读顺序的 Word 到 Line 合并原理与调优指南Parsr WordsToLineModule 深入解析基于阅读顺序的 Word 到 Line 合并原理与调优指南 导读 WordsToLineModule后端数据工程FAST Select.setPositioning() 方法详解基于视口空间的自适应 listbox 定位机制FAST Select.setPositioning 方法详解基于视口空间的自适应 listbox 定位机制 Select.setPositioning 是前端UI组件ElastAlert 异常检测算法基于统计模型的阈值自适应调整终极指南ElastAlert 异常检测算法基于统计模型的阈值自适应调整终极指南 ElastAlert 是一个强大的开源工具专门用于在 Elasticsearch 数告警异常检测上一篇Serilog日志等级动态调整终极指南HTTP API控制实现下一篇A股公告查不到0条的隐藏原因a-stock-data动态修复巨潮cninfo orgId映射附源码创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进