RED-6009 - Document Tree Structure

* refactored Visualization Class
This commit is contained in:
Kilian Schuettler 2023-05-10 10:41:59 +02:00
parent f0144799c5
commit ce0a65c06a
6 changed files with 68 additions and 246 deletions

View File

@ -1,4 +1,4 @@
package com.iqser.red.service.redaction.v1.server.visualization.service;
package com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils;
import java.awt.Color;
import java.awt.geom.Point2D;
@ -18,7 +18,6 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.no
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Page;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.AtomicTextBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RectangleTransformations;
import lombok.AccessLevel;
import lombok.Builder;
@ -26,17 +25,19 @@ import lombok.Getter;
import lombok.SneakyThrows;
import lombok.experimental.FieldDefaults;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@UtilityClass
public class PdfDraw {
public class PdfVisualisationUtility {
public static void drawDocumentGraph(PDDocument document, Document documentGraph) {
public void drawDocumentGraph(PDDocument document, Document documentGraph) {
documentGraph.getDocumentTree().streamAllEntriesInOrder().forEach(entry -> drawNode(document, entry));
}
public static void drawNode(PDDocument document, DocumentTree.Entry entry) {
public void drawNode(PDDocument document, DocumentTree.Entry entry) {
Options options = buildStandardOptionsForNodes(entry);
@ -45,13 +46,13 @@ public class PdfDraw {
}
public static void drawTextBlock(PDDocument document, TextBlock textBlock, Options options) {
public void drawTextBlock(PDDocument document, TextBlock textBlock, Options options) {
textBlock.getAtomicTextBlocks().forEach(atb -> drawAtomicTextBlock(document, atb, options));
}
public static void drawAtomicTextBlock(PDDocument document, AtomicTextBlock atomicTextBlock, Options options) {
public void drawAtomicTextBlock(PDDocument document, AtomicTextBlock atomicTextBlock, Options options) {
drawRectangle2DList(document, atomicTextBlock.getPage().getNumber(), atomicTextBlock.getPositions().stream().toList(), options);
@ -59,7 +60,7 @@ public class PdfDraw {
@SneakyThrows
private static void drawText(String string, PDDocument document, Point2D location, Integer pageNumber, Options options) {
public void drawText(String string, PDDocument document, Point2D location, Integer pageNumber, Options options) {
var pdPage = document.getPage(pageNumber - 1);
var contentStream = new PDPageContentStream(document, pdPage, PDPageContentStream.AppendMode.APPEND, true);
@ -77,14 +78,14 @@ public class PdfDraw {
@SneakyThrows
public static void drawRectangle2DList(PDDocument document, int pageNumber, List<Rectangle2D> rectCollection, Options options) {
public void drawRectangle2DList(PDDocument document, int pageNumber, List<Rectangle2D> rectCollection, Options options) {
var pdPage = document.getPage(pageNumber - 1);
drawRectangle2DList(document, rectCollection, options, pdPage);
}
private static void drawRectangle2DList(PDDocument document, List<Rectangle2D> rectCollection, Options options, PDPage pdPage) throws IOException {
private void drawRectangle2DList(PDDocument document, List<Rectangle2D> rectCollection, Options options, PDPage pdPage) throws IOException {
var contentStream = new PDPageContentStream(document, pdPage, PDPageContentStream.AppendMode.APPEND, true);
@ -107,6 +108,41 @@ public class PdfDraw {
}
private Options buildStandardOptionsForNodes(DocumentTree.Entry entry) {
return Options.builder().stroke(true).strokeColor(switch (entry.getType()) {
case DOCUMENT -> Color.LIGHT_GRAY;
case HEADER, FOOTER -> Color.GREEN;
case PARAGRAPH -> Color.BLUE;
case HEADLINE -> Color.RED;
case SECTION -> Color.BLACK;
case TABLE -> Color.ORANGE;
case TABLE_CELL -> Color.GRAY;
case IMAGE -> Color.MAGENTA;
}).build();
}
private void drawBBoxAndLabelAndNumberOnPage(PDDocument document, DocumentTree.Entry entry, Options options) {
Map<Page, Rectangle2D> rectanglesPerPage = entry.getNode().getBBox();
rectanglesPerPage.forEach((page, rectangle2D) -> {
Rectangle2D paddedRectangle2D = rectangle2D;
if (entry.getType() == NodeType.SECTION) {
paddedRectangle2D = RectangleTransformations.pad(rectangle2D, 10, 10);
}
drawRectangle2DList(document, page.getNumber(), List.of(paddedRectangle2D), options);
drawText(buildString(entry), document, new Point2D.Double(paddedRectangle2D.getMinX(), paddedRectangle2D.getMaxY() + 2), page.getNumber(), options);
});
}
private String buildString(DocumentTree.Entry entry) {
return entry.getNode().getNumberOnPage() + ": " + entry.getTocId() + ": " + entry.getType().toString();
}
@Builder
@Getter
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
@ -123,39 +159,4 @@ public class PdfDraw {
}
private static Options buildStandardOptionsForNodes(DocumentTree.Entry entry) {
return Options.builder().stroke(true).strokeColor(switch (entry.getType()) {
case DOCUMENT -> Color.LIGHT_GRAY;
case HEADER, FOOTER -> Color.GREEN;
case PARAGRAPH -> Color.BLUE;
case HEADLINE -> Color.RED;
case SECTION -> Color.BLACK;
case TABLE -> Color.ORANGE;
case TABLE_CELL -> Color.GRAY;
case IMAGE -> Color.MAGENTA;
}).build();
}
private static void drawBBoxAndLabelAndNumberOnPage(PDDocument document, DocumentTree.Entry entry, Options options) {
Map<Page, Rectangle2D> rectanglesPerPage = entry.getNode().getBBox();
rectanglesPerPage.forEach((page, rectangle2D) -> {
Rectangle2D paddedRectangle2D = rectangle2D;
if (entry.getType() == NodeType.SECTION) {
paddedRectangle2D = RectangleTransformations.pad(rectangle2D, 10, 10);
}
drawRectangle2DList(document, page.getNumber(), List.of(paddedRectangle2D), options);
drawText(buildString(entry), document, new Point2D.Double(paddedRectangle2D.getMinX(), paddedRectangle2D.getMaxY() + 2), page.getNumber(), options);
});
}
private static String buildString(DocumentTree.Entry entry) {
return entry.getNode().getNumberOnPage() + ": " + entry.getTocId() + ": " + entry.getType().toString();
}
}
}

View File

@ -1,184 +0,0 @@
package com.iqser.red.service.redaction.v1.server.visualization.service;
import java.awt.Color;
import java.io.IOException;
import java.util.List;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.springframework.stereotype.Service;
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.ClassificationPage;
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.ClassificationTextBlock;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
@RequiredArgsConstructor
public class PdfVisualisationService {
private static final boolean DRAW_POSITIONS = false;
public void visualizeParagraphs(ClassificationDocument classifiedDoc, PDDocument document) throws IOException {
for (int page = 1; page <= document.getNumberOfPages(); page++) {
PDPage pdPage = document.getPage(page - 1);
PDPageContentStream contentStream = new PDPageContentStream(document, pdPage, PDPageContentStream.AppendMode.APPEND, true);
for (ClassificationSection paragraph : classifiedDoc.getSections()) {
for (int i = 0; i <= paragraph.getPageBlocks().size() - 1; i++) {
AbstractPageBlock textBlock = paragraph.getPageBlocks().get(i);
if (textBlock.getPage() != page) {
continue;
}
if (textBlock instanceof ClassificationTextBlock) {
textBlock.setClassification((i + 1) + "/" + paragraph.getPageBlocks().size());
visualizeTextBlock((ClassificationTextBlock) textBlock, contentStream);
} else if (textBlock instanceof TablePageBlock) {
textBlock.setClassification((i + 1) + "/" + paragraph.getPageBlocks().size());
visualizeTable((TablePageBlock) textBlock, contentStream);
}
}
}
contentStream.close();
}
}
public void visualizeClassifications(ClassificationDocument classifiedDoc, PDDocument document) throws IOException {
for (int page = 1; page <= document.getNumberOfPages(); page++) {
ClassificationPage analyzedPage = classifiedDoc.getPages().get(page - 1);
PDPage pdPage = document.getPage(page - 1);
PDPageContentStream contentStream = new PDPageContentStream(document, pdPage, PDPageContentStream.AppendMode.APPEND, true);
for (AbstractPageBlock textBlock : analyzedPage.getTextBlocks()) {
if (textBlock == null) {
continue;
}
if (textBlock instanceof ClassificationTextBlock) {
visualizeTextBlock((ClassificationTextBlock) textBlock, contentStream);
} else if (textBlock instanceof TablePageBlock) {
visualizeTable((TablePageBlock) textBlock, contentStream);
}
}
contentStream.setStrokingColor(Color.YELLOW);
contentStream.addRect(analyzedPage.getBodyTextFrame().getTopLeft().getX(),
analyzedPage.getBodyTextFrame().getTopLeft().getY(),
analyzedPage.getBodyTextFrame().getWidth(),
analyzedPage.getBodyTextFrame().getHeight());
contentStream.stroke();
contentStream.close();
}
}
private void visualizeTextBlock(ClassificationTextBlock textBlock, PDPageContentStream contentStream) throws IOException {
contentStream.setStrokingColor(Color.RED);
contentStream.addRect(textBlock.getPdfMinX(), textBlock.getPdfMinY(), textBlock.getPdfMaxX() - textBlock.getPdfMinX(), textBlock.getPdfMaxY() - textBlock.getPdfMinY());
contentStream.stroke();
if (textBlock.getClassification() != null) {
contentStream.beginText();
contentStream.setNonStrokingColor(Color.BLUE);
contentStream.setFont(PDType1Font.TIMES_ROMAN, 9f);
contentStream.newLineAtOffset(textBlock.getPdfMinX(), textBlock.getPdfMaxY() + 2);
contentStream.showText(textBlock.getClassification() + textBlock.getOrientation() + "-->" + textBlock.getSequences().get(0).getDir());
contentStream.endText();
if (DRAW_POSITIONS) {
drawPositions(contentStream, textBlock);
}
}
}
@SneakyThrows
private void drawPositions(PDPageContentStream contentStream, ClassificationTextBlock textBlock) {
contentStream.setNonStrokingColor(Color.BLUE);
contentStream.setFont(PDType1Font.TIMES_ROMAN, 2f);
contentStream.beginText();
contentStream.newLineAtOffset(textBlock.getPdfMinX(), textBlock.getPdfMinY());
contentStream.showText("MinX,MinY(" + textBlock.getPdfMinX() + "," + textBlock.getPdfMinY() + ")");
contentStream.endText();
contentStream.beginText();
contentStream.newLineAtOffset(textBlock.getPdfMaxX(), textBlock.getPdfMinY());
contentStream.showText("MaxX,MinY(" + textBlock.getPdfMaxX() + "," + textBlock.getPdfMinY() + ")");
contentStream.endText();
contentStream.beginText();
contentStream.newLineAtOffset(textBlock.getPdfMinX(), textBlock.getPdfMaxY());
contentStream.showText("MinX,MaxY(" + textBlock.getPdfMinX() + "," + textBlock.getPdfMaxY() + ")");
contentStream.endText();
contentStream.beginText();
contentStream.newLineAtOffset(textBlock.getPdfMaxX(), textBlock.getPdfMaxY());
contentStream.showText("MaxX,MaxY(" + textBlock.getPdfMaxX() + "," + textBlock.getPdfMaxY() + ")");
contentStream.endText();
}
private void visualizeTable(TablePageBlock table, PDPageContentStream contentStream) throws IOException {
for (List<Cell> row : table.getRows()) {
for (Cell cell : row) {
if (cell != null) {
contentStream.setStrokingColor(Color.BLUE);
contentStream.addRect((float) cell.getX(), (float) cell.getY(), (float) cell.getWidth(), (float) cell.getHeight());
contentStream.stroke();
contentStream.setStrokingColor(Color.GREEN);
for (ClassificationTextBlock textBlock : cell.getTextBlocks()) {
contentStream.addRect(textBlock.getPdfMinX(),
textBlock.getPdfMinY(),
textBlock.getPdfMaxX() - textBlock.getPdfMinX(),
textBlock.getPdfMaxY() - textBlock.getPdfMinY());
contentStream.stroke();
}
}
}
}
if (table.getClassification() != null) {
contentStream.beginText();
contentStream.setNonStrokingColor(Color.BLUE);
contentStream.setFont(PDType1Font.TIMES_ROMAN, 12f);
contentStream.newLineAtOffset(table.getMinX(), table.getMinY());
contentStream.showText(table.getClassification());
contentStream.endText();
}
}
}

View File

@ -1,7 +1,6 @@
package com.iqser.red.service.redaction.v1.server;
import static com.iqser.red.service.redaction.v1.server.redaction.utils.SeparatorUtils.boundaryIsSurroundedBySeparators;
import static com.iqser.red.service.redaction.v1.server.visualization.service.PdfDraw.drawRectangle2DList;
import static org.mockito.Mockito.when;
import java.awt.Color;
@ -41,6 +40,7 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.no
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SemanticNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.services.EntityCreationService;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.PdfVisualisationUtility;
import com.iqser.red.service.redaction.v1.server.multitenancy.TenantContext;
import com.iqser.red.service.redaction.v1.server.redaction.adapter.NerEntities;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.Dictionary;
@ -48,7 +48,6 @@ import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.Dict
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.SearchImplementation;
import com.iqser.red.service.redaction.v1.server.redaction.service.DictionaryService;
import com.iqser.red.service.redaction.v1.server.redaction.service.DroolsExecutionService;
import com.iqser.red.service.redaction.v1.server.visualization.service.PdfDraw;
import lombok.SneakyThrows;
@ -262,8 +261,8 @@ public class DocumentPerformanceIntegrationTest extends BuildDocumentIntegration
.flatMap(entityPosition -> entityPosition.getRectanglePerLine().stream())
.toList();
PdfDraw.Options options = PdfDraw.Options.builder().strokeColor(Color.BLACK).stroke(true).build();
drawRectangle2DList(pdDocument, page.getNumber(), entityPositionsOnPage, options);
PdfVisualisationUtility.Options options = PdfVisualisationUtility.Options.builder().strokeColor(Color.BLACK).stroke(true).build();
PdfVisualisationUtility.drawRectangle2DList(pdDocument, page.getNumber(), entityPositionsOnPage, options);
}
for (Page page : document.getPages()) {
@ -276,8 +275,8 @@ public class DocumentPerformanceIntegrationTest extends BuildDocumentIntegration
.flatMap(entityPosition -> entityPosition.getRectanglePerLine().stream())
.toList();
PdfDraw.Options options = PdfDraw.Options.builder().strokeColor(Color.BLUE).stroke(true).build();
drawRectangle2DList(pdDocument, page.getNumber(), entityPositionsOnPage, options);
PdfVisualisationUtility.Options options = PdfVisualisationUtility.Options.builder().strokeColor(Color.BLUE).stroke(true).build();
PdfVisualisationUtility.drawRectangle2DList(pdDocument, page.getNumber(), entityPositionsOnPage, options);
}
File outputFile = new File(tmpFileName);
pdDocument.save(outputFile);

View File

@ -11,7 +11,7 @@ import org.springframework.core.io.ClassPathResource;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Document;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
import com.iqser.red.service.redaction.v1.server.visualization.service.PdfDraw;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.PdfVisualisationUtility;
import lombok.SneakyThrows;
@ -80,8 +80,10 @@ public class DocumentVisualizationIntegrationTest extends BuildDocumentIntegrati
try (var fileStream = fileResource.getInputStream()) {
PDDocument pdDocument = PDDocument.load(fileStream);
PdfDraw.drawDocumentGraph(pdDocument, document);
PdfDraw.drawTextBlock(pdDocument, textBlock, PdfDraw.Options.builder().stroke(true).strokeWidth(0.1f).strokeColor(Color.YELLOW).build());
PdfVisualisationUtility.drawDocumentGraph(pdDocument, document);
PdfVisualisationUtility.drawTextBlock(pdDocument,
textBlock,
PdfVisualisationUtility.Options.builder().stroke(true).strokeWidth(0.1f).strokeColor(Color.YELLOW).build());
File outputFile = new File(tmpFileName);
pdDocument.save(outputFile);
pdDocument.close();

View File

@ -25,7 +25,7 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.en
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionPosition;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Document;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.services.EntityCreationService;
import com.iqser.red.service.redaction.v1.server.visualization.service.PdfDraw;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.PdfVisualisationUtility;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@ -167,9 +167,15 @@ class NerEntitiesAdapterTest extends BuildDocumentIntegrationTest {
List<Rectangle2D> cbiAddressRects,
PDDocument pdDocument) {
//PdfDraw.drawRectangle2DList(pdDocument, pageNumber, cbiAuthorRects, PdfDraw.Options.builder().stroke(true).strokeColor(Color.blue).build());
PdfDraw.drawRectangle2DList(pdDocument, pageNumber, addressPartsRects, PdfDraw.Options.builder().stroke(true).strokeColor(Color.MAGENTA).build());
PdfDraw.drawRectangle2DList(pdDocument, pageNumber, cbiAddressRects, PdfDraw.Options.builder().stroke(true).strokeColor(Color.green).build());
PdfVisualisationUtility.drawRectangle2DList(pdDocument, pageNumber, cbiAuthorRects, PdfVisualisationUtility.Options.builder().stroke(true).strokeColor(Color.blue).build());
PdfVisualisationUtility.drawRectangle2DList(pdDocument,
pageNumber,
addressPartsRects,
PdfVisualisationUtility.Options.builder().stroke(true).strokeColor(Color.MAGENTA).build());
PdfVisualisationUtility.drawRectangle2DList(pdDocument,
pageNumber,
cbiAddressRects,
PdfVisualisationUtility.Options.builder().stroke(true).strokeColor(Color.green).build());
}

View File

@ -64,6 +64,7 @@ rule "0: Recommend CTL/BL laboratory that start with BL or CTL"
insert(entity);
});
end
// --------------------------------------- CBI Rules -------------------------------------------------------------------
rule "1: Redact CBI Authors (non vertebrate study)"
@ -285,8 +286,6 @@ rule "18.1: Do not redact Names and Addresses if published information found in
});
end
// --------------------------------------- PII rules -------------------------------------------------------------------
rule "19: Redact all PII (non vertebrate study)"
@ -341,7 +340,6 @@ rule "22: Redact Emails by RegEx (vertebrate study)"
});
end
rule "25: Redact Phone and Fax by RegEx (non vertebrate study)"
when
not FileAttribute(label == "Vertebrate Study", value.toLowerCase() == "yes")