RED-6009: Document Tree Structure

* extracted classification to enum
* extracted approxLineCount as constant
* removed NodeType from Entry in DocumentTree
* renamed a variable
This commit is contained in:
Kilian Schuettler 2023-05-17 13:29:41 +02:00
parent 1dd55d9af6
commit c8ee4444c2
14 changed files with 109 additions and 69 deletions

View File

@ -22,7 +22,7 @@ public abstract class AbstractPageBlock {
@JsonIgnore @JsonIgnore
protected float maxY; protected float maxY;
@JsonIgnore @JsonIgnore
protected String classification; protected PageBlockType classification;
@JsonIgnore @JsonIgnore
protected int page; protected int page;
@ -35,7 +35,7 @@ public abstract class AbstractPageBlock {
public boolean isHeadline() { public boolean isHeadline() {
return this instanceof ClassificationTextBlock && this.getClassification() != null && this.getClassification().startsWith("H"); return this instanceof ClassificationTextBlock && this.getClassification() != null && this.getClassification().isHeadline();
} }

View File

@ -0,0 +1,34 @@
package com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model;
public enum PageBlockType {
H1,
H2,
H3,
H4,
HEADER,
FOOTER,
TITLE,
PARAGRAPH,
PARAGRAPH_BOLD,
PARAGRAPH_ITALIC,
PARAGRAPH_UNKNOWN,
OTHER,
TABLE;
public static PageBlockType getHeadlineType(int i) {
return switch (i) {
case 1 -> PageBlockType.H1;
case 2 -> PageBlockType.H2;
case 3 -> PageBlockType.H3;
default -> PageBlockType.H4;
};
}
public boolean isHeadline() {
return this.equals(H1) || this.equals(H2) || this.equals(H3) || this.equals(H4);
}
}

View File

@ -12,6 +12,7 @@ import java.util.stream.Collectors;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.AbstractPageBlock; import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.AbstractPageBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.PageBlockType;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock; import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
import lombok.Getter; import lombok.Getter;
@ -39,7 +40,7 @@ public class TablePageBlock extends AbstractPageBlock {
minY = area.getBottom(); minY = area.getBottom();
maxX = area.getRight(); maxX = area.getRight();
maxY = area.getTop(); maxY = area.getTop();
classification = "TablePageBlock"; classification = PageBlockType.TABLE;
this.rotation = rotation; this.rotation = rotation;
} }

View File

@ -5,6 +5,7 @@ import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.AbstractPageBlock; import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.AbstractPageBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.PageBlockType;
import com.iqser.red.service.redaction.v1.server.redaction.utils.TextNormalizationUtilities; import com.iqser.red.service.redaction.v1.server.redaction.utils.TextNormalizationUtilities;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
@ -45,7 +46,7 @@ public class ClassificationTextBlock extends AbstractPageBlock {
private float highestFontSize; private float highestFontSize;
@JsonIgnore @JsonIgnore
private String classification; private PageBlockType classification;
@JsonIgnore @JsonIgnore

View File

@ -40,7 +40,7 @@ public class BlockificationService {
int indexOnPage = 0; int indexOnPage = 0;
List<TextPositionSequence> chunkWords = new ArrayList<>(); List<TextPositionSequence> chunkWords = new ArrayList<>();
List<AbstractPageBlock> chunkBlockList1 = new ArrayList<>(); List<AbstractPageBlock> chunkBlockList = new ArrayList<>();
float minX = 1000, maxX = 0, minY = 1000, maxY = 0; float minX = 1000, maxX = 0, minY = 1000, maxY = 0;
TextPositionSequence prev = null; TextPositionSequence prev = null;
@ -60,14 +60,14 @@ public class BlockificationService {
if (prev != null && (lineSeparation || startFromTop || splitByX || splitByDir || isSplitByRuling)) { if (prev != null && (lineSeparation || startFromTop || splitByX || splitByDir || isSplitByRuling)) {
Orientation prevOrientation = null; Orientation prevOrientation = null;
if (!chunkBlockList1.isEmpty()) { if (!chunkBlockList.isEmpty()) {
prevOrientation = chunkBlockList1.get(chunkBlockList1.size() - 1).getOrientation(); prevOrientation = chunkBlockList.get(chunkBlockList.size() - 1).getOrientation();
} }
ClassificationTextBlock cb1 = buildTextBlock(chunkWords, indexOnPage); ClassificationTextBlock cb1 = buildTextBlock(chunkWords, indexOnPage);
indexOnPage++; indexOnPage++;
chunkBlockList1.add(cb1); chunkBlockList.add(cb1);
chunkWords = new ArrayList<>(); chunkWords = new ArrayList<>();
if (splitByX && !isSplitByRuling) { if (splitByX && !isSplitByRuling) {
@ -108,10 +108,10 @@ public class BlockificationService {
ClassificationTextBlock cb1 = buildTextBlock(chunkWords, indexOnPage); ClassificationTextBlock cb1 = buildTextBlock(chunkWords, indexOnPage);
if (cb1 != null) { if (cb1 != null) {
chunkBlockList1.add(cb1); chunkBlockList.add(cb1);
} }
Iterator<AbstractPageBlock> itty = chunkBlockList1.iterator(); Iterator<AbstractPageBlock> itty = chunkBlockList.iterator();
ClassificationTextBlock previousLeft = null; ClassificationTextBlock previousLeft = null;
ClassificationTextBlock previousRight = null; ClassificationTextBlock previousRight = null;
@ -141,7 +141,7 @@ public class BlockificationService {
} }
} }
itty = chunkBlockList1.iterator(); itty = chunkBlockList.iterator();
ClassificationTextBlock previous = null; ClassificationTextBlock previous = null;
while (itty.hasNext()) { while (itty.hasNext()) {
ClassificationTextBlock block = (ClassificationTextBlock) itty.next(); ClassificationTextBlock block = (ClassificationTextBlock) itty.next();
@ -157,7 +157,7 @@ public class BlockificationService {
previous = block; previous = block;
} }
return new ClassificationPage(chunkBlockList1); return new ClassificationPage(chunkBlockList);
} }
@ -254,7 +254,7 @@ public class BlockificationService {
verticalRulingLines, verticalRulingLines,
word.getDir().getDegrees(), word.getDir().getDegrees(),
word.getPageWidth(), word.getPageWidth(),
word.getPageHeight()); // word.getPageHeight());
} }

View File

@ -17,6 +17,9 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.ut
@Service @Service
public class BodyTextFrameService { public class BodyTextFrameService {
private static final float APPROXIMATE_HEADER_LINE_COUNT = 2.9f;
/** /**
* Adjusts and sets the body text frame to a page. * Adjusts and sets the body text frame to a page.
* Note: This needs to use Pdf Coordinate System where {0,0} rotated with the page rotation. * Note: This needs to use Pdf Coordinate System where {0,0} rotated with the page rotation.
@ -84,7 +87,7 @@ public class BodyTextFrameService {
} }
float approxLineCount = PositionUtils.getApproxLineCount(textBlock); float approxLineCount = PositionUtils.getApproxLineCount(textBlock);
if (approxLineCount < 2.9f) { if (approxLineCount < APPROXIMATE_HEADER_LINE_COUNT) {
continue; continue;
} }

View File

@ -9,6 +9,7 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlo
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.AbstractPageBlock; import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.AbstractPageBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.ClassificationDocument; import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.ClassificationDocument;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.ClassificationPage; import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.ClassificationPage;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.PageBlockType;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock; import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.utils.PositionUtils; import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.utils.PositionUtils;
@ -53,21 +54,21 @@ public class ClassificationService {
var bodyTextFrame = page.getBodyTextFrame(); var bodyTextFrame = page.getBodyTextFrame();
if (document.getFontSizeCounter().getMostPopular() == null) { if (document.getFontSizeCounter().getMostPopular() == null) {
textBlock.setClassification("Other"); textBlock.setClassification(PageBlockType.OTHER);
return; return;
} }
if (PositionUtils.isOverBodyTextFrame(bodyTextFrame, textBlock, page.getRotation()) && (document.getFontSizeCounter() if (PositionUtils.isOverBodyTextFrame(bodyTextFrame, textBlock, page.getRotation()) && (document.getFontSizeCounter()
.getMostPopular() == null || textBlock.getHighestFontSize() <= document.getFontSizeCounter().getMostPopular())) { .getMostPopular() == null || textBlock.getHighestFontSize() <= document.getFontSizeCounter().getMostPopular())) {
textBlock.setClassification("Header"); textBlock.setClassification(PageBlockType.HEADER);
} else if (PositionUtils.isUnderBodyTextFrame(bodyTextFrame, textBlock, page.getRotation()) && (document.getFontSizeCounter() } else if (PositionUtils.isUnderBodyTextFrame(bodyTextFrame, textBlock, page.getRotation()) && (document.getFontSizeCounter()
.getMostPopular() == null || textBlock.getHighestFontSize() <= document.getFontSizeCounter().getMostPopular())) { .getMostPopular() == null || textBlock.getHighestFontSize() <= document.getFontSizeCounter().getMostPopular())) {
textBlock.setClassification("Footer"); textBlock.setClassification(PageBlockType.FOOTER);
} else if (page.getPageNumber() == 1 && (PositionUtils.getHeightDifferenceBetweenChunkWordAndDocumentWord(textBlock, } else if (page.getPageNumber() == 1 && (PositionUtils.getHeightDifferenceBetweenChunkWordAndDocumentWord(textBlock,
document.getTextHeightCounter().getMostPopular()) > 2.5 && textBlock.getHighestFontSize() > document.getFontSizeCounter().getMostPopular() || page.getTextBlocks() document.getTextHeightCounter().getMostPopular()) > 2.5 && textBlock.getHighestFontSize() > document.getFontSizeCounter().getMostPopular() || page.getTextBlocks()
.size() == 1)) { .size() == 1)) {
if (!Pattern.matches("[0-9]+", textBlock.toString())) { if (!Pattern.matches("[0-9]+", textBlock.toString())) {
textBlock.setClassification("Title"); textBlock.setClassification(PageBlockType.TITLE);
} }
} else if (textBlock.getMostPopularWordFontSize() > document.getFontSizeCounter() } else if (textBlock.getMostPopularWordFontSize() > document.getFontSizeCounter()
.getMostPopular() && PositionUtils.getApproxLineCount(textBlock) < 4.9 && (textBlock.getMostPopularWordStyle().equals("bold") || !document.getFontStyleCounter() .getMostPopular() && PositionUtils.getApproxLineCount(textBlock) < 4.9 && (textBlock.getMostPopularWordStyle().equals("bold") || !document.getFontStyleCounter()
@ -80,36 +81,34 @@ public class ClassificationService {
for (int i = 1; i <= headlineFontSizes.size(); i++) { for (int i = 1; i <= headlineFontSizes.size(); i++) {
if (textBlock.getMostPopularWordFontSize() == headlineFontSizes.get(i - 1)) { if (textBlock.getMostPopularWordFontSize() == headlineFontSizes.get(i - 1)) {
textBlock.setClassification("H " + i); textBlock.setClassification(PageBlockType.getHeadlineType(i));
document.setHeadlines(true); document.setHeadlines(true);
} }
} }
} else if (!textBlock.getText().startsWith("TablePageBlock ") && !textBlock.getText().startsWith("Figure ") && PositionUtils.isWithinBodyTextFrame(bodyTextFrame, } else if (!textBlock.getText().startsWith("Figure ") && PositionUtils.isWithinBodyTextFrame(bodyTextFrame, textBlock) && textBlock.getMostPopularWordStyle()
textBlock) && textBlock.getMostPopularWordStyle().equals("bold") && !document.getFontStyleCounter() .equals("bold") && !document.getFontStyleCounter().getMostPopular().equals("bold") && PositionUtils.getApproxLineCount(textBlock) < 2.9 && textBlock.getSequences()
.getMostPopular()
.equals("bold") && PositionUtils.getApproxLineCount(textBlock) < 2.9 && textBlock.getSequences()
.get(0) .get(0)
.getTextPositions() .getTextPositions()
.get(0) .get(0)
.getFontSizeInPt() >= textBlock.getMostPopularWordFontSize()) { .getFontSizeInPt() >= textBlock.getMostPopularWordFontSize()) {
textBlock.setClassification("H " + (headlineFontSizes.size() + 1)); textBlock.setClassification(PageBlockType.getHeadlineType(headlineFontSizes.size() + 1));
document.setHeadlines(true); document.setHeadlines(true);
} else if (PositionUtils.isWithinBodyTextFrame(bodyTextFrame, textBlock) && textBlock.getMostPopularWordFontSize() == document.getFontSizeCounter() } else if (PositionUtils.isWithinBodyTextFrame(bodyTextFrame, textBlock) && textBlock.getMostPopularWordFontSize() == document.getFontSizeCounter()
.getMostPopular() && textBlock.getMostPopularWordStyle().equals("bold") && !document.getFontStyleCounter().getMostPopular().equals("bold")) { .getMostPopular() && textBlock.getMostPopularWordStyle().equals("bold") && !document.getFontStyleCounter().getMostPopular().equals("bold")) {
textBlock.setClassification("TextBlock Bold"); textBlock.setClassification(PageBlockType.PARAGRAPH_BOLD);
} else if (PositionUtils.isWithinBodyTextFrame(bodyTextFrame, textBlock) && textBlock.getMostPopularWordFont() } else if (PositionUtils.isWithinBodyTextFrame(bodyTextFrame, textBlock) && textBlock.getMostPopularWordFont()
.equals(document.getFontCounter().getMostPopular()) && textBlock.getMostPopularWordStyle() .equals(document.getFontCounter().getMostPopular()) && textBlock.getMostPopularWordStyle()
.equals(document.getFontStyleCounter().getMostPopular()) && textBlock.getMostPopularWordFontSize() == document.getFontSizeCounter().getMostPopular()) { .equals(document.getFontStyleCounter().getMostPopular()) && textBlock.getMostPopularWordFontSize() == document.getFontSizeCounter().getMostPopular()) {
textBlock.setClassification("TextBlock"); textBlock.setClassification(PageBlockType.PARAGRAPH);
} else if (PositionUtils.isWithinBodyTextFrame(bodyTextFrame, textBlock) && textBlock.getMostPopularWordFontSize() == document.getFontSizeCounter() } else if (PositionUtils.isWithinBodyTextFrame(bodyTextFrame, textBlock) && textBlock.getMostPopularWordFontSize() == document.getFontSizeCounter()
.getMostPopular() && textBlock.getMostPopularWordStyle().equals("italic") && !document.getFontStyleCounter() .getMostPopular() && textBlock.getMostPopularWordStyle().equals("italic") && !document.getFontStyleCounter()
.getMostPopular() .getMostPopular()
.equals("italic") && PositionUtils.getApproxLineCount(textBlock) < 2.9) { .equals("italic") && PositionUtils.getApproxLineCount(textBlock) < 2.9) {
textBlock.setClassification("TextBlock Italic"); textBlock.setClassification(PageBlockType.PARAGRAPH_ITALIC);
} else if (PositionUtils.isWithinBodyTextFrame(bodyTextFrame, textBlock)) { } else if (PositionUtils.isWithinBodyTextFrame(bodyTextFrame, textBlock)) {
textBlock.setClassification("TextBlock Unknown"); textBlock.setClassification(PageBlockType.PARAGRAPH_UNKNOWN);
} else { } else {
textBlock.setClassification("Other"); textBlock.setClassification(PageBlockType.OTHER);
} }
} }

View File

@ -16,6 +16,7 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.mo
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.ClassificationHeader; import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.ClassificationHeader;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.ClassificationPage; import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.ClassificationPage;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.ClassificationSection; import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.ClassificationSection;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.PageBlockType;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.image.ClassifiedImage; import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.image.ClassifiedImage;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Cell; import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Cell;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.TablePageBlock; import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.TablePageBlock;
@ -52,22 +53,22 @@ public class SectionsBuilderService {
current.setPage(page.getPageNumber()); current.setPage(page.getPageNumber());
if (current.getClassification().equals("Header")) { if (current.getClassification().equals(PageBlockType.HEADER)) {
header.add((ClassificationTextBlock) current); header.add((ClassificationTextBlock) current);
continue; continue;
} }
if (current.getClassification().equals("Footer")) { if (current.getClassification().equals(PageBlockType.FOOTER)) {
footer.add((ClassificationTextBlock) current); footer.add((ClassificationTextBlock) current);
continue; continue;
} }
if (current.getClassification().equals("Other")) { if (current.getClassification().equals(PageBlockType.OTHER)) {
unclassifiedText.add((ClassificationTextBlock) current); unclassifiedText.add((ClassificationTextBlock) current);
continue; continue;
} }
if (prev != null && current.getClassification().startsWith("H ") && !prev.getClassification().startsWith("H ") || !document.isHeadlines()) { if (prev != null && current.getClassification().isHeadline() && !prev.getClassification().isHeadline() || !document.isHeadlines()) {
ClassificationSection chunkBlock = buildTextBlock(chunkWords, lastHeadline); ClassificationSection chunkBlock = buildTextBlock(chunkWords, lastHeadline);
chunkBlock.setHeadline(lastHeadline); chunkBlock.setHeadline(lastHeadline);
if (document.isHeadlines()) { if (document.isHeadlines()) {

View File

@ -93,7 +93,7 @@ public class DocumentGraphMapper {
} else { } else {
pages.forEach(page -> page.getMainBody().add(node)); pages.forEach(page -> page.getMainBody().add(node));
} }
newEntries.add(DocumentTree.Entry.builder().treeId(treeId).type(entryData.getType()).children(buildEntries(entryData.getSubEntries(), context)).node(node).build()); newEntries.add(DocumentTree.Entry.builder().treeId(treeId).children(buildEntries(entryData.getSubEntries(), context)).node(node).build());
} }
return newEntries; return newEntries;
} }

View File

@ -26,7 +26,6 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.no
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Header; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Header;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Headline; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Headline;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Image; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Image;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.NodeType;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Page; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Page;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Paragraph; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Paragraph;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Section; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Section;
@ -87,7 +86,7 @@ public class DocumentGraphFactory {
List<ClassificationTextBlock> textBlocks = new LinkedList<>(textBlocksToMerge); List<ClassificationTextBlock> textBlocks = new LinkedList<>(textBlocksToMerge);
textBlocks.add(originalTextBlock); textBlocks.add(originalTextBlock);
AtomicTextBlock textBlock = context.textBlockFactory.buildAtomicTextBlock(TextPositionOperations.mergeAndSortTextPositionSequenceByYThenX(textBlocks), node, context, page); AtomicTextBlock textBlock = context.textBlockFactory.buildAtomicTextBlock(TextPositionOperations.mergeAndSortTextPositionSequenceByYThenX(textBlocks), node, context, page);
List<Integer> treeId = context.documentTree.createNewChildEntryAndReturnId(parentNode, node.getType(), node); List<Integer> treeId = context.documentTree.createNewChildEntryAndReturnId(parentNode, node);
node.setLeafTextBlock(textBlock); node.setLeafTextBlock(textBlock);
node.setTreeId(treeId); node.setTreeId(treeId);
} }
@ -107,7 +106,7 @@ public class DocumentGraphFactory {
.build(); .build();
page.getMainBody().add(imageNode); page.getMainBody().add(imageNode);
List<Integer> tocId = context.getDocumentTree().createNewChildEntryAndReturnId(section, NodeType.IMAGE, imageNode); List<Integer> tocId = context.getDocumentTree().createNewChildEntryAndReturnId(section, imageNode);
imageNode.setTreeId(tocId); imageNode.setTreeId(tocId);
} }
@ -152,7 +151,7 @@ public class DocumentGraphFactory {
footer, footer,
context, context,
page); page);
List<Integer> tocId = context.getDocumentTree().createNewMainEntryAndReturnId(NodeType.FOOTER, footer); List<Integer> tocId = context.getDocumentTree().createNewMainEntryAndReturnId(footer);
footer.setTreeId(tocId); footer.setTreeId(tocId);
footer.setLeafTextBlock(textBlock); footer.setLeafTextBlock(textBlock);
page.setFooter(footer); page.setFooter(footer);
@ -164,7 +163,7 @@ public class DocumentGraphFactory {
Page page = context.getPage(textBlocks.get(0).getPage()); Page page = context.getPage(textBlocks.get(0).getPage());
Header header = Header.builder().documentTree(context.getDocumentTree()).build(); Header header = Header.builder().documentTree(context.getDocumentTree()).build();
AtomicTextBlock textBlock = context.textBlockFactory.buildAtomicTextBlock(TextPositionOperations.mergeAndSortTextPositionSequenceByYThenX(textBlocks), header, 0, page); AtomicTextBlock textBlock = context.textBlockFactory.buildAtomicTextBlock(TextPositionOperations.mergeAndSortTextPositionSequenceByYThenX(textBlocks), header, 0, page);
List<Integer> tocId = context.getDocumentTree().createNewMainEntryAndReturnId(NodeType.HEADER, header); List<Integer> tocId = context.getDocumentTree().createNewMainEntryAndReturnId(header);
header.setTreeId(tocId); header.setTreeId(tocId);
header.setLeafTextBlock(textBlock); header.setLeafTextBlock(textBlock);
page.setHeader(header); page.setHeader(header);
@ -176,7 +175,7 @@ public class DocumentGraphFactory {
Page page = context.getPage(pageIndex); Page page = context.getPage(pageIndex);
Footer footer = Footer.builder().documentTree(context.getDocumentTree()).build(); Footer footer = Footer.builder().documentTree(context.getDocumentTree()).build();
AtomicTextBlock textBlock = context.textBlockFactory.emptyTextBlock(footer, context, page); AtomicTextBlock textBlock = context.textBlockFactory.emptyTextBlock(footer, context, page);
List<Integer> tocId = context.getDocumentTree().createNewMainEntryAndReturnId(NodeType.FOOTER, footer); List<Integer> tocId = context.getDocumentTree().createNewMainEntryAndReturnId(footer);
footer.setTreeId(tocId); footer.setTreeId(tocId);
footer.setLeafTextBlock(textBlock); footer.setLeafTextBlock(textBlock);
page.setFooter(footer); page.setFooter(footer);
@ -188,7 +187,7 @@ public class DocumentGraphFactory {
Page page = context.getPage(pageIndex); Page page = context.getPage(pageIndex);
Header header = Header.builder().documentTree(context.getDocumentTree()).build(); Header header = Header.builder().documentTree(context.getDocumentTree()).build();
AtomicTextBlock textBlock = context.textBlockFactory.emptyTextBlock(header, 0, page); AtomicTextBlock textBlock = context.textBlockFactory.emptyTextBlock(header, 0, page);
List<Integer> tocId = context.getDocumentTree().createNewMainEntryAndReturnId(NodeType.HEADER, header); List<Integer> tocId = context.getDocumentTree().createNewMainEntryAndReturnId(header);
header.setTreeId(tocId); header.setTreeId(tocId);
header.setLeafTextBlock(textBlock); header.setLeafTextBlock(textBlock);
page.setHeader(header); page.setHeader(header);

View File

@ -44,19 +44,19 @@ public class SearchTextWithTextPositionFactory {
} }
previousTextPosition = RedTextPosition.builder().unicode(" ").position(previousTextPosition.getPosition()).build(); previousTextPosition = RedTextPosition.builder().unicode(" ").position(previousTextPosition.getPosition()).build();
context.sb.append(" "); context.stringBuilder.append(" ");
context.stringIdxToPositionIdx.add(context.positionIdx); context.stringIdxToPositionIdx.add(context.positionIdx);
++context.stringIdx; ++context.stringIdx;
} }
assert context.sb.length() == context.stringIdxToPositionIdx.size(); assert context.stringBuilder.length() == context.stringIdxToPositionIdx.size();
List<Rectangle2D> positions = sequences.stream() List<Rectangle2D> positions = sequences.stream()
.flatMap(sequence -> sequence.getTextPositions().stream().map(textPosition -> mapRedTextPositionToInitialUserSpace(textPosition, sequence))) .flatMap(sequence -> sequence.getTextPositions().stream().map(textPosition -> mapRedTextPositionToInitialUserSpace(textPosition, sequence)))
.toList(); .toList();
return SearchTextWithTextPositionDto.builder() return SearchTextWithTextPositionDto.builder()
.searchText(context.sb.toString()) .searchText(context.stringBuilder.toString())
.lineBreaks(context.lineBreaksStringIdx) .lineBreaks(context.lineBreaksStringIdx)
.stringCoordsToPositionCoords(context.stringIdxToPositionIdx) .stringCoordsToPositionCoords(context.stringIdxToPositionIdx)
.positions(positions) .positions(positions)
@ -66,7 +66,7 @@ public class SearchTextWithTextPositionFactory {
private static void appendCurrentTextPosition(Context context, RedTextPosition currentTextPosition) { private static void appendCurrentTextPosition(Context context, RedTextPosition currentTextPosition) {
context.sb.append(currentTextPosition.getUnicode()); context.stringBuilder.append(currentTextPosition.getUnicode());
// unicode characters with more than 16-bit encoding have a length > 1 in java strings // unicode characters with more than 16-bit encoding have a length > 1 in java strings
for (int j = 0; j < currentTextPosition.getUnicode().length(); j++) { for (int j = 0; j < currentTextPosition.getUnicode().length(); j++) {
@ -81,7 +81,7 @@ public class SearchTextWithTextPositionFactory {
if (isLineBreak(currentTextPosition, previousTextPosition)) { if (isLineBreak(currentTextPosition, previousTextPosition)) {
if (lastHyphenDirectlyBeforeLineBreak(context)) { if (lastHyphenDirectlyBeforeLineBreak(context)) {
context.sb.delete(context.lastHyphenIdx, context.sb.length()); context.stringBuilder.delete(context.lastHyphenIdx, context.stringBuilder.length());
context.stringIdxToPositionIdx = context.stringIdxToPositionIdx.subList(0, context.lastHyphenIdx); context.stringIdxToPositionIdx = context.stringIdxToPositionIdx.subList(0, context.lastHyphenIdx);
context.stringIdx = context.lastHyphenIdx; context.stringIdx = context.lastHyphenIdx;
context.lastHyphenIdx = -3; context.lastHyphenIdx = -3;
@ -166,14 +166,14 @@ public class SearchTextWithTextPositionFactory {
List<Integer> stringIdxToPositionIdx = new LinkedList<>(); List<Integer> stringIdxToPositionIdx = new LinkedList<>();
List<Integer> lineBreaksStringIdx = new LinkedList<>(); List<Integer> lineBreaksStringIdx = new LinkedList<>();
StringBuilder sb = new StringBuilder(); StringBuilder stringBuilder = new StringBuilder();
int stringIdx; int stringIdx;
int positionIdx; int positionIdx;
// when checking for a hyphen linebreak, we need to check after a linebreak if the last hyphen was less than three symbols away. // when checking for a hyphen linebreak, we need to check after a linebreak if the last hyphen was less than three symbols away.
// We detect a linebreak as either a "\n" character or if two adjacent symbol's position differ in y-coordinates by at least one character height. // We detect a linebreak as either a "\n" character or if two adjacent symbol's position differ in y-coordinates by at least one character height.
// If there is a hyphen linebreak, the hyphen will be 1 position in front of a "\n" or 2 positions in front of the character which has a lower y-coordinate // If there is a hyphen linebreak, the hyphen will be 1 position in front of a "\n" or 2 positions in front of the character which has a lower y-coordinate
// This is why, we need to initialize this to <= -3, otherwise, if the very first symbol is a \n we would detect a hyphen linebreak that isn't there. // This is why, we need to initialize this to < -2, otherwise, if the very first symbol is a \n we would detect a hyphen linebreak that isn't there.
// Also, Integer.MIN_VALUE is a bad idea due to potential overflow during arithmetic operations. This is why the default should be -3. // Also, Integer.MIN_VALUE is a bad idea due to potential overflow during arithmetic operations. This is why the default should be -3.
int lastHyphenIdx = -3; int lastHyphenIdx = -3;

View File

@ -14,7 +14,6 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.mo
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.TablePageBlock; import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.TablePageBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock; import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.GenericSemanticNode; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.GenericSemanticNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.NodeType;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Page; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Page;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Section; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Section;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.TableMergingUtility; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.TableMergingUtility;
@ -54,9 +53,9 @@ public class SectionNodeFactory {
private static List<Integer> getTreeId(GenericSemanticNode parentNode, DocumentGraphFactory.Context context, Section section) { private static List<Integer> getTreeId(GenericSemanticNode parentNode, DocumentGraphFactory.Context context, Section section) {
if (parentNode == null) { if (parentNode == null) {
return context.getDocumentTree().createNewMainEntryAndReturnId(NodeType.SECTION, section); return context.getDocumentTree().createNewMainEntryAndReturnId(section);
} else { } else {
return context.getDocumentTree().createNewChildEntryAndReturnId(parentNode, NodeType.SECTION, section); return context.getDocumentTree().createNewChildEntryAndReturnId(parentNode, section);
} }
} }

View File

@ -12,7 +12,6 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.mo
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.TablePageBlock; import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.TablePageBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextPositionSequence; import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextPositionSequence;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.GenericSemanticNode; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.GenericSemanticNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.NodeType;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Page; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Page;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SemanticNode; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SemanticNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Table; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Table;
@ -37,7 +36,7 @@ public class TableNodeFactory {
pages.forEach(page -> addTableToPage(page, parentNode, table)); pages.forEach(page -> addTableToPage(page, parentNode, table));
List<Integer> tocId = context.getDocumentTree().createNewChildEntryAndReturnId(parentNode, NodeType.TABLE, table); List<Integer> tocId = context.getDocumentTree().createNewChildEntryAndReturnId(parentNode, table);
table.setTreeId(tocId); table.setTreeId(tocId);
addTableCells(mergedRows, table, context); addTableCells(mergedRows, table, context);
@ -104,7 +103,7 @@ public class TableNodeFactory {
TextBlock textBlock; TextBlock textBlock;
List<Integer> tocId = context.getDocumentTree().createNewTableChildEntryAndReturnId(tableNode, NodeType.TABLE_CELL, tableCell); List<Integer> tocId = context.getDocumentTree().createNewTableChildEntryAndReturnId(tableNode, tableCell);
tableCell.setTreeId(tocId); tableCell.setTreeId(tocId);
if (cell.getTextBlocks().isEmpty()) { if (cell.getTextBlocks().isEmpty()) {
@ -142,8 +141,7 @@ public class TableNodeFactory {
private boolean firstTextBlockIsHeadline(Cell cell) { private boolean firstTextBlockIsHeadline(Cell cell) {
String classification = cell.getTextBlocks().get(0).getClassification(); return cell.getTextBlocks().get(0).isHeadline();
return classification != null && classification.startsWith("H");
} }
} }

View File

@ -33,7 +33,7 @@ public class DocumentTree {
public DocumentTree(Document document) { public DocumentTree(Document document) {
root = Entry.builder().treeId(Collections.emptyList()).type(NodeType.DOCUMENT).children(new LinkedList<>()).node(document).build(); root = Entry.builder().treeId(Collections.emptyList()).children(new LinkedList<>()).node(document).build();
} }
@ -43,32 +43,32 @@ public class DocumentTree {
} }
public List<Integer> createNewMainEntryAndReturnId(NodeType nodeType, GenericSemanticNode node) { public List<Integer> createNewMainEntryAndReturnId(GenericSemanticNode node) {
return createNewChildEntryAndReturnIdImpl(Collections.emptyList(), nodeType, node); return createNewChildEntryAndReturnIdImpl(Collections.emptyList(), node);
} }
public List<Integer> createNewChildEntryAndReturnId(GenericSemanticNode parentNode, NodeType nodeType, GenericSemanticNode node) { public List<Integer> createNewChildEntryAndReturnId(GenericSemanticNode parentNode, GenericSemanticNode node) {
return createNewChildEntryAndReturnIdImpl(parentNode.getTreeId(), nodeType, node); return createNewChildEntryAndReturnIdImpl(parentNode.getTreeId(), node);
} }
public List<Integer> createNewChildEntryAndReturnId(GenericSemanticNode parentNode, NodeType nodeType, Table node) { public List<Integer> createNewChildEntryAndReturnId(GenericSemanticNode parentNode, Table node) {
return createNewChildEntryAndReturnIdImpl(parentNode.getTreeId(), nodeType, node); return createNewChildEntryAndReturnIdImpl(parentNode.getTreeId(), node);
} }
public List<Integer> createNewTableChildEntryAndReturnId(Table parentTable, NodeType nodeType, TableCell tableCell) { public List<Integer> createNewTableChildEntryAndReturnId(Table parentTable, TableCell tableCell) {
return createNewChildEntryAndReturnIdImpl(parentTable.getTreeId(), nodeType, tableCell); return createNewChildEntryAndReturnIdImpl(parentTable.getTreeId(), tableCell);
} }
@SuppressWarnings("PMD.UnusedPrivateMethod") // PMD actually flags this wrong @SuppressWarnings("PMD.UnusedPrivateMethod") // PMD actually flags this wrong
private List<Integer> createNewChildEntryAndReturnIdImpl(List<Integer> parentId, NodeType nodeType, SemanticNode node) { private List<Integer> createNewChildEntryAndReturnIdImpl(List<Integer> parentId, SemanticNode node) {
if (!entryExists(parentId)) { if (!entryExists(parentId)) {
throw new UnsupportedOperationException(format("parentId %s does not exist!", parentId)); throw new UnsupportedOperationException(format("parentId %s does not exist!", parentId));
@ -77,7 +77,7 @@ public class DocumentTree {
Entry parent = getEntryById(parentId); Entry parent = getEntryById(parentId);
List<Integer> newId = new LinkedList<>(parentId); List<Integer> newId = new LinkedList<>(parentId);
newId.add(parent.children.size()); newId.add(parent.children.size());
parent.children.add(Entry.builder().treeId(newId).node(node).type(nodeType).children(new LinkedList<>()).build()); parent.children.add(Entry.builder().treeId(newId).node(node).children(new LinkedList<>()).build());
return newId; return newId;
} }
@ -119,7 +119,7 @@ public class DocumentTree {
public Stream<SemanticNode> streamChildNodesOfType(List<Integer> treeId, NodeType nodeType) { public Stream<SemanticNode> streamChildNodesOfType(List<Integer> treeId, NodeType nodeType) {
return getEntryById(treeId).children.stream().filter(entry -> entry.type.equals(nodeType)).map(Entry::getNode); return getEntryById(treeId).children.stream().filter(entry -> entry.node.getType().equals(nodeType)).map(Entry::getNode);
} }
@ -195,7 +195,6 @@ public class DocumentTree {
public static class Entry { public static class Entry {
List<Integer> treeId; List<Integer> treeId;
NodeType type;
SemanticNode node; SemanticNode node;
List<Entry> children; List<Entry> children;
@ -207,6 +206,12 @@ public class DocumentTree {
} }
public NodeType getType() {
return node.getType();
}
@Override @Override
public int hashCode() { public int hashCode() {