RED-6009: Document Tree Structure
* reworked section grid
This commit is contained in:
parent
d601942b7a
commit
32df4ca867
@ -57,7 +57,6 @@ public class DocumentGraphMapper {
|
|||||||
List<DocumentTree.Entry> newEntries = new LinkedList<>();
|
List<DocumentTree.Entry> newEntries = new LinkedList<>();
|
||||||
for (DocumentTreeData.EntryData entryData : entries) {
|
for (DocumentTreeData.EntryData entryData : entries) {
|
||||||
|
|
||||||
boolean terminal = isTerminal(entryData);
|
|
||||||
List<Page> pages = Arrays.stream(entryData.getPageNumbers()).map(pageNumber -> getPage(pageNumber, context)).toList();
|
List<Page> pages = Arrays.stream(entryData.getPageNumbers()).map(pageNumber -> getPage(pageNumber, context)).toList();
|
||||||
|
|
||||||
SemanticNode node = switch (entryData.getType()) {
|
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) {
|
private Image buildImage(Context context, Map<String, String> properties, Long[] pageNumbers) {
|
||||||
|
|
||||||
assert pageNumbers.length == 1;
|
assert pageNumbers.length == 1;
|
||||||
|
|||||||
@ -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.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.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.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.model.StructureAnalyzeRequest;
|
||||||
import com.iqser.red.service.redaction.v1.server.client.LegalBasisClient;
|
import com.iqser.red.service.redaction.v1.server.client.LegalBasisClient;
|
||||||
import com.iqser.red.service.redaction.v1.server.client.model.NerEntitiesModel;
|
import com.iqser.red.service.redaction.v1.server.client.model.NerEntitiesModel;
|
||||||
@ -85,7 +86,6 @@ public class AnalyzeService {
|
|||||||
|
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
var pageCount = 0;
|
|
||||||
ClassificationDocument classifiedDoc;
|
ClassificationDocument classifiedDoc;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -99,19 +99,18 @@ public class AnalyzeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
classifiedDoc = pdfSegmentationService.parseDocument(analyzeRequest.getDossierId(), analyzeRequest.getFileId(), storedObjectStream, pdfImages);
|
classifiedDoc = pdfSegmentationService.parseDocument(analyzeRequest.getDossierId(), analyzeRequest.getFileId(), storedObjectStream, pdfImages);
|
||||||
pageCount = classifiedDoc.getPages().size();
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RedactionException(e);
|
throw new RedactionException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
Document document = DocumentGraphFactory.buildDocumentGraph(classifiedDoc);
|
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());
|
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.TEXT, DocumentData.fromDocument(document));
|
||||||
redactionStorageService.storeObject(analyzeRequest.getDossierId(), analyzeRequest.getFileId(), FileType.SIMPLIFIED_TEXT, toSimplifiedText(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()
|
return AnalyzeResult.builder()
|
||||||
.dossierId(analyzeRequest.getDossierId())
|
.dossierId(analyzeRequest.getDossierId())
|
||||||
|
|||||||
@ -1,19 +1,30 @@
|
|||||||
package com.iqser.red.service.redaction.v1.server.redaction.service;
|
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.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 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.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.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.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.document.graph.nodes.Document;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.ClassificationDocument;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.NodeType;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.ClassificationSection;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Page;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Cell;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SemanticNode;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.TablePageBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Table;
|
||||||
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.TableCell;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
@ -21,63 +32,114 @@ import lombok.RequiredArgsConstructor;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class SectionGridCreatorService {
|
public class SectionGridCreatorService {
|
||||||
|
|
||||||
public void createSectionGrid(ClassificationDocument classifiedDoc, int numberOfPages) {
|
public SectionGrid createSectionGrid(Document document) {
|
||||||
|
|
||||||
for (int page = 1; page <= numberOfPages; page++) {
|
Map<Integer, List<SectionRectangle>> sectionBBox = document.streamAllSubNodesOfType(NodeType.SECTION).map(SemanticNode::getBBox).collect(new SectionGridCollector());
|
||||||
addSectionGrid(classifiedDoc, page);
|
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++) {
|
return HashMap::new;
|
||||||
|
|
||||||
AbstractPageBlock textBlock = section.getPageBlocks().get(i);
|
|
||||||
|
|
||||||
if (textBlock.getPage() != page) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (textBlock instanceof TextPageBlock) {
|
|
||||||
|
|
||||||
TextPageBlock tb = (TextPageBlock) textBlock;
|
@Override
|
||||||
classifiedDoc.getSectionGrid()
|
public Function<Map<Integer, List<SectionRectangle>>, Map<Integer, List<SectionRectangle>>> finisher() {
|
||||||
.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) {
|
return Function.identity();
|
||||||
|
|
||||||
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()
|
@Override
|
||||||
.computeIfAbsent(page, (x) -> new ArrayList<>())
|
public BinaryOperator<Map<Integer, List<SectionRectangle>>> combiner() {
|
||||||
.add(new SectionRectangle(new Point(textBlock.getMinX(), textBlock.getMinY()),
|
|
||||||
textBlock.getWidth(),
|
return SectionGridCreatorService::mergeMapsByConcatenatingLists;
|
||||||
textBlock.getHeight(),
|
}
|
||||||
i + 1,
|
|
||||||
section.getPageBlocks().size(),
|
|
||||||
cellRectangles));
|
@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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user