From 32df4ca867efa1253c5af6a062748c20522ce593 Mon Sep 17 00:00:00 2001 From: Kilian Schuettler Date: Fri, 26 May 2023 13:45:20 +0200 Subject: [PATCH] RED-6009: Document Tree Structure * reworked section grid --- .../data/mapper/DocumentGraphMapper.java | 7 - .../redaction/service/AnalyzeService.java | 7 +- .../service/SectionGridCreatorService.java | 176 ++++++++++++------ 3 files changed, 122 insertions(+), 68 deletions(-) diff --git a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/layoutparsing/document/data/mapper/DocumentGraphMapper.java b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/layoutparsing/document/data/mapper/DocumentGraphMapper.java index 648e7100..7702f176 100644 --- a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/layoutparsing/document/data/mapper/DocumentGraphMapper.java +++ b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/layoutparsing/document/data/mapper/DocumentGraphMapper.java @@ -57,7 +57,6 @@ public class DocumentGraphMapper { List newEntries = new LinkedList<>(); for (DocumentTreeData.EntryData entryData : entries) { - boolean terminal = isTerminal(entryData); List pages = Arrays.stream(entryData.getPageNumbers()).map(pageNumber -> getPage(pageNumber, context)).toList(); SemanticNode node = switch (entryData.getType()) { @@ -97,12 +96,6 @@ public class DocumentGraphMapper { } - private static boolean isTerminal(DocumentTreeData.EntryData entryData) { - - return entryData.getAtomicBlockIds().length > 0; - } - - private Image buildImage(Context context, Map properties, Long[] pageNumbers) { assert pageNumbers.length == 1; diff --git a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/redaction/service/AnalyzeService.java b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/redaction/service/AnalyzeService.java index e43ed1b9..4cc67431 100644 --- a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/redaction/service/AnalyzeService.java +++ b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/redaction/service/AnalyzeService.java @@ -23,6 +23,7 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlo import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLogChanges; import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLogEntry; import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLogLegalBasis; +import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.section.SectionGrid; import com.iqser.red.service.redaction.v1.model.StructureAnalyzeRequest; import com.iqser.red.service.redaction.v1.server.client.LegalBasisClient; import com.iqser.red.service.redaction.v1.server.client.model.NerEntitiesModel; @@ -85,7 +86,6 @@ public class AnalyzeService { long startTime = System.currentTimeMillis(); - var pageCount = 0; ClassificationDocument classifiedDoc; try { @@ -99,19 +99,18 @@ public class AnalyzeService { } classifiedDoc = pdfSegmentationService.parseDocument(analyzeRequest.getDossierId(), analyzeRequest.getFileId(), storedObjectStream, pdfImages); - pageCount = classifiedDoc.getPages().size(); } catch (Exception e) { throw new RedactionException(e); } Document document = DocumentGraphFactory.buildDocumentGraph(classifiedDoc); - sectionGridCreatorService.createSectionGrid(classifiedDoc, pageCount); + SectionGrid sectionGrid = sectionGridCreatorService.createSectionGrid(document); log.info("Store document graph, text, simplified text, and section grid for file {} in dossier {}", analyzeRequest.getFileId(), analyzeRequest.getDossierId()); redactionStorageService.storeObject(analyzeRequest.getDossierId(), analyzeRequest.getFileId(), FileType.TEXT, DocumentData.fromDocument(document)); redactionStorageService.storeObject(analyzeRequest.getDossierId(), analyzeRequest.getFileId(), FileType.SIMPLIFIED_TEXT, toSimplifiedText(document)); - redactionStorageService.storeObject(analyzeRequest.getDossierId(), analyzeRequest.getFileId(), FileType.SECTION_GRID, classifiedDoc.getSectionGrid()); + redactionStorageService.storeObject(analyzeRequest.getDossierId(), analyzeRequest.getFileId(), FileType.SECTION_GRID, sectionGrid); return AnalyzeResult.builder() .dossierId(analyzeRequest.getDossierId()) diff --git a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/redaction/service/SectionGridCreatorService.java b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/redaction/service/SectionGridCreatorService.java index 00657234..9f4bd0a1 100644 --- a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/redaction/service/SectionGridCreatorService.java +++ b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/redaction/service/SectionGridCreatorService.java @@ -1,19 +1,30 @@ package com.iqser.red.service.redaction.v1.server.redaction.service; -import java.util.ArrayList; +import java.awt.geom.Rectangle2D; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; +import java.util.stream.Stream; import org.springframework.stereotype.Service; import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Point; import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.section.CellRectangle; +import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.section.SectionGrid; import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.section.SectionRectangle; -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.ClassificationSection; -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.text.TextPageBlock; +import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Document; +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.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.TableCell; import lombok.RequiredArgsConstructor; @@ -21,63 +32,114 @@ import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public class SectionGridCreatorService { - public void createSectionGrid(ClassificationDocument classifiedDoc, int numberOfPages) { + public SectionGrid createSectionGrid(Document document) { - for (int page = 1; page <= numberOfPages; page++) { - addSectionGrid(classifiedDoc, page); - } + Map> sectionBBox = document.streamAllSubNodesOfType(NodeType.SECTION).map(SemanticNode::getBBox).collect(new SectionGridCollector()); + Map> paragraphBBox = document.streamAllSubNodesOfType(NodeType.PARAGRAPH).map(SemanticNode::getBBox).collect(new SectionGridCollector()); + Map> headlineBBox = document.streamAllSubNodesOfType(NodeType.HEADLINE).map(SemanticNode::getBBox).collect(new SectionGridCollector()); + Map> tableBBox = document.streamAllSubNodesOfType(NodeType.TABLE).map(node -> (Table) node).collect(new TableGridCollector()); + var sectionGrid = new SectionGrid(); + + sectionGrid.setRectanglesPerPage(mergeMapsByConcatenatingLists(// + mergeMapsByConcatenatingLists(paragraphBBox, headlineBBox), // + mergeMapsByConcatenatingLists(sectionBBox, tableBBox))); + return sectionGrid; } - private void addSectionGrid(ClassificationDocument classifiedDoc, int page) { + private static abstract class GridCollector implements Collector>, Map>> { - for (ClassificationSection section : classifiedDoc.getSections()) { + @Override + public Supplier>> supplier() { - for (int i = 0; i <= section.getPageBlocks().size() - 1; i++) { - - AbstractPageBlock textBlock = section.getPageBlocks().get(i); - - if (textBlock.getPage() != page) { - continue; - } - - if (textBlock instanceof TextPageBlock) { - - TextPageBlock tb = (TextPageBlock) textBlock; - classifiedDoc.getSectionGrid() - .getRectanglesPerPage() - .computeIfAbsent(page, (x) -> new ArrayList<>()) - .add(new SectionRectangle(new Point(tb.getPdfMinX(), tb.getPdfMinY()), - tb.getPdfMaxX() - tb.getPdfMinX(), - tb.getPdfMaxY() - tb.getPdfMinY(), - i + 1, - section.getPageBlocks().size(), - null)); - - } else if (textBlock instanceof TablePageBlock) { - - List cellRectangles = new ArrayList<>(); - for (List row : ((TablePageBlock) textBlock).getRows()) { - for (Cell cell : row) { - if (cell != null) { - cellRectangles.add(new CellRectangle(new Point((float) cell.getX(), (float) cell.getY()), (float) cell.getWidth(), (float) cell.getHeight())); - } - } - } - - classifiedDoc.getSectionGrid() - .getRectanglesPerPage() - .computeIfAbsent(page, (x) -> new ArrayList<>()) - .add(new SectionRectangle(new Point(textBlock.getMinX(), textBlock.getMinY()), - textBlock.getWidth(), - textBlock.getHeight(), - i + 1, - section.getPageBlocks().size(), - cellRectangles)); - - } - } + return HashMap::new; } + + + @Override + public Function>, Map>> finisher() { + + return Function.identity(); + } + + + @Override + public BinaryOperator>> combiner() { + + return SectionGridCreatorService::mergeMapsByConcatenatingLists; + } + + + @Override + public Set characteristics() { + + return Set.of(Characteristics.IDENTITY_FINISH, Characteristics.CONCURRENT, Characteristics.UNORDERED); + } + + } + + private static class TableGridCollector extends GridCollector { + + @Override + public BiConsumer>, Table> accumulator() { + + return (map, table) -> table.getPages() + .forEach(page -> map.merge(page.getNumber(), List.of(toSectionRectangle(table, page, table.getPages().size())), SectionGridCreatorService::concatLists)); + } + + + private static SectionRectangle toSectionRectangle(Table table, Page page, int numberOfParts) { + + Rectangle2D rect = table.getBBox().get(page); + List tableCellRectangles = table.streamTableCells() + .map(TableCell::getBBox) + .map(map -> map.get(page)) + .filter(Objects::nonNull) + .map(rectangle2D -> new CellRectangle(new Point((float) rectangle2D.getX(), (float) rectangle2D.getY()), + (float) rectangle2D.getWidth(), + (float) rectangle2D.getHeight())) + .toList(); + return new SectionRectangle(new Point((float) rect.getX(), (float) rect.getY()), + (float) rect.getWidth(), + (float) rect.getHeight(), + 1, + numberOfParts, + tableCellRectangles); + } + + } + + private static class SectionGridCollector extends GridCollector> { + + @Override + public BiConsumer>, Map> accumulator() { + + return (mapToKeep, mapToMerge) -> mapToMerge.forEach((page, rectangle) -> mapToKeep.merge(page.getNumber(), + List.of(toSectionRectangle(rectangle, mapToMerge.values().size())), + SectionGridCreatorService::concatLists)); + + } + + + private static SectionRectangle toSectionRectangle(Rectangle2D rect, int numberOfParts) { + + return new SectionRectangle(new Point((float) rect.getX(), (float) rect.getY()), (float) rect.getWidth(), (float) rect.getHeight(), 1, numberOfParts, null); + } + + } + + + private static Map> mergeMapsByConcatenatingLists(Map> mapToKeep, + Map> mapToMerge) { + + mapToMerge.forEach((page, rectangle) -> mapToKeep.merge(page, rectangle, SectionGridCreatorService::concatLists)); + return mapToKeep; + } + + + private static List concatLists(List l1, List l2) { + + return Stream.concat(l1.stream(), l2.stream()).toList(); } }