From 567cbc178b425572d13bebedf672f8fb0c27c029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominique=20Eifl=C3=A4nder?= Date: Tue, 17 Oct 2023 15:52:19 +0200 Subject: [PATCH] hotfix: Fixed parsing for specific taas document --- .../TaasBlockificationService.java | 46 ++++++++++-------- .../Wie weiter bei Kristeneinrichtungen.pdf | Bin 0 -> 15371 bytes 2 files changed, 27 insertions(+), 19 deletions(-) create mode 100644 layoutparser-service/layoutparser-service-server/src/test/resources/files/bdr/Wie weiter bei Kristeneinrichtungen.pdf diff --git a/layoutparser-service/layoutparser-service-processor/src/main/java/com/knecon/fforesight/service/layoutparser/processor/services/blockification/TaasBlockificationService.java b/layoutparser-service/layoutparser-service-processor/src/main/java/com/knecon/fforesight/service/layoutparser/processor/services/blockification/TaasBlockificationService.java index f16198d..287d2ba 100644 --- a/layoutparser-service/layoutparser-service-processor/src/main/java/com/knecon/fforesight/service/layoutparser/processor/services/blockification/TaasBlockificationService.java +++ b/layoutparser-service/layoutparser-service-processor/src/main/java/com/knecon/fforesight/service/layoutparser/processor/services/blockification/TaasBlockificationService.java @@ -4,18 +4,6 @@ package com.knecon.fforesight.service.layoutparser.processor.services.blockifica // TODO: figure out, why this fails the build // import static com.knecon.fforesight.service.layoutparser.processor.services.factory.SearchTextWithTextPositionFactory.HEIGHT_PADDING; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Stream; - -import org.springframework.stereotype.Service; - import com.knecon.fforesight.service.layoutparser.processor.model.AbstractPageBlock; import com.knecon.fforesight.service.layoutparser.processor.model.ClassificationPage; import com.knecon.fforesight.service.layoutparser.processor.model.Orientation; @@ -23,6 +11,12 @@ import com.knecon.fforesight.service.layoutparser.processor.model.table.Ruling; import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPageBlock; import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPositionSequence; import com.knecon.fforesight.service.layoutparser.processor.utils.RulingTextDirAdjustUtil; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; @Service @SuppressWarnings("all") @@ -33,9 +27,10 @@ public class TaasBlockificationService { private static final float INTERSECTS_Y_THRESHOLD = 4;// 2 * HEIGHT_PADDING // This is exactly 2 times our position height padding. This is required to find boxes that are visually intersecting. private static final int X_GAP_SPLIT_CONSTANT = 50; public static final int X_ALIGNMENT_THRESHOLD = 1; - public static final int SMALL_Y_GAP_THRESHOLD = 5; public static final int NEGATIVE_X_GAP_THRESHOLD = -5; + private Pattern listIdentifier = Pattern.compile("^(?:(?:[1-9]|1\\d|20|[ivxlc]|[a-z])\\s*(?:[.)]))|\\uF0B7", Pattern.CASE_INSENSITIVE); + /** * This method is building blocks by expanding the minX/maxX and minY/maxY value on each word that is not split by the conditions. @@ -80,16 +75,29 @@ public class TaasBlockificationService { List currentTextBlocksToMerge = new LinkedList<>(); textBlocksToMerge.add(currentTextBlocksToMerge); TextPageBlock previousTextBlock = null; + Float lastLineGap = null; for (TextPageBlock currentTextBlock : classificationTextBlocks) { if (previousTextBlock == null) { currentTextBlocksToMerge.add(currentTextBlock); previousTextBlock = currentTextBlock; continue; } + + + Matcher listIdentifierPattern = listIdentifier.matcher(currentTextBlock.getText()); + boolean isListIdentifier = listIdentifierPattern.find(); + + boolean yGap = Math.abs(currentTextBlock.getPdfMaxY() - previousTextBlock.getPdfMinY()) < previousTextBlock.getMostPopularWordHeight() * Y_GAP_SPLIT_HEIGHT_MODIFIER; + + boolean sameFont = previousTextBlock.getMostPopularWordFont().equals(currentTextBlock.getMostPopularWordFont()) && previousTextBlock.getMostPopularWordFontSize() == currentTextBlock.getMostPopularWordFontSize(); +// boolean yGap = previousTextBlock != null && currentTextBlock.getMinYDirAdj() - maxY > Math.min(word.getHeight(), prev.getHeight()) * Y_GAP_SPLIT_HEIGHT_MODIFIER; + boolean alignsXRight = Math.abs(currentTextBlock.getPdfMaxX() - previousTextBlock.getPdfMaxX()) < X_ALIGNMENT_THRESHOLD; - boolean smallYGap = Math.abs(currentTextBlock.getPdfMaxY() - previousTextBlock.getPdfMinY()) < SMALL_Y_GAP_THRESHOLD; - if (alignsXRight && smallYGap) { + boolean alignsXLeft = Math.abs(currentTextBlock.getPdfMinX() - previousTextBlock.getPdfMinX()) < X_ALIGNMENT_THRESHOLD; +// boolean smallYGap = Math.abs(currentTextBlock.getPdfMaxY() - previousTextBlock.getPdfMinY()) < yGap; + if (yGap && sameFont && !isListIdentifier) { currentTextBlocksToMerge.add(currentTextBlock); + } else { currentTextBlocksToMerge = new LinkedList<>(); currentTextBlocksToMerge.add(currentTextBlock); @@ -170,8 +178,8 @@ public class TaasBlockificationService { private List constructFineGranularTextPageBlocks(List textPositions, - List horizontalRulingLines, - List verticalRulingLines) { + List horizontalRulingLines, + List verticalRulingLines) { int indexOnPage = 0; List wordClusterToCombine = new ArrayList<>(); @@ -180,13 +188,13 @@ public class TaasBlockificationService { float minX = 1000, maxX = 0, minY = 1000, maxY = 0; TextPositionSequence prev = null; // TODO: make static final constant - var listIdentitifier = Pattern.compile("\\b(?:[1-9]|1\\d|20|[ivxlc]|[a-z])\\s*(?:[.)])", Pattern.CASE_INSENSITIVE); + boolean wasSplitted = false; Float splitX1 = null; for (TextPositionSequence word : textPositions) { - Matcher listIdentifierPattern = listIdentitifier.matcher(word.toString()); + Matcher listIdentifierPattern = listIdentifier.matcher(word.toString()); boolean yGap = prev != null && word.getMinYDirAdj() - maxY > Math.min(word.getHeight(), prev.getHeight()) * Y_GAP_SPLIT_HEIGHT_MODIFIER; boolean sameLine = prev != null && equalsWithThreshold(prev.getMinYDirAdj(), word.getMinYDirAdj()); diff --git a/layoutparser-service/layoutparser-service-server/src/test/resources/files/bdr/Wie weiter bei Kristeneinrichtungen.pdf b/layoutparser-service/layoutparser-service-server/src/test/resources/files/bdr/Wie weiter bei Kristeneinrichtungen.pdf new file mode 100644 index 0000000000000000000000000000000000000000..1ca7c468c76a616e8b115192efe0355ea2b4b3a1 GIT binary patch literal 15371 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