RED-6009: Document Tree Structure

* reworked section grid
This commit is contained in:
Kilian Schuettler 2023-05-26 13:45:20 +02:00
parent d601942b7a
commit 32df4ca867
3 changed files with 122 additions and 68 deletions

View File

@ -57,7 +57,6 @@ public class DocumentGraphMapper {
List<DocumentTree.Entry> newEntries = new LinkedList<>();
for (DocumentTreeData.EntryData entryData : entries) {
boolean terminal = isTerminal(entryData);
List<Page> 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<String, String> properties, Long[] pageNumbers) {
assert pageNumbers.length == 1;

View File

@ -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())

View File

@ -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<Integer, List<SectionRectangle>> sectionBBox = document.streamAllSubNodesOfType(NodeType.SECTION).map(SemanticNode::getBBox).collect(new SectionGridCollector());
Map<Integer, List<SectionRectangle>> paragraphBBox = document.streamAllSubNodesOfType(NodeType.PARAGRAPH).map(SemanticNode::getBBox).collect(new SectionGridCollector());
Map<Integer, List<SectionRectangle>> headlineBBox = document.streamAllSubNodesOfType(NodeType.HEADLINE).map(SemanticNode::getBBox).collect(new SectionGridCollector());
Map<Integer, List<SectionRectangle>> 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<T> implements Collector<T, Map<Integer, List<SectionRectangle>>, Map<Integer, List<SectionRectangle>>> {
for (ClassificationSection section : classifiedDoc.getSections()) {
@Override
public Supplier<Map<Integer, List<SectionRectangle>>> 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<CellRectangle> cellRectangles = new ArrayList<>();
for (List<Cell> 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<Integer, List<SectionRectangle>>, Map<Integer, List<SectionRectangle>>> finisher() {
return Function.identity();
}
@Override
public BinaryOperator<Map<Integer, List<SectionRectangle>>> combiner() {
return SectionGridCreatorService::mergeMapsByConcatenatingLists;
}
@Override
public Set<Characteristics> characteristics() {
return Set.of(Characteristics.IDENTITY_FINISH, Characteristics.CONCURRENT, Characteristics.UNORDERED);
}
}
private static class TableGridCollector extends GridCollector<Table> {
@Override
public BiConsumer<Map<Integer, List<SectionRectangle>>, 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<CellRectangle> 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<Map<Page, Rectangle2D>> {
@Override
public BiConsumer<Map<Integer, List<SectionRectangle>>, Map<Page, Rectangle2D>> 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<Integer, List<SectionRectangle>> mergeMapsByConcatenatingLists(Map<Integer, List<SectionRectangle>> mapToKeep,
Map<Integer, List<SectionRectangle>> mapToMerge) {
mapToMerge.forEach((page, rectangle) -> mapToKeep.merge(page, rectangle, SectionGridCreatorService::concatLists));
return mapToKeep;
}
private static List<SectionRectangle> concatLists(List<SectionRectangle> l1, List<SectionRectangle> l2) {
return Stream.concat(l1.stream(), l2.stream()).toList();
}
}