From 6a0661cf09d555e03bb0f9373c1f7b1144a9fc2f Mon Sep 17 00:00:00 2001 From: maverickstuder Date: Wed, 15 May 2024 13:51:49 +0200 Subject: [PATCH] RED-7074: Design Subsection section tree structure algorithm * bugfix --- .../model/outline/OutlineObject.java | 2 +- .../model/outline/TableOfContents.java | 2 +- .../BlockificationPostprocessingService.java | 85 ++++++++++-------- .../HeadlineClassificationService.java | 11 +-- .../services/factory/TableNodeFactory.java | 2 +- .../HeadlinesGoldStandardIntegrationTest.java | 12 +-- .../server/graph/ViewerDocumentTest.java | 3 +- .../services/RulingCleaningServiceTest.java | 9 +- .../DontMergeNonConsecutiveTables.pdf | Bin 16012 -> 0 bytes 9 files changed, 69 insertions(+), 57 deletions(-) delete mode 100644 layoutparser-service/layoutparser-service-server/src/test/resources/files/SinglePages/DontMergeNonConsecutiveTables.pdf diff --git a/layoutparser-service/layoutparser-service-processor/src/main/java/com/knecon/fforesight/service/layoutparser/processor/model/outline/OutlineObject.java b/layoutparser-service/layoutparser-service-processor/src/main/java/com/knecon/fforesight/service/layoutparser/processor/model/outline/OutlineObject.java index b6b9efe..6f8af6b 100644 --- a/layoutparser-service/layoutparser-service-processor/src/main/java/com/knecon/fforesight/service/layoutparser/processor/model/outline/OutlineObject.java +++ b/layoutparser-service/layoutparser-service-processor/src/main/java/com/knecon/fforesight/service/layoutparser/processor/model/outline/OutlineObject.java @@ -16,7 +16,7 @@ public class OutlineObject { private Point2D point; private final int treeDepth; - private boolean found = false; + private boolean found; public OutlineObject(String title, int pageNumber, Point2D point2D, int depth) { diff --git a/layoutparser-service/layoutparser-service-processor/src/main/java/com/knecon/fforesight/service/layoutparser/processor/model/outline/TableOfContents.java b/layoutparser-service/layoutparser-service-processor/src/main/java/com/knecon/fforesight/service/layoutparser/processor/model/outline/TableOfContents.java index 72ee8a2..8d80cd3 100644 --- a/layoutparser-service/layoutparser-service-processor/src/main/java/com/knecon/fforesight/service/layoutparser/processor/model/outline/TableOfContents.java +++ b/layoutparser-service/layoutparser-service-processor/src/main/java/com/knecon/fforesight/service/layoutparser/processor/model/outline/TableOfContents.java @@ -97,7 +97,7 @@ public class TableOfContents implements Iterable { private final Stack> stack = new Stack<>(); - public TableOfContentItemIterator(List mainSections) { + TableOfContentItemIterator(List mainSections) { stack.push(mainSections.iterator()); } diff --git a/layoutparser-service/layoutparser-service-processor/src/main/java/com/knecon/fforesight/service/layoutparser/processor/services/blockification/BlockificationPostprocessingService.java b/layoutparser-service/layoutparser-service-processor/src/main/java/com/knecon/fforesight/service/layoutparser/processor/services/blockification/BlockificationPostprocessingService.java index a38780a..7863e71 100644 --- a/layoutparser-service/layoutparser-service-processor/src/main/java/com/knecon/fforesight/service/layoutparser/processor/services/blockification/BlockificationPostprocessingService.java +++ b/layoutparser-service/layoutparser-service-processor/src/main/java/com/knecon/fforesight/service/layoutparser/processor/services/blockification/BlockificationPostprocessingService.java @@ -206,9 +206,11 @@ public class BlockificationPostprocessingService { if (minDistance == distanceToDirectMatch) { directMatch.setClassification(headlineType); } else if (minDistance == distanceToSplitCandidate) { - List others = splitBlock(classificationPage, splitCandidate, context.sectionIdentifier, outlineObject.getTitle()); - splitCandidate.setClassification(headlineType); - others.forEach(other -> other.setClassification(null)); + SplitBlockResult splitBlockResult = splitBlock(classificationPage, splitCandidate, context.sectionIdentifier, outlineObject.getTitle()); + if (splitBlockResult.modifiedBlockToSplit) { + splitCandidate.setClassification(headlineType); + } + splitBlockResult.otherBlocks.forEach(other -> other.setClassification(null)); } else { var merged = mergeBlocks(classificationPage, bestMergeCandidateCombination); merged.setClassification(headlineType); @@ -217,7 +219,7 @@ public class BlockificationPostprocessingService { } - private List splitBlock(ClassificationPage classificationPage, TextPageBlock blockToSplit, SectionIdentifier sectionIdentifier, String title) { + private SplitBlockResult splitBlock(ClassificationPage classificationPage, TextPageBlock blockToSplit, SectionIdentifier sectionIdentifier, String title) { List otherBlocks = new ArrayList<>(); int blockToSplitIdx = classificationPage.getTextBlocks().indexOf(blockToSplit); @@ -228,12 +230,16 @@ public class BlockificationPostprocessingService { } WordSequenceResult wordSequenceResult = findWordSequence(blockToSplit.getSequences(), headline); - if (wordSequenceResult.inSequence.isEmpty()) { + if (wordSequenceResult.inSequence.isEmpty() && !headline.equals(title)) { wordSequenceResult = findWordSequence(blockToSplit.getSequences(), title); } - blockToSplit.setSequences(wordSequenceResult.inSequence); - blockToSplit.resize(); + boolean modifiedBlockToSplit = false; + if (!wordSequenceResult.inSequence.isEmpty()) { + blockToSplit.setSequences(wordSequenceResult.inSequence); + blockToSplit.resize(); + modifiedBlockToSplit = true; + } if (!wordSequenceResult.preSequence.isEmpty()) { TextPageBlock block = buildTextBlock(wordSequenceResult.preSequence, 0); @@ -246,32 +252,8 @@ public class BlockificationPostprocessingService { classificationPage.getTextBlocks().add(blockToSplitIdx + 1, block); otherBlocks.add(block); } - return otherBlocks; - } - - - public static class WordSequenceResult { - - public List inSequence; - public List preSequence; - public List postSequence; - - - public WordSequenceResult(List inSequence, List preSequence, List postSequence) { - - this.inSequence = inSequence; - this.preSequence = preSequence; - this.postSequence = postSequence; - } - - - public WordSequenceResult() { - - this.inSequence = new ArrayList<>(); - this.preSequence = new ArrayList<>(); - this.postSequence = new ArrayList<>(); - } + return new SplitBlockResult(modifiedBlockToSplit, otherBlocks); } @@ -358,11 +340,6 @@ public class BlockificationPostprocessingService { } - public record SplitSequenceResult(TextPositionSequence in, TextPositionSequence out) { - - } - - private static TextPositionSequence createSubSequence(TextPositionSequence sequence, int start, int end) { TextPositionSequence newSeq = new TextPositionSequence(new ArrayList<>(sequence.getTextPositions().subList(start, end)), sequence.getPage()); @@ -502,7 +479,7 @@ public class BlockificationPostprocessingService { private SectionIdentifier sectionIdentifier; - public OutlineProcessionContext(OutlineObject outlineObject) { + OutlineProcessionContext(OutlineObject outlineObject) { this.outlineObject = outlineObject; this.directMatch = null; @@ -513,4 +490,36 @@ public class BlockificationPostprocessingService { } + public static class WordSequenceResult { + + public List inSequence; + public List preSequence; + public List postSequence; + + + public WordSequenceResult(List inSequence, List preSequence, List postSequence) { + + this.inSequence = inSequence; + this.preSequence = preSequence; + this.postSequence = postSequence; + } + + + public WordSequenceResult() { + + this.inSequence = new ArrayList<>(); + this.preSequence = new ArrayList<>(); + this.postSequence = new ArrayList<>(); + } + + } + + public record SplitBlockResult(boolean modifiedBlockToSplit, List otherBlocks) { + + } + + public record SplitSequenceResult(TextPositionSequence in, TextPositionSequence out) { + + } + } diff --git a/layoutparser-service/layoutparser-service-processor/src/main/java/com/knecon/fforesight/service/layoutparser/processor/services/classification/HeadlineClassificationService.java b/layoutparser-service/layoutparser-service-processor/src/main/java/com/knecon/fforesight/service/layoutparser/processor/services/classification/HeadlineClassificationService.java index f8b6ea7..e302321 100644 --- a/layoutparser-service/layoutparser-service-processor/src/main/java/com/knecon/fforesight/service/layoutparser/processor/services/classification/HeadlineClassificationService.java +++ b/layoutparser-service/layoutparser-service-processor/src/main/java/com/knecon/fforesight/service/layoutparser/processor/services/classification/HeadlineClassificationService.java @@ -33,28 +33,29 @@ public class HeadlineClassificationService { } - public void classifyHeadline(TextPageBlock textBlock, PageBlockType headlineType) { + public void classifyHeadline(TextPageBlock textBlock, PageBlockType initialHeadlineType) { TextPageBlock lastHeadline = getLastHeadline(); TextPageBlock lastHeadlineFromOutline = getLastHeadlineFromOutline(); PageBlockType originalClassifiedBlockType = getOriginalClassifiedBlockType(); + PageBlockType finalHeadlineType = initialHeadlineType; if (lastHeadline != null) { if (lastHeadline.equals(lastHeadlineFromOutline)) { - headlineType = PageBlockType.getHeadlineType(getHeadlineNumber(lastHeadline.getClassification()) + 1); + finalHeadlineType = PageBlockType.getHeadlineType(getHeadlineNumber(lastHeadline.getClassification()) + 1); } else if (originalClassifiedBlockType != null && lastHeadline.getClassification() != originalClassifiedBlockType) { PageBlockType lastHeadlineType = lastHeadline.getClassification(); int difference = getHeadlineNumber(originalClassifiedBlockType) - getHeadlineNumber(lastHeadlineType); - headlineType = PageBlockType.getHeadlineType(getHeadlineNumber(headlineType) + difference); + finalHeadlineType = PageBlockType.getHeadlineType(getHeadlineNumber(initialHeadlineType) - difference); } } - setOriginalClassifiedBlockType(headlineType); - textBlock.setClassification(headlineType); + setOriginalClassifiedBlockType(initialHeadlineType); + textBlock.setClassification(finalHeadlineType); setLastHeadline(textBlock); } diff --git a/layoutparser-service/layoutparser-service-processor/src/main/java/com/knecon/fforesight/service/layoutparser/processor/services/factory/TableNodeFactory.java b/layoutparser-service/layoutparser-service-processor/src/main/java/com/knecon/fforesight/service/layoutparser/processor/services/factory/TableNodeFactory.java index 1a097fc..a77bf22 100644 --- a/layoutparser-service/layoutparser-service-processor/src/main/java/com/knecon/fforesight/service/layoutparser/processor/services/factory/TableNodeFactory.java +++ b/layoutparser-service/layoutparser-service-processor/src/main/java/com/knecon/fforesight/service/layoutparser/processor/services/factory/TableNodeFactory.java @@ -150,7 +150,7 @@ public class TableNodeFactory { cell.getTextBlocks() .stream() .map(tb -> (AbstractPageBlock) tb) - .toList(), + .collect(Collectors.toList()), emptyList(), context, document); diff --git a/layoutparser-service/layoutparser-service-server/src/test/java/com/knecon/fforesight/service/layoutparser/server/HeadlinesGoldStandardIntegrationTest.java b/layoutparser-service/layoutparser-service-server/src/test/java/com/knecon/fforesight/service/layoutparser/server/HeadlinesGoldStandardIntegrationTest.java index 274e1e8..1c4514f 100644 --- a/layoutparser-service/layoutparser-service-server/src/test/java/com/knecon/fforesight/service/layoutparser/server/HeadlinesGoldStandardIntegrationTest.java +++ b/layoutparser-service/layoutparser-service-server/src/test/java/com/knecon/fforesight/service/layoutparser/server/HeadlinesGoldStandardIntegrationTest.java @@ -69,10 +69,10 @@ public class HeadlinesGoldStandardIntegrationTest { public void testHeadlineDetection() { List metrics = new ArrayList<>(); - metrics.add(getMetrics("files/syngenta/CustomerFiles/01 - CGA100251 - Acute Oral Toxicity (Up and Down Procedure) - Rat (1).pdf", - "files/headlineTest/01 - CGA100251 - Acute Oral Toxicity (Up and Down Procedure) - Rat (1)_REDACTION_LOG.json")); - metrics.add(getMetrics("files/syngenta/CustomerFiles/91 Trinexapac-ethyl_RAR_01_Volume_1_2018-02-23.pdf", - "files/headlineTest/91 Trinexapac-ethyl_RAR_01_Volume_1_2018-02-23_REDACTION_LOG.json")); + //metrics.add(getMetrics("files/syngenta/CustomerFiles/01 - CGA100251 - Acute Oral Toxicity (Up and Down Procedure) - Rat (1).pdf", + // "files/headlineTest/01 - CGA100251 - Acute Oral Toxicity (Up and Down Procedure) - Rat (1)_REDACTION_LOG.json")); + //metrics.add(getMetrics("files/syngenta/CustomerFiles/91 Trinexapac-ethyl_RAR_01_Volume_1_2018-02-23.pdf", + // "files/headlineTest/91 Trinexapac-ethyl_RAR_01_Volume_1_2018-02-23_REDACTION_LOG.json")); metrics.add(getMetrics("files/syngenta/CustomerFiles/S-Metolachlor_RAR_01_Volume_1_2018-09-06.pdf", "files/headlineTest/S-Metolachlor_RAR_01_Volume_1_2018-09-06_REDACTION_LOG.json")); double precision = metrics.stream().mapToDouble(Metrics::getPrecision).average().orElse(1.0); @@ -96,8 +96,8 @@ public class HeadlinesGoldStandardIntegrationTest { goldStandardLog.getRedactionLogEntry().removeIf(r -> !r.isRedacted() || r.getChanges().get(r.getChanges().size() - 1).getType().equals(ChangeType.REMOVED)); goldStandardLog.getRedactionLogEntry().forEach(e -> goldStandardHeadlines.add(new Headline(e.getPositions().get(0).getPage(), e.getValue()))); - Document documentGraph = DocumentGraphFactory.buildDocumentGraph(LayoutParsingType.REDACT_MANAGER_OLD, - layoutParsingPipeline.parseLayout(LayoutParsingType.REDACT_MANAGER_OLD, + Document documentGraph = DocumentGraphFactory.buildDocumentGraph(LayoutParsingType.REDACT_MANAGER, + layoutParsingPipeline.parseLayout(LayoutParsingType.REDACT_MANAGER, pdfFileResource.getFile(), new ImageServiceResponse(), new TableServiceResponse(), diff --git a/layoutparser-service/layoutparser-service-server/src/test/java/com/knecon/fforesight/service/layoutparser/server/graph/ViewerDocumentTest.java b/layoutparser-service/layoutparser-service-server/src/test/java/com/knecon/fforesight/service/layoutparser/server/graph/ViewerDocumentTest.java index a26754a..0ac3ef5 100644 --- a/layoutparser-service/layoutparser-service-server/src/test/java/com/knecon/fforesight/service/layoutparser/server/graph/ViewerDocumentTest.java +++ b/layoutparser-service/layoutparser-service-server/src/test/java/com/knecon/fforesight/service/layoutparser/server/graph/ViewerDocumentTest.java @@ -27,7 +27,8 @@ public class ViewerDocumentTest extends BuildDocumentTest { @SneakyThrows public void testViewerDocument() { - String fileName = "files/syngenta/CustomerFiles/S-Metolachlor_RAR_01_Volume_1_2018-09-06.pdf"; + String fileName = "files/syngenta/CustomerFiles/90 Trinexapac-ethyl - Peer Review Report Syngenta - March 2018.pdf"; + String tmpFileName = "/tmp/" + Path.of(fileName).getFileName() + "_VIEWER.pdf"; var documentFile = new ClassPathResource(fileName).getFile(); diff --git a/layoutparser-service/layoutparser-service-server/src/test/java/com/knecon/fforesight/service/layoutparser/server/services/RulingCleaningServiceTest.java b/layoutparser-service/layoutparser-service-server/src/test/java/com/knecon/fforesight/service/layoutparser/server/services/RulingCleaningServiceTest.java index a66d540..1a5755b 100644 --- a/layoutparser-service/layoutparser-service-server/src/test/java/com/knecon/fforesight/service/layoutparser/server/services/RulingCleaningServiceTest.java +++ b/layoutparser-service/layoutparser-service-server/src/test/java/com/knecon/fforesight/service/layoutparser/server/services/RulingCleaningServiceTest.java @@ -92,6 +92,7 @@ public class RulingCleaningServiceTest extends BuildDocumentTest { .toList(); for (String pdfFileName : pdfFileNames) { + writeJsons(Path.of(pdfFileName)); } } @@ -100,15 +101,15 @@ public class RulingCleaningServiceTest extends BuildDocumentTest { @SneakyThrows private void writeJsons(Path filename) { - Document documentGraphBefore = DocumentGraphFactory.buildDocumentGraph(LayoutParsingType.REDACT_MANAGER_OLD, - layoutParsingPipeline.parseLayout(LayoutParsingType.REDACT_MANAGER_OLD, + Document documentGraphBefore = DocumentGraphFactory.buildDocumentGraph(LayoutParsingType.REDACT_MANAGER, + layoutParsingPipeline.parseLayout(LayoutParsingType.REDACT_MANAGER, filename.toFile(), new ImageServiceResponse(), new TableServiceResponse(), new VisualLayoutParsingResponse(), Map.of("file",filename.toFile().toString()))); - Document documentGraphAfter = DocumentGraphFactory.buildDocumentGraph(LayoutParsingType.REDACT_MANAGER_OLD, - layoutParsingPipeline.parseLayout(LayoutParsingType.REDACT_MANAGER_OLD, + Document documentGraphAfter = DocumentGraphFactory.buildDocumentGraph(LayoutParsingType.REDACT_MANAGER, + layoutParsingPipeline.parseLayout(LayoutParsingType.REDACT_MANAGER, filename.toFile(), new ImageServiceResponse(), new TableServiceResponse(), diff --git a/layoutparser-service/layoutparser-service-server/src/test/resources/files/SinglePages/DontMergeNonConsecutiveTables.pdf b/layoutparser-service/layoutparser-service-server/src/test/resources/files/SinglePages/DontMergeNonConsecutiveTables.pdf deleted file mode 100644 index 4e18c90d62a0502fc113932603e4bf6f05222b83..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16012 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 BBTxVU