RED-6009 - Document Tree Structure

* fixed javadoc and delombok plugins
* removed unused classes
* refactored names
This commit is contained in:
Kilian Schuettler 2023-05-08 16:42:24 +02:00
parent c68c6f9ba9
commit 088a5cde1a
118 changed files with 1636 additions and 7145 deletions

View File

@ -23,6 +23,7 @@
<properties>
<pdfbox.version>2.0.24</pdfbox.version>
<lombok.version>1.18.26</lombok.version>
</properties>
@ -54,12 +55,10 @@
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>3.9.0.2155</version>
</plugin>
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>6.3.1</version>
<configuration>
<format>ALL</format>
</configuration>
@ -67,6 +66,12 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.8</version>
<configuration>
<excludes>
<exclude>org/drools/**/*</exclude>
</excludes>
</configuration>
<executions>
<execution>
<id>prepare-agent</id>
@ -86,22 +91,21 @@
</pluginManagement>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.8</version>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<version>1.18.20.0</version>
<executions>
<execution>
<id>prepare-agent</id>
<id>delombok</id>
<phase>generate-sources</phase>
<goals>
<goal>prepare-agent</goal>
<goal>delombok</goal>
</goals>
</execution>
<execution>
<id>report</id>
<goals>
<goal>report-aggregate</goal>
</goals>
<phase>verify</phase>
<configuration>
<addOutputDirectory>false</addOutputDirectory>
<sourceDirectory>src/main/java</sourceDirectory>
<outputDirectory>${delomboked.sources}</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>

View File

@ -39,24 +39,5 @@
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -132,76 +132,4 @@
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<!-- generate git.properties for exposure in /info -->
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>revision</goal>
</goals>
<configuration>
<generateGitPropertiesFile>true</generateGitPropertiesFile>
<gitDescribe>
<tags>true</tags>
</gitDescribe>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>original-jar</id>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<classifier>original</classifier>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<!-- repackages the generated jar into a runnable fat-jar and makes it
executable -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<executable>true</executable>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,13 +1,13 @@
package com.iqser.red.service.redaction.v1.server.controller;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.redaction.v1.model.RuleBuilderModel;
import com.iqser.red.service.redaction.v1.resources.RuleBuilderResource;
import com.iqser.red.service.redaction.v1.server.redaction.rulebuilder.RuleBuilderModelService;
import com.iqser.red.service.redaction.v1.server.redaction.service.RuleBuilderModelService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
public class RuleBuilderController implements RuleBuilderResource {

View File

@ -1,5 +1,6 @@
package com.iqser.red.service.redaction.v1.server.layoutparsing.classification.adapter;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@ -11,10 +12,9 @@ import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileType;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.adapter.image.ImageServiceResponse;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Page;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.ClassificationPage;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.image.ClassifiedImage;
import com.iqser.red.service.redaction.v1.server.redaction.model.ImageType;
import com.iqser.red.service.redaction.v1.server.redaction.model.RedRectangle2D;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.ImageType;
import com.iqser.red.service.redaction.v1.server.storage.RedactionStorageService;
import lombok.RequiredArgsConstructor;
@ -41,7 +41,7 @@ public class ImageServiceResponseAdapter {
.getLabel()
.toUpperCase(Locale.ROOT)) : ImageType.OTHER;
images.computeIfAbsent(imageMetadata.getPosition().getPageNumber(), x -> new ArrayList<>())
.add(new ClassifiedImage(new RedRectangle2D(imageMetadata.getPosition().getX1(),
.add(new ClassifiedImage(new Rectangle2D.Double(imageMetadata.getPosition().getX1(),
imageMetadata.getPosition().getY1(),
imageMetadata.getGeometry().getWidth(),
imageMetadata.getGeometry().getHeight()), classification, imageMetadata.isAlpha(), imageMetadata.getPosition().getPageNumber()));
@ -53,7 +53,7 @@ public class ImageServiceResponseAdapter {
.getLabel()
.toUpperCase(Locale.ROOT)) : ImageType.OTHER;
images.computeIfAbsent(imageMetadata.getPosition().getPageNumber(), x -> new ArrayList<>())
.add(new ClassifiedImage(new RedRectangle2D(imageMetadata.getPosition().getX1(),
.add(new ClassifiedImage(new Rectangle2D.Double(imageMetadata.getPosition().getX1(),
imageMetadata.getPosition().getY1(),
imageMetadata.getGeometry().getWidth(),
imageMetadata.getGeometry().getHeight()), classification, imageMetadata.isAlpha(), imageMetadata.getPosition().getPageNumber()));
@ -63,7 +63,7 @@ public class ImageServiceResponseAdapter {
}
public void findOcr(Page page) {
public void findOcr(ClassificationPage page) {
page.getImages().forEach(image -> {
if (image.getImageType().equals(ImageType.OTHER)) {

View File

@ -13,12 +13,12 @@ import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class Document {
public class ClassificationDocument {
private List<Page> pages = new ArrayList<>();
private List<Section> sections = new ArrayList<>();
private List<Header> headers = new ArrayList<>();
private List<Footer> footers = new ArrayList<>();
private List<ClassificationPage> pages = new ArrayList<>();
private List<ClassificationSection> sections = new ArrayList<>();
private List<ClassificationHeader> headers = new ArrayList<>();
private List<ClassificationFooter> footers = new ArrayList<>();
private List<UnclassifiedText> unclassifiedTexts = new ArrayList<>();
private FloatFrequencyCounter textHeightCounter = new FloatFrequencyCounter();
private FloatFrequencyCounter fontSizeCounter = new FloatFrequencyCounter();

View File

@ -4,14 +4,14 @@ import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
import com.iqser.red.service.redaction.v1.server.redaction.model.SearchableText;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.SearchableText;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Footer {
public class ClassificationFooter {
private List<ClassificationTextBlock> textBlocks;

View File

@ -4,14 +4,14 @@ import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
import com.iqser.red.service.redaction.v1.server.redaction.model.SearchableText;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.SearchableText;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Header {
public class ClassificationHeader {
private List<ClassificationTextBlock> textBlocks;

View File

@ -6,14 +6,14 @@ import java.util.List;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.image.ClassifiedImage;
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 com.iqser.red.service.redaction.v1.server.redaction.model.SearchableText;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.SearchableText;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class Section implements Comparable {
public class ClassificationSection implements Comparable {
private List<AbstractPageBlock> pageBlocks = new ArrayList<>();
private List<ClassifiedImage> images = new ArrayList<>();

View File

@ -1,7 +1,8 @@
package com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.image;
import com.iqser.red.service.redaction.v1.server.redaction.model.ImageType;
import com.iqser.red.service.redaction.v1.server.redaction.model.RedRectangle2D;
import java.awt.geom.Rectangle2D;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.ImageType;
import lombok.Data;
import lombok.NonNull;
@ -12,7 +13,7 @@ import lombok.RequiredArgsConstructor;
public class ClassifiedImage {
@NonNull
private RedRectangle2D position;
private Rectangle2D position;
@NonNull
private ImageType imageType;
private boolean isAppendedToSection;

View File

@ -0,0 +1,45 @@
package com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text;
import java.util.ArrayList;
import java.util.List;
import com.iqser.red.service.redaction.v1.server.redaction.utils.TextNormalizationUtilities;
import lombok.Getter;
public class SearchableText {
@Getter
private final List<TextPositionSequence> sequences = new ArrayList<>();
public void add(TextPositionSequence textPositionSequence) {
sequences.add(textPositionSequence);
}
public void addAll(List<TextPositionSequence> textPositionSequences) {
sequences.addAll(textPositionSequences);
}
@Override
public String toString() {
return buildString(sequences);
}
public static String buildString(List<TextPositionSequence> sequences) {
StringBuilder sb = new StringBuilder();
for (TextPositionSequence word : sequences) {
sb.append(word.toString());
sb.append(' ');
}
return TextNormalizationUtilities.removeHyphenLineBreaks(sb.toString()).replaceAll("\n", " ").replaceAll(" {2}", " ");
}
}

View File

@ -1,64 +0,0 @@
package com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.section.SectionArea;
import com.iqser.red.service.redaction.v1.server.redaction.model.CellValue;
import com.iqser.red.service.redaction.v1.server.redaction.model.Image;
import com.iqser.red.service.redaction.v1.server.redaction.model.SearchableText;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SectionText {
private int sectionNumber;
private String text;
private boolean isTable;
private String headline;
@Builder.Default
private List<SectionArea> sectionAreas = new ArrayList<>();
@Builder.Default
private Set<Image> images = new HashSet<>();
@Builder.Default
private List<ClassificationTextBlock> textBlocks = new ArrayList<>();
@Builder.Default
private Map<String, CellValue> tabularData = new HashMap<>();
@Builder.Default
private List<Integer> cellStarts = new ArrayList<>();
public void setTabularData(Map<String, CellValue> tabularData) {
tabularData.remove(null);
this.tabularData = tabularData;
}
@JsonIgnore
public SearchableText getSearchableText() {
SearchableText searchableText = new SearchableText();
textBlocks.forEach(block -> {
if (block != null) {
searchableText.addAll(block.getSequences());
}
});
return searchableText;
}
}

View File

@ -1,18 +0,0 @@
package com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Text {
private int numberOfPages;
private List<SectionText> sectionTexts = new ArrayList<>();
}

View File

@ -3,7 +3,6 @@ package com.iqser.red.service.redaction.v1.server.layoutparsing.classification.m
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.iqser.red.service.redaction.v1.server.redaction.model.SearchableText;
import lombok.AllArgsConstructor;
import lombok.Data;

View File

@ -10,9 +10,9 @@ import java.util.List;
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.ClassificationPage;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.FloatFrequencyCounter;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Orientation;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Page;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Ruling;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.StringFrequencyCounter;
@ -36,7 +36,7 @@ public class BlockificationService {
* @param verticalRulingLines Vertical table lines.
* @return Page object that contains the Textblock and text statistics.
*/
public Page blockify(List<TextPositionSequence> textPositions, List<Ruling> horizontalRulingLines, List<Ruling> verticalRulingLines) {
public ClassificationPage blockify(List<TextPositionSequence> textPositions, List<Ruling> horizontalRulingLines, List<Ruling> verticalRulingLines) {
int indexOnPage = 0;
List<TextPositionSequence> chunkWords = new ArrayList<>();
@ -157,7 +157,7 @@ public class BlockificationService {
previous = block;
}
return new Page(chunkBlockList1);
return new ClassificationPage(chunkBlockList1);
}

View File

@ -7,8 +7,8 @@ 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.Rectangle;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.AbstractPageBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.ClassificationPage;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.FloatFrequencyCounter;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Page;
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;
@ -30,7 +30,7 @@ public class BodyTextFrameService {
* @param bodyTextFrame frame that contains the main text on portrait pages
* @param landscapeBodyTextFrame frame that contains the main text on landscape pages
*/
public void setBodyTextFrameAdjustedToPage(Page page, Rectangle bodyTextFrame, Rectangle landscapeBodyTextFrame) {
public void setBodyTextFrameAdjustedToPage(ClassificationPage page, Rectangle bodyTextFrame, Rectangle landscapeBodyTextFrame) {
Rectangle textFrame = page.isLandscape() ? landscapeBodyTextFrame : bodyTextFrame;
@ -65,11 +65,11 @@ public class BodyTextFrameService {
* @param landscape Calculate for landscape or portrait
* @return Rectangle of the text frame
*/
public Rectangle calculateBodyTextFrame(List<Page> pages, FloatFrequencyCounter documentFontSizeCounter, boolean landscape) {
public Rectangle calculateBodyTextFrame(List<ClassificationPage> pages, FloatFrequencyCounter documentFontSizeCounter, boolean landscape) {
BodyTextFrameExpansionsRectangle expansionsRectangle = new BodyTextFrameExpansionsRectangle();
for (Page page : pages) {
for (ClassificationPage page : pages) {
if (page.getTextBlocks().isEmpty() || landscape != page.isLandscape()) {
continue;
@ -117,7 +117,7 @@ public class BodyTextFrameService {
}
private void expandRectangle(ClassificationTextBlock textBlock, Page page, BodyTextFrameExpansionsRectangle expansionsRectangle) {
private void expandRectangle(ClassificationTextBlock textBlock, ClassificationPage page, BodyTextFrameExpansionsRectangle expansionsRectangle) {
if (page.getPageWidth() > page.getPageHeight() && page.getRotation() != 0) {
if (textBlock.getPdfMinY() < expansionsRectangle.minX) {

View File

@ -7,8 +7,8 @@ import org.springframework.stereotype.Service;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Rectangle;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.AbstractPageBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Document;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Page;
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.text.ClassificationTextBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.utils.PositionUtils;
@ -23,7 +23,7 @@ public class ClassificationService {
private final BodyTextFrameService bodyTextFrameService;
public void classifyDocument(Document document) {
public void classifyDocument(ClassificationDocument document) {
Rectangle bodyTextFrame = bodyTextFrameService.calculateBodyTextFrame(document.getPages(), document.getFontSizeCounter(), false);
Rectangle landscapeBodyTextFrame = bodyTextFrameService.calculateBodyTextFrame(document.getPages(), document.getFontSizeCounter(), true);
@ -31,14 +31,14 @@ public class ClassificationService {
log.debug("Document FontSize counters are: {}", document.getFontSizeCounter().getCountPerValue());
for (Page page : document.getPages()) {
for (ClassificationPage page : document.getPages()) {
bodyTextFrameService.setBodyTextFrameAdjustedToPage(page, bodyTextFrame, landscapeBodyTextFrame);
classifyPage(page, document, headlineFontSizes);
}
}
public void classifyPage(Page page, Document document, List<Float> headlineFontSizes) {
public void classifyPage(ClassificationPage page, ClassificationDocument document, List<Float> headlineFontSizes) {
for (AbstractPageBlock textBlock : page.getTextBlocks()) {
if (textBlock instanceof ClassificationTextBlock) {
@ -48,7 +48,7 @@ public class ClassificationService {
}
public void classifyBlock(ClassificationTextBlock textBlock, Page page, Document document, List<Float> headlineFontSizes) {
public void classifyBlock(ClassificationTextBlock textBlock, ClassificationPage page, ClassificationDocument document, List<Float> headlineFontSizes) {
var bodyTextFrame = page.getBodyTextFrame();

View File

@ -20,8 +20,8 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.ad
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.adapter.TableServiceResponseAdapter;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.adapter.table.PdfTableCell;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.AbstractPageBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Document;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Page;
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.image.ClassifiedImage;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.CleanRulings;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
@ -48,7 +48,10 @@ public class PdfSegmentationService {
private final TableServiceResponseAdapter tableServiceResponseAdapter;
public Document parseDocument(String dossierId, String fileId, InputStream documentInputStream, Map<Integer, List<ClassifiedImage>> pdfImages) throws IOException {
public ClassificationDocument parseDocument(String dossierId,
String fileId,
InputStream documentInputStream,
Map<Integer, List<ClassifiedImage>> pdfImages) throws IOException {
PDDocument pdDocument = null;
File tempFile = null;
@ -63,8 +66,8 @@ public class PdfSegmentationService {
IOUtils.copy(documentInputStream, fos);
// initialize required variables
Document document = new Document();
List<Page> pages = new ArrayList<>();
ClassificationDocument document = new ClassificationDocument();
List<ClassificationPage> pages = new ArrayList<>();
pdDocument = PDDocument.load(tempFile, MemoryUsageSetting.setupMixed(67108864L));
pdDocument.setAllSecurityToBeRemoved(true);
@ -95,8 +98,8 @@ public class PdfSegmentationService {
private void processPage(Map<Integer, List<ClassifiedImage>> pdfImages,
PDDocument pdDocument,
Map<Integer, List<PdfTableCell>> pdfTableCells,
Document document,
List<Page> pages,
ClassificationDocument document,
List<ClassificationPage> pages,
int pageNumber) throws IOException {
PDFLinesTextStripper stripper = new PDFLinesTextStripper();
@ -117,7 +120,7 @@ public class PdfSegmentationService {
stripper.getRulings(),
stripper.getMinCharWidth(),
stripper.getMaxCharHeight());
Page page = blockificationService.blockify(stripper.getTextPositionSequences(), cleanRulings.getHorizontal(), cleanRulings.getVertical());
ClassificationPage page = blockificationService.blockify(stripper.getTextPositionSequences(), cleanRulings.getHorizontal(), cleanRulings.getVertical());
page.setRotation(rotation);
page.setLandscape(isLandscape);
@ -139,7 +142,7 @@ public class PdfSegmentationService {
}
private void increaseDocumentStatistics(Page page, Document document) {
private void increaseDocumentStatistics(ClassificationPage page, ClassificationDocument document) {
if (!page.isLandscape()) {
document.getFontSizeCounter().addAll(page.getFontSizeCounter().getCountPerValue());
@ -150,7 +153,7 @@ public class PdfSegmentationService {
}
private void buildPageStatistics(Page page) {
private void buildPageStatistics(ClassificationPage page) {
// Collect all statistics for the page, except from blocks inside tables, as tables will always be added to BodyTextFrame.
for (AbstractPageBlock textBlock : page.getTextBlocks()) {

View File

@ -11,11 +11,11 @@ import org.apache.commons.collections4.CollectionUtils;
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.Document;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Footer;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Header;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Page;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Section;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.ClassificationDocument;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.ClassificationFooter;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.ClassificationHeader;
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.image.ClassifiedImage;
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;
@ -28,19 +28,19 @@ import lombok.extern.slf4j.Slf4j;
@Service
public class SectionsBuilderService {
public void buildSections(Document document) {
public void buildSections(ClassificationDocument document) {
List<AbstractPageBlock> chunkWords = new ArrayList<>();
List<Section> chunkBlockList = new ArrayList<>();
List<Header> headers = new ArrayList<>();
List<Footer> footers = new ArrayList<>();
List<ClassificationSection> chunkBlockList = new ArrayList<>();
List<ClassificationHeader> headers = new ArrayList<>();
List<ClassificationFooter> footers = new ArrayList<>();
List<UnclassifiedText> unclassifiedTexts = new ArrayList<>();
AbstractPageBlock prev = null;
String lastHeadline = "";
TablePageBlock previousTable = null;
for (Page page : document.getPages()) {
for (ClassificationPage page : document.getPages()) {
List<ClassificationTextBlock> header = new ArrayList<>();
List<ClassificationTextBlock> footer = new ArrayList<>();
List<ClassificationTextBlock> unclassifiedText = new ArrayList<>();
@ -68,7 +68,7 @@ public class SectionsBuilderService {
}
if (prev != null && current.getClassification().startsWith("H ") && !prev.getClassification().startsWith("H ") || !document.isHeadlines()) {
Section chunkBlock = buildTextBlock(chunkWords, lastHeadline);
ClassificationSection chunkBlock = buildTextBlock(chunkWords, lastHeadline);
chunkBlock.setHeadline(lastHeadline);
if (document.isHeadlines()) {
lastHeadline = current.getText();
@ -89,17 +89,17 @@ public class SectionsBuilderService {
}
if (!header.isEmpty()) {
headers.add(new Header(header));
headers.add(new ClassificationHeader(header));
}
if (!footer.isEmpty()) {
footers.add(new Footer(footer));
footers.add(new ClassificationFooter(footer));
}
if (!unclassifiedText.isEmpty()) {
unclassifiedTexts.add(new UnclassifiedText(unclassifiedText));
}
}
Section chunkBlock = buildTextBlock(chunkWords, lastHeadline);
ClassificationSection chunkBlock = buildTextBlock(chunkWords, lastHeadline);
chunkBlock.setHeadline(lastHeadline);
chunkBlockList.add(chunkBlock);
@ -110,13 +110,13 @@ public class SectionsBuilderService {
}
public void addImagesToSections(Document document) {
public void addImagesToSections(ClassificationDocument document) {
Map<Integer, List<Section>> sectionMap = new HashMap<>();
for (Section section : document.getSections()) {
Map<Integer, List<ClassificationSection>> sectionMap = new HashMap<>();
for (ClassificationSection section : document.getSections()) {
for (AbstractPageBlock container : section.getPageBlocks()) {
List<Section> sectionsOnPage = sectionMap.computeIfAbsent(container.getPage(), c -> new ArrayList<>());
List<ClassificationSection> sectionsOnPage = sectionMap.computeIfAbsent(container.getPage(), c -> new ArrayList<>());
if (sectionsOnPage.contains(section)) {
continue;
}
@ -125,7 +125,7 @@ public class SectionsBuilderService {
}
if (sectionMap.isEmpty()) {
Section section = new Section();
ClassificationSection section = new ClassificationSection();
document.getSections().add(section);
sectionMap.computeIfAbsent(1, x -> new ArrayList<>()).add(section);
}
@ -133,14 +133,14 @@ public class SectionsBuilderService {
// first page is always a paragraph, else we can't process pages 1..N,
// where N is the first found page with a paragraph
if (sectionMap.get(1) == null) {
Section section = new Section();
ClassificationSection section = new ClassificationSection();
document.getSections().add(section);
sectionMap.computeIfAbsent(1, x -> new ArrayList<>()).add(section);
}
for (Page page : document.getPages()) {
for (ClassificationPage page : document.getPages()) {
for (ClassifiedImage image : page.getImages()) {
List<Section> sectionsOnPage = sectionMap.get(page.getPageNumber());
List<ClassificationSection> sectionsOnPage = sectionMap.get(page.getPageNumber());
if (sectionsOnPage == null) {
int i = page.getPageNumber();
while (sectionsOnPage == null) {
@ -148,7 +148,7 @@ public class SectionsBuilderService {
i--;
}
}
for (Section section : sectionsOnPage) {
for (ClassificationSection section : sectionsOnPage) {
Float xMin = null;
Float yMin = null;
Float xMax = null;
@ -240,9 +240,9 @@ public class SectionsBuilderService {
}
private Section buildTextBlock(List<AbstractPageBlock> wordBlockList, String lastHeadline) {
private ClassificationSection buildTextBlock(List<AbstractPageBlock> wordBlockList, String lastHeadline) {
Section section = new Section();
ClassificationSection section = new ClassificationSection();
for (AbstractPageBlock container : wordBlockList) {
if (container instanceof TablePageBlock table) {

View File

@ -14,7 +14,7 @@ import java.util.stream.Collectors;
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.Page;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.ClassificationPage;
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.CleanRulings;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Rectangle;
@ -79,7 +79,7 @@ public class TableExtractionService {
* @param cleanRulings The lines used to build the table.
* @param page Page object that contains textblocks and statistics.
*/
public void extractTables(CleanRulings cleanRulings, Page page) {
public void extractTables(CleanRulings cleanRulings, ClassificationPage page) {
List<Cell> cells = findCells(cleanRulings.getHorizontal(), cleanRulings.getVertical());

View File

@ -15,6 +15,6 @@ public class DocumentData {
PageData[] pages;
AtomicTextBlockData[] atomicTextBlocks;
AtomicPositionBlockData[] atomicPositionBlocks;
TableOfContentsData tableOfContents;
DocumentTreeData documentTreeData;
}

View File

@ -19,7 +19,7 @@ import lombok.experimental.FieldDefaults;
@AllArgsConstructor
@NoArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class TableOfContentsData {
public class DocumentTreeData {
EntryData root;
@ -39,7 +39,7 @@ public class TableOfContentsData {
public Stream<EntryData> streamAllEntries() {
return Stream.concat(Stream.of(root), root.subEntries.stream()).flatMap(TableOfContentsData::flatten);
return Stream.concat(Stream.of(root), root.subEntries.stream()).flatMap(DocumentTreeData::flatten);
}
@ -51,7 +51,7 @@ public class TableOfContentsData {
private static Stream<EntryData> flatten(EntryData entry) {
return Stream.concat(Stream.of(entry), entry.subEntries.stream().flatMap(TableOfContentsData::flatten));
return Stream.concat(Stream.of(entry), entry.subEntries.stream().flatMap(DocumentTreeData::flatten));
}
@ -62,7 +62,7 @@ public class TableOfContentsData {
public static class EntryData {
NodeType type;
int[] tocId;
int[] treeId;
Long[] atomicBlocks;
Long[] pages;
Map<String, String> properties;
@ -74,7 +74,7 @@ public class TableOfContentsData {
StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i : tocId) {
for (int i : treeId) {
sb.append(i);
sb.append(",");
}

View File

@ -8,15 +8,15 @@ import java.util.Map;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.AtomicPositionBlockData;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.AtomicTextBlockData;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.DocumentData;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.DocumentTreeData;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.PageData;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.TableOfContentsData;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.ImageNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.PageNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SectionNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.TableCellNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.TableNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree;
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.Image;
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.Section;
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 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;
@ -25,38 +25,38 @@ import lombok.experimental.UtilityClass;
@UtilityClass
public class DocumentDataMapper {
public DocumentData toDocumentData(DocumentGraph documentGraph) {
public DocumentData toDocumentData(Document document) {
List<AtomicTextBlockData> atomicTextBlockData = documentGraph.streamTerminalTextBlocksInOrder()
List<AtomicTextBlockData> atomicTextBlockData = document.streamTerminalTextBlocksInOrder()
.flatMap(textBlock -> textBlock.getAtomicTextBlocks().stream())
.distinct()
.map(DocumentDataMapper::toAtomicTextBlockData)
.toList();
List<AtomicPositionBlockData> atomicPositionBlockData = documentGraph.streamTerminalTextBlocksInOrder()
List<AtomicPositionBlockData> atomicPositionBlockData = document.streamTerminalTextBlocksInOrder()
.flatMap(textBlock -> textBlock.getAtomicTextBlocks().stream())
.distinct()
.map(DocumentDataMapper::toAtomicPositionBlockData)
.toList();
List<PageData> pageData = documentGraph.getPages().stream().map(DocumentDataMapper::toPageData).toList();
TableOfContentsData tableOfContentsData = toTableOfContentsData(documentGraph.getTableOfContents());
List<PageData> pageData = document.getPages().stream().map(DocumentDataMapper::toPageData).toList();
DocumentTreeData documentTreeData = toTableOfContentsData(document.getDocumentTree());
return DocumentData.builder()
.atomicTextBlocks(atomicTextBlockData.toArray(new AtomicTextBlockData[0]))
.atomicPositionBlocks(atomicPositionBlockData.toArray(new AtomicPositionBlockData[0]))
.pages(pageData.toArray(new PageData[0]))
.tableOfContents(tableOfContentsData)
.documentTreeData(documentTreeData)
.build();
}
private TableOfContentsData toTableOfContentsData(TableOfContents tableOfContents) {
private DocumentTreeData toTableOfContentsData(DocumentTree documentTree) {
return new TableOfContentsData(toEntryData(tableOfContents.getRoot()));
return new DocumentTreeData(toEntryData(documentTree.getRoot()));
}
private TableOfContentsData.EntryData toEntryData(TableOfContents.Entry entry) {
private DocumentTreeData.EntryData toEntryData(DocumentTree.Entry entry) {
Long[] atomicTextBlocks;
@ -67,19 +67,19 @@ public class DocumentDataMapper {
}
Map<String, String> properties = switch (entry.getType()) {
case TABLE -> PropertiesMapper.buildTableProperties((TableNode) entry.getNode());
case TABLE_CELL -> PropertiesMapper.buildTableCellProperties((TableCellNode) entry.getNode());
case IMAGE -> PropertiesMapper.buildImageProperties((ImageNode) entry.getNode());
case SECTION -> PropertiesMapper.buildSectionProperties((SectionNode) entry.getNode());
case TABLE -> PropertiesMapper.buildTableProperties((Table) entry.getNode());
case TABLE_CELL -> PropertiesMapper.buildTableCellProperties((TableCell) entry.getNode());
case IMAGE -> PropertiesMapper.buildImageProperties((Image) entry.getNode());
case SECTION -> PropertiesMapper.buildSectionProperties((Section) entry.getNode());
default -> new HashMap<>();
};
return TableOfContentsData.EntryData.builder()
.tocId(toPrimitiveIntArray(entry.getTocId()))
return DocumentTreeData.EntryData.builder()
.treeId(toPrimitiveIntArray(entry.getTocId()))
.subEntries(entry.getChildren().stream().map(DocumentDataMapper::toEntryData).toList())
.type(entry.getType())
.atomicBlocks(atomicTextBlocks)
.pages(entry.getNode().getPages().stream().map(PageNode::getNumber).map(Integer::longValue).toArray(Long[]::new))
.pages(entry.getNode().getPages().stream().map(Page::getNumber).map(Integer::longValue).toArray(Long[]::new))
.properties(properties)
.build();
}
@ -91,7 +91,7 @@ public class DocumentDataMapper {
}
private PageData toPageData(PageNode p) {
private PageData toPageData(Page p) {
return PageData.builder().rotation(p.getRotation()).height(p.getHeight()).width(p.getWidth()).number(p.getNumber()).build();
}

View File

@ -15,21 +15,21 @@ import com.google.common.primitives.Ints;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.AtomicPositionBlockData;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.AtomicTextBlockData;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.DocumentData;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.DocumentTreeData;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.PageData;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.TableOfContentsData;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.FooterNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.HeaderNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.HeadlineNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.ImageNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.PageNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.ParagraphNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SectionNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree;
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.Footer;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Header;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Headline;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Image;
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.Paragraph;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Section;
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.TableCellNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.TableNode;
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 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.graph.textblock.TextBlockCollector;
@ -39,11 +39,11 @@ import lombok.experimental.UtilityClass;
@UtilityClass
public class DocumentGraphMapper {
public DocumentGraph toDocumentGraph(DocumentData documentData) {
public Document toDocumentGraph(DocumentData documentData) {
DocumentGraph documentGraph = new DocumentGraph();
Document document = new Document();
Context context = new Context(documentData,
new TableOfContents(documentGraph),
new DocumentTree(document),
new LinkedList<>(),
new LinkedList<>(),
Arrays.stream(documentData.getAtomicTextBlocks()).toList(),
@ -51,24 +51,24 @@ public class DocumentGraphMapper {
context.pages.addAll(Arrays.stream(documentData.getPages()).map(DocumentGraphMapper::buildPage).toList());
context.tableOfContents.getRoot().getChildren().addAll(buildEntries(documentData.getTableOfContents().getRoot().getSubEntries(), context));
context.documentTree.getRoot().getChildren().addAll(buildEntries(documentData.getDocumentTreeData().getRoot().getSubEntries(), context));
documentGraph.setTableOfContents(context.tableOfContents);
documentGraph.setPages(new HashSet<>(context.pages));
documentGraph.setNumberOfPages(documentData.getPages().length);
document.setDocumentTree(context.documentTree);
document.setPages(new HashSet<>(context.pages));
document.setNumberOfPages(documentData.getPages().length);
documentGraph.setTextBlock(documentGraph.buildTextBlock());
return documentGraph;
document.setTextBlock(document.buildTextBlock());
return document;
}
private List<TableOfContents.Entry> buildEntries(List<TableOfContentsData.EntryData> entries, Context context) {
private List<DocumentTree.Entry> buildEntries(List<DocumentTreeData.EntryData> entries, Context context) {
List<TableOfContents.Entry> newEntries = new LinkedList<>();
for (TableOfContentsData.EntryData entryData : entries) {
List<DocumentTree.Entry> newEntries = new LinkedList<>();
for (DocumentTreeData.EntryData entryData : entries) {
boolean terminal = isTerminal(entryData);
List<PageNode> pages = Arrays.stream(entryData.getPages()).map(pageNumber -> getPage(pageNumber, context)).toList();
List<Page> pages = Arrays.stream(entryData.getPages()).map(pageNumber -> getPage(pageNumber, context)).toList();
SemanticNode node = switch (entryData.getType()) {
case SECTION -> buildSection(context, entryData.getProperties());
@ -86,83 +86,83 @@ public class DocumentGraphMapper {
TextBlock textBlock = toTextBlock(entryData.getAtomicBlocks(), context, node);
node.setTerminalTextBlock(textBlock);
}
List<Integer> tocId = Arrays.stream(entryData.getTocId()).boxed().toList();
node.setTocId(tocId);
List<Integer> tocId = Arrays.stream(entryData.getTreeId()).boxed().toList();
node.setTreeId(tocId);
if (entryData.getType() == HEADER) {
pages.forEach(page -> page.setHeader((HeaderNode) node));
pages.forEach(page -> page.setHeader((Header) node));
} else if (entryData.getType() == FOOTER) {
pages.forEach(page -> page.setFooter((FooterNode) node));
pages.forEach(page -> page.setFooter((Footer) node));
} else {
pages.forEach(page -> page.getMainBody().add(node));
}
newEntries.add(TableOfContents.Entry.builder().tocId(tocId).type(entryData.getType()).children(buildEntries(entryData.getSubEntries(), context)).node(node).build());
newEntries.add(DocumentTree.Entry.builder().tocId(tocId).type(entryData.getType()).children(buildEntries(entryData.getSubEntries(), context)).node(node).build());
}
return newEntries;
}
private HeadlineNode buildHeadline(Context context, boolean terminal) {
private Headline buildHeadline(Context context, boolean terminal) {
return HeadlineNode.builder().terminal(terminal).tableOfContents(context.tableOfContents()).build();
return Headline.builder().terminal(terminal).documentTree(context.documentTree()).build();
}
private static boolean isTerminal(TableOfContentsData.EntryData entryData) {
private static boolean isTerminal(DocumentTreeData.EntryData entryData) {
return entryData.getAtomicBlocks().length > 0;
}
private ImageNode buildImage(Context context, Map<String, String> properties, Long[] pageNumbers) {
private Image buildImage(Context context, Map<String, String> properties, Long[] pageNumbers) {
assert pageNumbers.length == 1;
PageNode pageNode = getPage(pageNumbers[0], context);
var builder = ImageNode.builder();
Page page = getPage(pageNumbers[0], context);
var builder = Image.builder();
PropertiesMapper.parseImageProperties(properties, builder);
return builder.tableOfContents(context.tableOfContents()).page(pageNode).build();
return builder.documentTree(context.documentTree()).page(page).build();
}
private TableCellNode buildTableCell(Context context, Map<String, String> properties, boolean terminal) {
private TableCell buildTableCell(Context context, Map<String, String> properties, boolean terminal) {
TableCellNode.TableCellNodeBuilder builder = TableCellNode.builder();
TableCell.TableCellBuilder builder = TableCell.builder();
PropertiesMapper.parseTableCellProperties(properties, builder);
return builder.terminal(terminal).tableOfContents(context.tableOfContents()).build();
return builder.terminal(terminal).documentTree(context.documentTree()).build();
}
private TableNode buildTable(Context context, Map<String, String> properties) {
private Table buildTable(Context context, Map<String, String> properties) {
TableNode.TableNodeBuilder builder = TableNode.builder();
Table.TableBuilder builder = Table.builder();
PropertiesMapper.parseTableProperties(properties, builder);
return builder.tableOfContents(context.tableOfContents()).build();
return builder.documentTree(context.documentTree()).build();
}
private FooterNode buildFooter(Context context, boolean terminal) {
private Footer buildFooter(Context context, boolean terminal) {
return FooterNode.builder().terminal(terminal).tableOfContents(context.tableOfContents()).build();
return Footer.builder().terminal(terminal).documentTree(context.documentTree()).build();
}
private HeaderNode buildHeader(Context context, boolean terminal) {
private Header buildHeader(Context context, boolean terminal) {
return HeaderNode.builder().terminal(terminal).tableOfContents(context.tableOfContents()).build();
return Header.builder().terminal(terminal).documentTree(context.documentTree()).build();
}
private SectionNode buildSection(Context context, Map<String, String> properties) {
private Section buildSection(Context context, Map<String, String> properties) {
var builder = SectionNode.builder();
var builder = Section.builder();
PropertiesMapper.parseSectionProperties(properties, builder);
return builder.tableOfContents(context.tableOfContents()).build();
return builder.documentTree(context.documentTree()).build();
}
private ParagraphNode buildParagraph(Context context, boolean terminal) {
private Paragraph buildParagraph(Context context, boolean terminal) {
return ParagraphNode.builder().terminal(terminal).tableOfContents(context.tableOfContents()).build();
return Paragraph.builder().terminal(terminal).documentTree(context.documentTree()).build();
}
@ -177,9 +177,9 @@ public class DocumentGraphMapper {
}
private PageNode buildPage(PageData p) {
private Page buildPage(PageData p) {
return PageNode.builder().rotation(p.getRotation()).height(p.getHeight()).width(p.getWidth()).number(p.getNumber()).mainBody(new LinkedList<>()).build();
return Page.builder().rotation(p.getRotation()).height(p.getHeight()).width(p.getWidth()).number(p.getNumber()).mainBody(new LinkedList<>()).build();
}
@ -205,7 +205,7 @@ public class DocumentGraphMapper {
}
private PageNode getPage(Long pageIndex, Context context) {
private Page getPage(Long pageIndex, Context context) {
return context.pages.stream()
.filter(page -> page.getNumber() == Math.toIntExact(pageIndex))
@ -216,9 +216,9 @@ public class DocumentGraphMapper {
record Context(
DocumentData layoutParsingModel,
TableOfContents tableOfContents,
List<PageNode> pages,
List<SectionNode> sections,
DocumentTree documentTree,
List<Page> pages,
List<Section> sections,
List<AtomicTextBlockData> atomicTextBlockData,
List<AtomicPositionBlockData> atomicPositionBlockData) {

View File

@ -5,16 +5,16 @@ import static com.iqser.red.service.redaction.v1.server.layoutparsing.document.u
import java.util.HashMap;
import java.util.Map;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.ImageNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SectionNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.TableCellNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.TableNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Image;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.ImageType;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Section;
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 com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RectangleMapper;
import com.iqser.red.service.redaction.v1.server.redaction.model.ImageType;
public class PropertiesMapper {
public static Map<String, String> buildImageProperties(ImageNode image) {
public static Map<String, String> buildImageProperties(Image image) {
Map<String, String> properties = new HashMap<>();
properties.put("imageType", image.getImageType().toString());
@ -25,7 +25,7 @@ public class PropertiesMapper {
}
public static Map<String, String> buildTableCellProperties(TableCellNode tableCell) {
public static Map<String, String> buildTableCellProperties(TableCell tableCell) {
Map<String, String> properties = new HashMap<>();
properties.put("row", String.valueOf(tableCell.getRow()));
@ -42,7 +42,7 @@ public class PropertiesMapper {
}
public static Map<String, String> buildTableProperties(TableNode table) {
public static Map<String, String> buildTableProperties(Table table) {
Map<String, String> properties = new HashMap<>();
properties.put("numberOfRows", String.valueOf(table.getNumberOfRows()));
@ -51,7 +51,7 @@ public class PropertiesMapper {
}
public static Map<String, String> buildSectionProperties(SectionNode node) {
public static Map<String, String> buildSectionProperties(Section node) {
Map<String, String> properties = new HashMap<>();
properties.put("excludesTables", String.valueOf(node.isExcludesTables()));
@ -59,7 +59,7 @@ public class PropertiesMapper {
}
public static void parseImageProperties(Map<String, String> properties, ImageNode.ImageNodeBuilder builder) {
public static void parseImageProperties(Map<String, String> properties, Image.ImageBuilder builder) {
builder.imageType(parseImageType(properties.get("imageType")));
builder.transparent(Boolean.parseBoolean(properties.get("transparency")));
@ -68,7 +68,7 @@ public class PropertiesMapper {
}
public static void parseTableCellProperties(Map<String, String> properties, TableCellNode.TableCellNodeBuilder builder) {
public static void parseTableCellProperties(Map<String, String> properties, TableCell.TableCellBuilder builder) {
builder.row(Integer.parseInt(properties.get("row")));
builder.col(Integer.parseInt(properties.get("col")));
@ -77,14 +77,14 @@ public class PropertiesMapper {
}
public static void parseTableProperties(Map<String, String> properties, TableNode.TableNodeBuilder builder) {
public static void parseTableProperties(Map<String, String> properties, Table.TableBuilder builder) {
builder.numberOfRows(Integer.parseInt(properties.get("numberOfRows")));
builder.numberOfCols(Integer.parseInt(properties.get("numberOfCols")));
}
public static void parseSectionProperties(Map<String, String> properties, SectionNode.SectionNodeBuilder builder) {
public static void parseSectionProperties(Map<String, String> properties, Section.SectionBuilder builder) {
builder.excludesTables(Boolean.parseBoolean(properties.get("excludesTables")));
}

View File

@ -14,25 +14,24 @@ import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.AbstractPageBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Document;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Footer;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Header;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Page;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.ClassificationDocument;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.ClassificationFooter;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.ClassificationHeader;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.ClassificationPage;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.image.ClassifiedImage;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.FooterNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.HeaderNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.HeadlineNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.ImageNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree;
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.Footer;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Header;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Headline;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Image;
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.PageNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.ParagraphNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SectionNode;
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.Paragraph;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Section;
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.AtomicTextBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RectangleMapper;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.TextPositionOperations;
import com.iqser.red.service.redaction.v1.server.redaction.utils.IdBuilder;
@ -45,11 +44,11 @@ import lombok.experimental.UtilityClass;
@UtilityClass
public class DocumentGraphFactory {
public DocumentGraph buildDocumentGraph(Document document) {
public Document buildDocumentGraph(ClassificationDocument document) {
TextBlockFactory textBlockFactory = new TextBlockFactory();
DocumentGraph documentGraph = new DocumentGraph();
Context context = new Context(new TableOfContents(documentGraph), new HashMap<>(), new LinkedList<>(), new LinkedList<>(), textBlockFactory);
Document documentGraph = new Document();
Context context = new Context(new DocumentTree(documentGraph), new HashMap<>(), new LinkedList<>(), new LinkedList<>(), textBlockFactory);
document.getPages().stream().map(DocumentGraphFactory::buildPage).forEach(page -> context.getPages().put(page, new AtomicInteger(1)));
document.getSections().stream().flatMap(section -> section.getImages().stream()).forEach(image -> context.getImages().add(image));
@ -58,13 +57,13 @@ public class DocumentGraphFactory {
documentGraph.setNumberOfPages(context.pages.size());
documentGraph.setPages(context.pages.keySet());
documentGraph.setTableOfContents(context.tableOfContents);
documentGraph.setDocumentTree(context.documentTree);
documentGraph.setTextBlock(documentGraph.buildTextBlock());
return documentGraph;
}
private void addSections(Document document, Context context) {
private void addSections(ClassificationDocument document, Context context) {
document.getSections().forEach(section -> SectionNodeFactory.addSection(null, section.getPageBlocks(), section.getImages(), context));
}
@ -78,13 +77,13 @@ public class DocumentGraphFactory {
public void addParagraphOrHeadline(SemanticNode parentNode, ClassificationTextBlock originalTextBlock, Context context, List<ClassificationTextBlock> textBlocksToMerge) {
PageNode page = context.getPage(originalTextBlock.getPage());
Page page = context.getPage(originalTextBlock.getPage());
SemanticNode node;
if (pageBlockIsHeadline(originalTextBlock)) {
node = HeadlineNode.builder().tableOfContents(context.getTableOfContents()).build();
node = Headline.builder().documentTree(context.getDocumentTree()).build();
} else {
node = ParagraphNode.builder().tableOfContents(context.getTableOfContents()).build();
node = Paragraph.builder().documentTree(context.getDocumentTree()).build();
}
page.getMainBody().add(node);
@ -93,49 +92,49 @@ public class DocumentGraphFactory {
textBlocks.add(originalTextBlock);
AtomicTextBlock textBlock = context.textBlockFactory.buildAtomicTextBlock(TextPositionOperations.mergeAndSortTextPositionSequenceByYThenX(textBlocks), node, context, page);
if (node instanceof HeadlineNode headlineNode) {
List<Integer> tocId = context.tableOfContents.createNewChildEntryAndReturnId(parentNode.getTocId(), NodeType.HEADLINE, node);
headlineNode.setTerminalTextBlock(textBlock);
headlineNode.setTocId(tocId);
if (node instanceof Headline headline) {
List<Integer> tocId = context.documentTree.createNewChildEntryAndReturnId(parentNode.getTreeId(), NodeType.HEADLINE, node);
headline.setTerminalTextBlock(textBlock);
headline.setTreeId(tocId);
}
if (node instanceof ParagraphNode paragraphNode) {
List<Integer> tocId = context.tableOfContents.createNewChildEntryAndReturnId(parentNode.getTocId(), NodeType.PARAGRAPH, node);
paragraphNode.setTerminalTextBlock(textBlock);
paragraphNode.setTocId(tocId);
if (node instanceof Paragraph paragraph) {
List<Integer> tocId = context.documentTree.createNewChildEntryAndReturnId(parentNode.getTreeId(), NodeType.PARAGRAPH, node);
paragraph.setTerminalTextBlock(textBlock);
paragraph.setTreeId(tocId);
}
}
public void addImage(SectionNode sectionNode, ClassifiedImage image, Context context) {
public void addImage(Section section, ClassifiedImage image, Context context) {
Rectangle2D position = RectangleMapper.toRectangle2D(image.getPosition());
PageNode page = context.getPage(image.getPage());
ImageNode imageNode = ImageNode.builder()
Rectangle2D position = image.getPosition();
Page page = context.getPage(image.getPage());
Image imageNode = Image.builder()
.id(IdBuilder.buildId(Set.of(page), List.of(position)))
.imageType(image.getImageType())
.position(position)
.transparent(image.isHasTransparency())
.page(page)
.tableOfContents(context.getTableOfContents())
.documentTree(context.getDocumentTree())
.build();
page.getMainBody().add(imageNode);
List<Integer> tocId = context.getTableOfContents().createNewChildEntryAndReturnId(sectionNode.getTocId(), NodeType.IMAGE, imageNode);
imageNode.setTocId(tocId);
List<Integer> tocId = context.getDocumentTree().createNewChildEntryAndReturnId(section.getTreeId(), NodeType.IMAGE, imageNode);
imageNode.setTreeId(tocId);
}
private void addHeaderAndFooterToEachPage(Document document, Context context) {
private void addHeaderAndFooterToEachPage(ClassificationDocument document, Context context) {
Map<Integer, List<ClassificationTextBlock>> headers = document.getHeaders()
.stream()
.map(Header::getTextBlocks)
.map(ClassificationHeader::getTextBlocks)
.flatMap(List::stream)
.collect(groupingBy(AbstractPageBlock::getPage, toList()));
Map<Integer, List<ClassificationTextBlock>> footers = document.getFooters()
.stream()
.map(Footer::getTextBlocks)
.map(ClassificationFooter::getTextBlocks)
.flatMap(List::stream)
.collect(groupingBy(AbstractPageBlock::getPage, toList()));
@ -159,14 +158,14 @@ public class DocumentGraphFactory {
private void addFooter(List<ClassificationTextBlock> textBlocks, Context context) {
PageNode page = context.getPage(textBlocks.get(0).getPage());
FooterNode footer = FooterNode.builder().tableOfContents(context.getTableOfContents()).build();
Page page = context.getPage(textBlocks.get(0).getPage());
Footer footer = Footer.builder().documentTree(context.getDocumentTree()).build();
AtomicTextBlock textBlock = context.textBlockFactory.buildAtomicTextBlock(TextPositionOperations.mergeAndSortTextPositionSequenceByYThenX(textBlocks),
footer,
context,
page);
List<Integer> tocId = context.getTableOfContents().createNewMainEntryAndReturnId(NodeType.FOOTER, footer);
footer.setTocId(tocId);
List<Integer> tocId = context.getDocumentTree().createNewMainEntryAndReturnId(NodeType.FOOTER, footer);
footer.setTreeId(tocId);
footer.setTerminalTextBlock(textBlock);
page.setFooter(footer);
}
@ -174,15 +173,15 @@ public class DocumentGraphFactory {
public void addHeader(List<ClassificationTextBlock> textBlocks, Context context) {
PageNode page = context.getPage(textBlocks.get(0).getPage());
HeaderNode header = HeaderNode.builder().tableOfContents(context.getTableOfContents()).build();
Page page = context.getPage(textBlocks.get(0).getPage());
Header header = Header.builder().documentTree(context.getDocumentTree()).build();
AtomicTextBlock textBlock = context.textBlockFactory.buildAtomicTextBlock(TextPositionOperations.mergeAndSortTextPositionSequenceByYThenX(textBlocks),
header,
context,
0,
page);
List<Integer> tocId = context.getTableOfContents().createNewMainEntryAndReturnId(NodeType.HEADER, header);
header.setTocId(tocId);
List<Integer> tocId = context.getDocumentTree().createNewMainEntryAndReturnId(NodeType.HEADER, header);
header.setTreeId(tocId);
header.setTerminalTextBlock(textBlock);
page.setHeader(header);
}
@ -190,11 +189,11 @@ public class DocumentGraphFactory {
private void addEmptyFooter(int pageIndex, Context context) {
PageNode page = context.getPage(pageIndex);
FooterNode footer = FooterNode.builder().tableOfContents(context.getTableOfContents()).build();
Page page = context.getPage(pageIndex);
Footer footer = Footer.builder().documentTree(context.getDocumentTree()).build();
AtomicTextBlock textBlock = context.textBlockFactory.emptyTextBlock(footer, context, page);
List<Integer> tocId = context.getTableOfContents().createNewMainEntryAndReturnId(NodeType.FOOTER, footer);
footer.setTocId(tocId);
List<Integer> tocId = context.getDocumentTree().createNewMainEntryAndReturnId(NodeType.FOOTER, footer);
footer.setTreeId(tocId);
footer.setTerminalTextBlock(textBlock);
page.setFooter(footer);
}
@ -202,19 +201,19 @@ public class DocumentGraphFactory {
private void addEmptyHeader(int pageIndex, Context context) {
PageNode page = context.getPage(pageIndex);
HeaderNode header = HeaderNode.builder().tableOfContents(context.getTableOfContents()).build();
Page page = context.getPage(pageIndex);
Header header = Header.builder().documentTree(context.getDocumentTree()).build();
AtomicTextBlock textBlock = context.textBlockFactory.emptyTextBlock(header, 0, page);
List<Integer> tocId = context.getTableOfContents().createNewMainEntryAndReturnId(NodeType.HEADER, header);
header.setTocId(tocId);
List<Integer> tocId = context.getDocumentTree().createNewMainEntryAndReturnId(NodeType.HEADER, header);
header.setTreeId(tocId);
header.setTerminalTextBlock(textBlock);
page.setHeader(header);
}
private PageNode buildPage(Page p) {
private Page buildPage(ClassificationPage p) {
return PageNode.builder()
return Page.builder()
.height((int) p.getPageHeight())
.width((int) p.getPageWidth())
.number(p.getPageNumber())
@ -229,14 +228,14 @@ public class DocumentGraphFactory {
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
static final class Context {
TableOfContents tableOfContents;
Map<PageNode, AtomicInteger> pages;
List<SectionNode> sections;
DocumentTree documentTree;
Map<Page, AtomicInteger> pages;
List<Section> sections;
List<ClassifiedImage> images;
TextBlockFactory textBlockFactory;
public PageNode getPage(int pageIndex) {
public Page getPage(int pageIndex) {
return pages.keySet()
.stream()

View File

@ -15,8 +15,8 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.mo
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 com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.NodeType;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.PageNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SectionNode;
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.Section;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SemanticNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.TableMergingUtility;
@ -31,30 +31,30 @@ public class SectionNodeFactory {
return;
}
Map<Integer, List<AbstractPageBlock>> blocksPerPage = pageBlocks.stream().collect(groupingBy(AbstractPageBlock::getPage));
SectionNode sectionNode = SectionNode.builder().entities(new HashSet<>()).tableOfContents(context.getTableOfContents()).build();
Section section = Section.builder().entities(new HashSet<>()).documentTree(context.getDocumentTree()).build();
context.getSections().add(sectionNode);
blocksPerPage.keySet().forEach(pageNumber -> addSectionNodeToPageNode(context, sectionNode, pageNumber));
context.getSections().add(section);
blocksPerPage.keySet().forEach(pageNumber -> addSectionNodeToPageNode(context, section, pageNumber));
List<Integer> tocId;
if (parentNode == null) {
tocId = context.getTableOfContents().createNewMainEntryAndReturnId(NodeType.SECTION, sectionNode);
tocId = context.getDocumentTree().createNewMainEntryAndReturnId(NodeType.SECTION, section);
} else {
tocId = context.getTableOfContents().createNewChildEntryAndReturnId(parentNode.getTocId(), NodeType.SECTION, sectionNode);
tocId = context.getDocumentTree().createNewChildEntryAndReturnId(parentNode.getTreeId(), NodeType.SECTION, section);
}
sectionNode.setTocId(tocId);
section.setTreeId(tocId);
addFirstHeadlineDirectlyToSection(pageBlocks, context, sectionNode);
addFirstHeadlineDirectlyToSection(pageBlocks, context, section);
if (containsTablesAndTextBlocks(pageBlocks)) {
splitPageBlocksIntoSubSections(pageBlocks).forEach(subSectionPageBlocks -> addSection(sectionNode, subSectionPageBlocks, emptyList(), context));
splitPageBlocksIntoSubSections(pageBlocks).forEach(subSectionPageBlocks -> addSection(section, subSectionPageBlocks, emptyList(), context));
} else {
addTablesOrParagraphsOrHeadlinesToSection(pageBlocks, context, sectionNode);
addTablesOrParagraphsOrHeadlinesToSection(pageBlocks, context, section);
if (listIsTextBlockOnly(pageBlocks)) {
sectionNode.setExcludesTables(true);
section.setExcludesTables(true);
}
}
images.stream().distinct().forEach(image -> DocumentGraphFactory.addImage(sectionNode, image, context));
images.stream().distinct().forEach(image -> DocumentGraphFactory.addImage(section, image, context));
}
@ -64,16 +64,16 @@ public class SectionNodeFactory {
}
private void addFirstHeadlineDirectlyToSection(List<AbstractPageBlock> pageBlocks, DocumentGraphFactory.Context context, SectionNode sectionNode) {
private void addFirstHeadlineDirectlyToSection(List<AbstractPageBlock> pageBlocks, DocumentGraphFactory.Context context, Section section) {
if (pageBlockIsHeadline(pageBlocks.get(0))) {
addTablesOrParagraphsOrHeadlinesToSection(List.of(pageBlocks.get(0)), context, sectionNode);
addTablesOrParagraphsOrHeadlinesToSection(List.of(pageBlocks.get(0)), context, section);
pageBlocks.remove(0);
}
}
private void addTablesOrParagraphsOrHeadlinesToSection(List<AbstractPageBlock> pageBlocks, DocumentGraphFactory.Context context, SectionNode sectionNode) {
private void addTablesOrParagraphsOrHeadlinesToSection(List<AbstractPageBlock> pageBlocks, DocumentGraphFactory.Context context, Section section) {
Set<AbstractPageBlock> alreadyMerged = new HashSet<>();
List<AbstractPageBlock> remainingBlocks = new LinkedList<>(pageBlocks);
@ -88,13 +88,13 @@ public class SectionNodeFactory {
if (abstractPageBlock instanceof ClassificationTextBlock) {
List<ClassificationTextBlock> textBlocks = findTextBlocksWithSameClassificationAndAlignsY(abstractPageBlock, remainingBlocks);
alreadyMerged.addAll(textBlocks);
DocumentGraphFactory.addParagraphOrHeadline(sectionNode, (ClassificationTextBlock) abstractPageBlock, context, textBlocks);
DocumentGraphFactory.addParagraphOrHeadline(section, (ClassificationTextBlock) abstractPageBlock, context, textBlocks);
}
if (abstractPageBlock instanceof TablePageBlock tablePageBlock) {
List<TablePageBlock> tablesToMerge = TableMergingUtility.findConsecutiveTablesWithSameColCountAndSameHeaders(tablePageBlock, remainingBlocks);
alreadyMerged.addAll(tablesToMerge);
TableNodeFactory.addTable(sectionNode, tablesToMerge, context);
TableNodeFactory.addTable(section, tablesToMerge, context);
}
}
}
@ -181,10 +181,10 @@ public class SectionNodeFactory {
}
private void addSectionNodeToPageNode(DocumentGraphFactory.Context context, SectionNode sectionNode, Integer pageNumber) {
private void addSectionNodeToPageNode(DocumentGraphFactory.Context context, Section section, Integer pageNumber) {
PageNode page = context.getPage(pageNumber);
page.getMainBody().add(sectionNode);
Page page = context.getPage(pageNumber);
page.getMainBody().add(section);
}
}

View File

@ -12,10 +12,10 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.mo
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.TextPositionSequence;
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.PageNode;
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.TableCellNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.TableNode;
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 com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.TextPositionOperations;
import lombok.experimental.UtilityClass;
@ -29,17 +29,17 @@ public class TableNodeFactory {
public void addTable(SemanticNode parentNode, List<TablePageBlock> tablesToMerge, DocumentGraphFactory.Context context) {
setPageNumberInCellTextBlocks(tablesToMerge);
Set<PageNode> pages = tablesToMerge.stream().map(AbstractPageBlock::getPage).map(context::getPage).collect(Collectors.toSet());
Set<Page> pages = tablesToMerge.stream().map(AbstractPageBlock::getPage).map(context::getPage).collect(Collectors.toSet());
List<List<Cell>> mergedRows = tablesToMerge.stream().map(TablePageBlock::getRows).flatMap(Collection::stream).toList();
TableNode tableNode = TableNode.builder().tableOfContents(context.getTableOfContents()).numberOfCols(mergedRows.get(0).size()).numberOfRows(mergedRows.size()).build();
Table table = Table.builder().documentTree(context.getDocumentTree()).numberOfCols(mergedRows.get(0).size()).numberOfRows(mergedRows.size()).build();
pages.forEach(page -> addTableToPage(page, parentNode, tableNode));
pages.forEach(page -> addTableToPage(page, parentNode, table));
List<Integer> tocId = context.getTableOfContents().createNewChildEntryAndReturnId(parentNode.getTocId(), NodeType.TABLE, tableNode);
tableNode.setTocId(tocId);
addTableCells(mergedRows, tableNode, context);
List<Integer> tocId = context.getDocumentTree().createNewChildEntryAndReturnId(parentNode.getTreeId(), NodeType.TABLE, table);
table.setTreeId(tocId);
addTableCells(mergedRows, table, context);
IfTableHasNoHeadersAssumeFirstRowAreHeaders(tableNode);
IfTableHasNoHeadersAssumeFirstRowAreHeaders(table);
}
@ -53,29 +53,29 @@ public class TableNodeFactory {
}
private static void addTableToPage(PageNode page, SemanticNode parentNode, TableNode tableNode) {
private static void addTableToPage(Page page, SemanticNode parentNode, Table table) {
if (!page.getMainBody().contains(parentNode)) {
parentNode.getPages().add(page);
}
page.getMainBody().add(tableNode);
page.getMainBody().add(table);
}
private static void IfTableHasNoHeadersAssumeFirstRowAreHeaders(TableNode tableNode) {
private static void IfTableHasNoHeadersAssumeFirstRowAreHeaders(Table table) {
if (tableNode.streamHeaders().findAny().isEmpty()) {
tableNode.streamRow(0).forEach(tableCellNode -> tableCellNode.setHeader(true));
if (table.streamHeaders().findAny().isEmpty()) {
table.streamRow(0).forEach(tableCellNode -> tableCellNode.setHeader(true));
}
}
private void addTableCells(List<List<Cell>> rows, TableNode tableNode, DocumentGraphFactory.Context context) {
private void addTableCells(List<List<Cell>> rows, Table table, DocumentGraphFactory.Context context) {
for (int rowIndex = 0; rowIndex < rows.size(); rowIndex++) {
for (int colIndex = 0; colIndex < rows.get(rowIndex).size(); colIndex++) {
addTableCell(rows.get(rowIndex).get(colIndex), rowIndex, colIndex, tableNode, context);
addTableCell(rows.get(rowIndex).get(colIndex), rowIndex, colIndex, table, context);
}
}
}
@ -84,50 +84,44 @@ public class TableNodeFactory {
@SuppressWarnings("PMD.UnusedPrivateMethod") // PMD actually flags this wrong
private void addTableCell(Cell cell, int rowIndex, int colIndex, SemanticNode parentNode, DocumentGraphFactory.Context context) {
PageNode page = context.getPage(cell.getPageNumber());
Page page = context.getPage(cell.getPageNumber());
TableCellNode tableCellNode = TableCellNode.builder()
.tableOfContents(context.getTableOfContents())
.row(rowIndex)
.col(colIndex)
.header(cell.isHeaderCell())
.bBox(cell.getBounds2D())
.build();
page.getMainBody().add(tableCellNode);
TableCell tableCell = TableCell.builder().documentTree(context.getDocumentTree()).row(rowIndex).col(colIndex).header(cell.isHeaderCell()).bBox(cell.getBounds2D()).build();
page.getMainBody().add(tableCell);
com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock textBlock;
List<Integer> tocId = context.getTableOfContents().createNewChildEntryAndReturnId(parentNode.getTocId(), NodeType.TABLE_CELL, tableCellNode);
tableCellNode.setTocId(tocId);
List<Integer> tocId = context.getDocumentTree().createNewChildEntryAndReturnId(parentNode.getTreeId(), NodeType.TABLE_CELL, tableCell);
tableCell.setTreeId(tocId);
if (cell.getTextBlocks().isEmpty()) {
tableCellNode.setTerminalTextBlock(context.getTextBlockFactory().emptyTextBlock(parentNode, context, page));
tableCellNode.setTerminal(true);
tableCell.setTerminalTextBlock(context.getTextBlockFactory().emptyTextBlock(parentNode, context, page));
tableCell.setTerminal(true);
} else if (cell.getTextBlocks().size() == 1) {
textBlock = context.getTextBlockFactory().buildAtomicTextBlock(cell.getTextBlocks().get(0).getSequences(), tableCellNode, context, page);
tableCellNode.setTerminalTextBlock(textBlock);
tableCellNode.setTerminal(true);
textBlock = context.getTextBlockFactory().buildAtomicTextBlock(cell.getTextBlocks().get(0).getSequences(), tableCell, context, page);
tableCell.setTerminalTextBlock(textBlock);
tableCell.setTerminal(true);
} else if (firstTextBlockIsHeadline(cell)) {
SectionNodeFactory.addSection(tableCellNode, cell.getTextBlocks().stream().map(tb -> (AbstractPageBlock) tb).toList(), emptyList(), context);
tableCellNode.setTerminal(false);
SectionNodeFactory.addSection(tableCell, cell.getTextBlocks().stream().map(tb -> (AbstractPageBlock) tb).toList(), emptyList(), context);
tableCell.setTerminal(false);
} else if (cellAreaIsSmallerThanPageAreaTimesThreshold(cell, page)) {
List<TextPositionSequence> sequences = TextPositionOperations.mergeAndSortTextPositionSequenceByYThenX(cell.getTextBlocks());
textBlock = context.getTextBlockFactory().buildAtomicTextBlock(sequences, tableCellNode, context, page);
tableCellNode.setTerminalTextBlock(textBlock);
tableCellNode.setTerminal(true);
textBlock = context.getTextBlockFactory().buildAtomicTextBlock(sequences, tableCell, context, page);
tableCell.setTerminalTextBlock(textBlock);
tableCell.setTerminal(true);
} else {
cell.getTextBlocks().forEach(tb -> DocumentGraphFactory.addParagraphOrHeadline(tableCellNode, tb, context, emptyList()));
tableCellNode.setTerminal(false);
cell.getTextBlocks().forEach(tb -> DocumentGraphFactory.addParagraphOrHeadline(tableCell, tb, context, emptyList()));
tableCell.setTerminal(false);
}
}
private static boolean cellAreaIsSmallerThanPageAreaTimesThreshold(Cell cell, PageNode page) {
private static boolean cellAreaIsSmallerThanPageAreaTimesThreshold(Cell cell, Page page) {
return cell.getArea() < TABLE_CELL_MERGE_CONTENTS_SIZE_THRESHOLD * page.getHeight() * page.getWidth();
}

View File

@ -7,7 +7,7 @@ import java.util.concurrent.atomic.AtomicLong;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextPositionSequence;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.PageNode;
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.textblock.AtomicTextBlock;
@ -24,18 +24,14 @@ public class TextBlockFactory {
}
public AtomicTextBlock buildAtomicTextBlock(List<TextPositionSequence> sequences, SemanticNode parent, DocumentGraphFactory.Context context, PageNode page) {
public AtomicTextBlock buildAtomicTextBlock(List<TextPositionSequence> sequences, SemanticNode parent, DocumentGraphFactory.Context context, Page page) {
Integer numberOnPage = context.getPages().get(page).getAndIncrement();
return buildAtomicTextBlock(sequences, parent, context, numberOnPage, page);
}
public AtomicTextBlock buildAtomicTextBlock(List<TextPositionSequence> sequences,
SemanticNode parent,
DocumentGraphFactory.Context context,
Integer numberOnPage,
PageNode page) {
public AtomicTextBlock buildAtomicTextBlock(List<TextPositionSequence> sequences, SemanticNode parent, DocumentGraphFactory.Context context, Integer numberOnPage, Page page) {
SearchTextWithTextPositionDto searchTextWithTextPositionDto = SearchTextWithTextPositionFactory.buildSearchTextToTextPositionModel(sequences);
int offset = stringOffset.getAndAdd(searchTextWithTextPositionDto.getSearchText().length());
@ -54,13 +50,13 @@ public class TextBlockFactory {
}
public AtomicTextBlock emptyTextBlock(SemanticNode parent, DocumentGraphFactory.Context context, PageNode page) {
public AtomicTextBlock emptyTextBlock(SemanticNode parent, DocumentGraphFactory.Context context, Page page) {
return emptyTextBlock(parent, context.getPages().get(page).getAndIncrement(), page);
}
public AtomicTextBlock emptyTextBlock(SemanticNode parent, Integer numberOnPage, PageNode page) {
public AtomicTextBlock emptyTextBlock(SemanticNode parent, Integer numberOnPage, Page page) {
return AtomicTextBlock.builder()
.id(textBlockIdx.getAndIncrement())

View File

@ -9,7 +9,7 @@ import java.util.List;
import java.util.stream.Stream;
import com.google.common.hash.Hashing;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
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.SemanticNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
@ -23,14 +23,14 @@ import lombok.Getter;
import lombok.experimental.FieldDefaults;
@Data
public class TableOfContents {
public class DocumentTree {
private final Entry root;
public TableOfContents(DocumentGraph documentGraph) {
public DocumentTree(Document document) {
root = Entry.builder().tocId(Collections.emptyList()).type(NodeType.DOCUMENT).children(new LinkedList<>()).node(documentGraph).build();
root = Entry.builder().tocId(Collections.emptyList()).type(NodeType.DOCUMENT).children(new LinkedList<>()).node(document).build();
}
@ -128,13 +128,13 @@ public class TableOfContents {
public Stream<Entry> streamAllEntriesInOrder() {
return Stream.of(root).flatMap(TableOfContents::flatten);
return Stream.of(root).flatMap(DocumentTree::flatten);
}
public Stream<Entry> streamAllSubEntriesInOrder(List<Integer> parentId) {
return getEntryById(parentId).children.stream().flatMap(TableOfContents::flatten);
return getEntryById(parentId).children.stream().flatMap(DocumentTree::flatten);
}
@ -147,7 +147,7 @@ public class TableOfContents {
private static Stream<Entry> flatten(Entry entry) {
return Stream.concat(Stream.of(entry), entry.children.stream().flatMap(TableOfContents::flatten));
return Stream.concat(Stream.of(entry), entry.children.stream().flatMap(DocumentTree::flatten));
}

View File

@ -1,4 +1,4 @@
package com.iqser.red.service.redaction.v1.server.redaction.model;
package com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity;
public enum EntityType {
ENTITY,

View File

@ -14,9 +14,8 @@ import java.util.Set;
import com.google.common.hash.Hashing;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Engine;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.PageNode;
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.redaction.model.EntityType;
import com.iqser.red.service.redaction.v1.server.redaction.utils.IdBuilder;
import lombok.AccessLevel;
@ -56,7 +55,7 @@ public class RedactionEntity {
String textBefore;
String textAfter;
@Builder.Default
Set<PageNode> pages = new HashSet<>();
Set<Page> pages = new HashSet<>();
List<RedactionPosition> redactionPositionsPerPage;
@Builder.Default
List<SemanticNode> intersectingNodes = new LinkedList<>();
@ -138,11 +137,11 @@ public class RedactionEntity {
public List<RedactionPosition> getRedactionPositionsPerPage() {
if (redactionPositionsPerPage == null || redactionPositionsPerPage.isEmpty()) {
Map<PageNode, List<Rectangle2D>> rectanglesPerLinePerPage = deepestFullyContainingNode.buildTextBlock().getEntityPositionsPerPage(boundary);
Map<Page, List<Rectangle2D>> rectanglesPerLinePerPage = deepestFullyContainingNode.buildTextBlock().getEntityPositionsPerPage(boundary);
PageNode firstPage = rectanglesPerLinePerPage.keySet()
Page firstPage = rectanglesPerLinePerPage.keySet()
.stream()
.min(Comparator.comparingInt(PageNode::getNumber))
.min(Comparator.comparingInt(Page::getNumber))
.orElseThrow(() -> new RuntimeException("No Positions found on any page!"));
String id = IdBuilder.buildId(pages, rectanglesPerLinePerPage.values().stream().flatMap(Collection::stream).toList());
@ -152,7 +151,7 @@ public class RedactionEntity {
}
private static RedactionPosition buildRedactionPosition(PageNode firstPage, String id, Map.Entry<PageNode, List<Rectangle2D>> entry) {
private static RedactionPosition buildRedactionPosition(Page firstPage, String id, Map.Entry<Page, List<Rectangle2D>> entry) {
if (entry.getKey().equals(firstPage)) {
return new RedactionPosition(id, entry.getKey(), entry.getValue());

View File

@ -3,7 +3,7 @@ package com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.e
import java.awt.geom.Rectangle2D;
import java.util.List;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.PageNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Page;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
@ -16,7 +16,7 @@ import lombok.experimental.FieldDefaults;
public class RedactionPosition {
final String id;
PageNode pageNode;
Page page;
List<Rectangle2D> rectanglePerLine;
}

View File

@ -11,7 +11,7 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.iqser.red.service.redaction.v1.server.exception.NotFoundException;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlockCollector;
@ -28,10 +28,10 @@ import lombok.experimental.FieldDefaults;
@AllArgsConstructor
@NoArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class DocumentGraph implements SemanticNode {
public class Document implements SemanticNode {
Set<PageNode> pages;
TableOfContents tableOfContents;
Set<Page> pages;
DocumentTree documentTree;
Integer numberOfPages;
TextBlock textBlock;
@Builder.Default
@ -44,9 +44,9 @@ public class DocumentGraph implements SemanticNode {
}
public List<SectionNode> getMainSections() {
public List<Section> getMainSections() {
return streamChildren().filter(node -> node instanceof SectionNode).map(node -> (SectionNode) node).collect(Collectors.toList());
return streamChildren().filter(node -> node instanceof Section).map(node -> (Section) node).collect(Collectors.toList());
}
@ -57,24 +57,24 @@ public class DocumentGraph implements SemanticNode {
@Override
public List<Integer> getTocId() {
public List<Integer> getTreeId() {
return Collections.emptyList();
}
@Override
public void setTocId(List<Integer> tocId) {
public void setTreeId(List<Integer> tocId) {
throw new UnsupportedOperationException("DocumentGraph is always the root of the TablePageBlock of Contents");
throw new UnsupportedOperationException("Document is always the root of the TablePageBlock of Contents");
}
@Override
public HeadlineNode getHeadline() {
public Headline getHeadline() {
return streamAllSubNodes().filter(node -> node instanceof HeadlineNode)
.map(node -> (HeadlineNode) node)
return streamAllSubNodes().filter(node -> node instanceof Headline)
.map(node -> (Headline) node)
.findFirst()
.orElseThrow(() -> new NotFoundException("No Headlines found in this document!"));
}
@ -82,13 +82,13 @@ public class DocumentGraph implements SemanticNode {
private Stream<SemanticNode> streamAllNodes() {
return tableOfContents.streamAllEntriesInOrder().map(TableOfContents.Entry::getNode);
return documentTree.streamAllEntriesInOrder().map(DocumentTree.Entry::getNode);
}
public Stream<ImageNode> streamAllImages() {
public Stream<Image> streamAllImages() {
return streamAllSubNodes().filter(node -> node instanceof ImageNode).map(node -> (ImageNode) node);
return streamAllSubNodes().filter(node -> node instanceof Image).map(node -> (Image) node);
}
@ -100,10 +100,10 @@ public class DocumentGraph implements SemanticNode {
@Override
public Map<PageNode, Rectangle2D> getBBox() {
public Map<Page, Rectangle2D> getBBox() {
Map<PageNode, Rectangle2D> bBox = new HashMap<>();
for (PageNode page : pages) {
Map<Page, Rectangle2D> bBox = new HashMap<>();
for (Page page : pages) {
bBox.put(page, new Rectangle2D.Double(0, 0, page.getWidth(), page.getHeight()));
}
return bBox;

View File

@ -4,7 +4,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
@ -21,16 +21,16 @@ import lombok.experimental.FieldDefaults;
@AllArgsConstructor
@NoArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class HeaderNode implements SemanticNode {
public class Footer implements SemanticNode {
List<Integer> tocId;
List<Integer> treeId;
TextBlock terminalTextBlock;
@Builder.Default
boolean terminal = true;
@EqualsAndHashCode.Exclude
TableOfContents tableOfContents;
DocumentTree documentTree;
@Builder.Default
@EqualsAndHashCode.Exclude
@ -47,7 +47,7 @@ public class HeaderNode implements SemanticNode {
@Override
public String toString() {
return tocId + ": " + NodeType.HEADER + ": " + terminalTextBlock.buildSummary();
return treeId + ": " + NodeType.FOOTER + ": " + terminalTextBlock.buildSummary();
}
}

View File

@ -4,7 +4,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
@ -21,16 +21,16 @@ import lombok.experimental.FieldDefaults;
@AllArgsConstructor
@NoArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class FooterNode implements SemanticNode {
public class Header implements SemanticNode {
List<Integer> tocId;
List<Integer> treeId;
TextBlock terminalTextBlock;
@Builder.Default
boolean terminal = true;
@EqualsAndHashCode.Exclude
TableOfContents tableOfContents;
DocumentTree documentTree;
@Builder.Default
@EqualsAndHashCode.Exclude
@ -47,7 +47,7 @@ public class FooterNode implements SemanticNode {
@Override
public String toString() {
return tocId + ": " + NodeType.FOOTER + ": " + terminalTextBlock.buildSummary();
return treeId + ": " + NodeType.HEADER + ": " + terminalTextBlock.buildSummary();
}
}

View File

@ -4,7 +4,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
@ -21,16 +21,16 @@ import lombok.experimental.FieldDefaults;
@AllArgsConstructor
@NoArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class HeadlineNode implements SemanticNode {
public class Headline implements SemanticNode {
List<Integer> tocId;
List<Integer> treeId;
TextBlock terminalTextBlock;
@Builder.Default
boolean terminal = true;
@EqualsAndHashCode.Exclude
TableOfContents tableOfContents;
DocumentTree documentTree;
@Builder.Default
@EqualsAndHashCode.Exclude
@ -47,12 +47,12 @@ public class HeadlineNode implements SemanticNode {
@Override
public String toString() {
return tocId + ": " + NodeType.HEADLINE + ": " + terminalTextBlock.buildSummary();
return treeId + ": " + NodeType.HEADLINE + ": " + terminalTextBlock.buildSummary();
}
@Override
public HeadlineNode getHeadline() {
public Headline getHeadline() {
return this;
}

View File

@ -8,11 +8,10 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlockCollector;
import com.iqser.red.service.redaction.v1.server.redaction.model.ImageType;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
@ -27,9 +26,9 @@ import lombok.experimental.FieldDefaults;
@AllArgsConstructor
@NoArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class ImageNode implements SemanticNode {
public class Image implements SemanticNode {
List<Integer> tocId;
List<Integer> treeId;
String id;
ImageType imageType;
@ -46,10 +45,10 @@ public class ImageNode implements SemanticNode {
int matchedRule = -1;
@EqualsAndHashCode.Exclude
PageNode page;
Page page;
@EqualsAndHashCode.Exclude
TableOfContents tableOfContents;
DocumentTree documentTree;
@Builder.Default
@EqualsAndHashCode.Exclude
@ -64,7 +63,7 @@ public class ImageNode implements SemanticNode {
@Override
public Set<PageNode> getPages() {
public Set<Page> getPages() {
return Collections.singleton(page);
}
@ -73,14 +72,14 @@ public class ImageNode implements SemanticNode {
@Override
public String toString() {
return tocId + ": " + NodeType.IMAGE + ": " + imageType.toString() + " " + position;
return treeId + ": " + NodeType.IMAGE + ": " + imageType.toString() + " " + position;
}
@Override
public Map<PageNode, Rectangle2D> getBBox() {
public Map<Page, Rectangle2D> getBBox() {
Map<PageNode, Rectangle2D> bBoxPerPage = new HashMap<>();
Map<Page, Rectangle2D> bBoxPerPage = new HashMap<>();
bBoxPerPage.put(page, position);
return bBoxPerPage;
}

View File

@ -0,0 +1,9 @@
package com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes;
public enum ImageType {
LOGO,
FORMULA,
SIGNATURE,
OTHER,
OCR
}

View File

@ -21,7 +21,7 @@ import lombok.experimental.FieldDefaults;
@Builder
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class PageNode {
public class Page {
Integer number;
Integer height;
@ -31,9 +31,9 @@ public class PageNode {
@EqualsAndHashCode.Exclude
List<SemanticNode> mainBody;
@EqualsAndHashCode.Exclude
HeaderNode header;
Header header;
@EqualsAndHashCode.Exclude
FooterNode footer;
Footer footer;
@Builder.Default
@EqualsAndHashCode.Exclude
@ -41,7 +41,7 @@ public class PageNode {
@Builder.Default
@EqualsAndHashCode.Exclude
Set<ImageNode> images = new HashSet<>();
Set<Image> images = new HashSet<>();
public TextBlock getMainBodyTextBlock() {
@ -67,7 +67,7 @@ public class PageNode {
@Override
public boolean equals(Object o) {
return o instanceof PageNode && o.hashCode() == this.hashCode();
return o instanceof Page && o.hashCode() == this.hashCode();
}
}

View File

@ -4,7 +4,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
@ -19,16 +19,16 @@ import lombok.experimental.FieldDefaults;
@Builder
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class ParagraphNode implements SemanticNode {
public class Paragraph implements SemanticNode {
List<Integer> tocId;
List<Integer> treeId;
TextBlock terminalTextBlock;
@Builder.Default
boolean terminal = true;
@EqualsAndHashCode.Exclude
TableOfContents tableOfContents;
DocumentTree documentTree;
@Builder.Default
@EqualsAndHashCode.Exclude
@ -45,7 +45,7 @@ public class ParagraphNode implements SemanticNode {
@Override
public String toString() {
return tocId + ": " + NodeType.PARAGRAPH + ": " + terminalTextBlock.buildSummary();
return treeId + ": " + NodeType.PARAGRAPH + ": " + terminalTextBlock.buildSummary();
}
}

View File

@ -4,7 +4,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlockCollector;
@ -22,13 +22,13 @@ import lombok.extern.slf4j.Slf4j;
@Builder
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class SectionNode implements SemanticNode {
public class Section implements SemanticNode {
List<Integer> tocId;
List<Integer> treeId;
TextBlock textBlock;
@EqualsAndHashCode.Exclude
TableOfContents tableOfContents;
DocumentTree documentTree;
boolean excludesTables;
@Builder.Default
@ -49,15 +49,15 @@ public class SectionNode implements SemanticNode {
@Override
public String toString() {
return tocId.toString() + ": " + NodeType.SECTION + ": " + buildTextBlock().buildSummary();
return treeId.toString() + ": " + NodeType.SECTION + ": " + buildTextBlock().buildSummary();
}
public HeadlineNode getHeadline() {
public Headline getHeadline() {
return streamChildren()//
.filter(node -> node instanceof HeadlineNode)//
.map(node -> (HeadlineNode) node)//
.filter(node -> node instanceof Headline)//
.map(node -> (Headline) node)//
.findFirst()//
.orElseGet(() -> getParent().getHeadline());
}

View File

@ -11,7 +11,7 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
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;
@ -20,7 +20,7 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.Re
public interface SemanticNode {
/**
* Searches all Nodes located underneath this Node in the TableOfContents and concatenates their AtomicTextBlocks into a single TextBlockEntity.
* Searches all Nodes located underneath this Node in the DocumentTree and concatenates their AtomicTextBlocks into a single TextBlockEntity.
* So, for a ClassificationSection all TextBlocks of Subsections, Paragraphs, and Tables are concatenated into a single TextBlockEntity
* If the Node is Terminal, the TerminalTextBlock will be returned instead.
*
@ -43,7 +43,7 @@ public interface SemanticNode {
*
* @return Set of PageNodes this node appears on.
*/
default Set<PageNode> getPages() {
default Set<Page> getPages() {
return buildTextBlock().getPages();
}
@ -54,7 +54,7 @@ public interface SemanticNode {
*
* @return Set of PageNodes this node appears on.
*/
default Set<PageNode> getPages(Boundary boundary) {
default Set<Page> getPages(Boundary boundary) {
if (!getBoundary().contains(boundary)) {
throw new IllegalArgumentException(format("%s which was used to query foir pages is not contained in the %s of this node!", getBoundary(), boundary));
@ -64,17 +64,17 @@ public interface SemanticNode {
/**
* @return the TableOfContents of the ClassificationDocument this node belongs to
* @return the DocumentTree of the ClassificationDocument this node belongs to
*/
TableOfContents getTableOfContents();
DocumentTree getDocumentTree();
/**
* The id is a List of Integers uniquely identifying this node in the TableOfContents.
* The id is a List of Integers uniquely identifying this node in the DocumentTree.
*
* @return the TableOfContents ID
* @return the DocumentTree ID
*/
List<Integer> getTocId();
List<Integer> getTreeId();
/**
@ -82,37 +82,39 @@ public interface SemanticNode {
*
* @param tocId List of Integers
*/
void setTocId(List<Integer> tocId);
void setTreeId(List<Integer> tocId);
/**
* Traverses the Tree up, until it hits a HeadlineNode or hits a SectionNode which will then return the first HeadlineNode from its children.
* Traverses the Tree up, until it hits a Headline or hits a Section which will then return the first Headline from its children.
* Throws NotFoundException if no Headline is found this way
*
* @return First HeadlineNode found
* @return First Headline found
*/
default HeadlineNode getHeadline() {
default Headline getHeadline() {
return getParent().getHeadline();
}
/**
* @return boolean indicating wether this Node has a Parent in the TableOfContents
* Checks if its TocId has a length greater than zero.
*
* @return boolean indicating whether this Node has a Parent in the DocumentTree
*/
default boolean hasParent() {
return getTableOfContents().hasParentById(getTocId());
return getDocumentTree().hasParentById(getTreeId());
}
/**
* @return The SemanticNode representing the Parent in the TableOfContents
* @return The SemanticNode representing the Parent in the DocumentTree
* throws NotFoundException, when no parent is present
*/
default SemanticNode getParent() {
return getTableOfContents().getParentEntryById(getTocId()).getNode();
return getDocumentTree().getParentEntryById(getTreeId()).getNode();
}
@ -122,7 +124,7 @@ public interface SemanticNode {
*/
default SemanticNode getHighestParent() {
return getTableOfContents().getHighestParentById(getTocId());
return getDocumentTree().getHighestParentById(getTreeId());
}
@ -273,24 +275,24 @@ public interface SemanticNode {
/**
* Streams all children located directly underneath this node in the TableOfContents.
* Streams all children located directly underneath this node in the DocumentTree.
*
* @return Stream of all children
*/
default Stream<SemanticNode> streamChildren() {
return getTableOfContents().streamChildrenNodes(getTocId());
return getDocumentTree().streamChildrenNodes(getTreeId());
}
/**
* recursively streams all SemanticNodes located underneath this node in the TableOfContents in order.
* recursively streams all SemanticNodes located underneath this node in the DocumentTree in order.
*
* @return Stream of all SubNodes
*/
default Stream<SemanticNode> streamAllSubNodes() {
return getTableOfContents().streamAllSubEntriesInOrder(getTocId()).map(TableOfContents.Entry::getNode);
return getDocumentTree().streamAllSubEntriesInOrder(getTreeId()).map(DocumentTree.Entry::getNode);
}
@ -309,9 +311,9 @@ public interface SemanticNode {
*
* @return Rectangle2D fully encapsulating this Node for each page.
*/
default Map<PageNode, Rectangle2D> getBBox() {
default Map<Page, Rectangle2D> getBBox() {
Map<PageNode, Rectangle2D> bBoxPerPage = new HashMap<>();
Map<Page, Rectangle2D> bBoxPerPage = new HashMap<>();
if (isTerminal()) {
return getBBoxFromTerminalTextBlock(bBoxPerPage);
}
@ -320,13 +322,30 @@ public interface SemanticNode {
}
/**
* Checks whether the Bounding Box of this SemanticNode contains the provided rectangle on the provided page.
*
* @param rectangle2D The rectangle to check if it is contained
* @param pageNumber The Page number on which the rectangle should be checked
* @return boolean
*/
default boolean containsRectangle(Rectangle2D rectangle2D, Integer pageNumber) {
Page helperPage = Page.builder().number(pageNumber).build();
if (!getPages().contains(helperPage)) {
return false;
}
return getBBox().get(helperPage).contains(rectangle2D);
}
/**
* TODO this does not yet work for sections spanning multiple columns.
*
* @param bBoxPerPage initial empty BoundingBox
* @return The union of the BoundingBoxes of all children
*/
private Map<PageNode, Rectangle2D> getBBoxFromChildren(Map<PageNode, Rectangle2D> bBoxPerPage) {
private Map<Page, Rectangle2D> getBBoxFromChildren(Map<Page, Rectangle2D> bBoxPerPage) {
return streamChildren().map(SemanticNode::getBBox).reduce((map1, map2) -> {
map1.forEach((page, rectangle) -> map2.merge(page, rectangle, (rect1, rect2) -> rect1.createUnion(rect2).getBounds2D()));
@ -339,9 +358,9 @@ public interface SemanticNode {
* @param bBoxPerPage initial empty BoundingBox
* @return The union of all BoundingBoxes of the ClassificationTextBlock of this node
*/
private Map<PageNode, Rectangle2D> getBBoxFromTerminalTextBlock(Map<PageNode, Rectangle2D> bBoxPerPage) {
private Map<Page, Rectangle2D> getBBoxFromTerminalTextBlock(Map<Page, Rectangle2D> bBoxPerPage) {
Map<PageNode, List<AtomicTextBlock>> atomicTextBlockPerPage = buildTextBlock().getAtomicTextBlocks().stream().collect(Collectors.groupingBy(AtomicTextBlock::getPage));
Map<Page, List<AtomicTextBlock>> atomicTextBlockPerPage = buildTextBlock().getAtomicTextBlocks().stream().collect(Collectors.groupingBy(AtomicTextBlock::getPage));
atomicTextBlockPerPage.forEach((page, atbs) -> bBoxPerPage.put(page, RectangleTransformations.bBoxUnionAtomicTextBlock(atbs)));
return bBoxPerPage;
}

View File

@ -9,7 +9,7 @@ import java.util.Set;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlockCollector;
@ -25,10 +25,10 @@ import lombok.experimental.FieldDefaults;
@Builder
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class TableNode implements SemanticNode {
public class Table implements SemanticNode {
List<Integer> tocId;
TableOfContents tableOfContents;
List<Integer> treeId;
DocumentTree documentTree;
int numberOfRows;
int numberOfCols;
@ -46,34 +46,32 @@ public class TableNode implements SemanticNode {
.boxed()
.filter(row -> rowContainsStringsIgnoreCase(row, strings))
.flatMap(this::streamRow)
.map(TableCellNode::getEntities)
.map(TableCell::getEntities)
.flatMap(Collection::stream);
}
public boolean rowContainsStringsIgnoreCase(Integer row, List<String> strings) {
String rowText = streamRow(row).map(TableCellNode::buildTextBlock).collect(new TextBlockCollector()).getSearchText().toLowerCase();
String rowText = streamRow(row).map(TableCell::buildTextBlock).collect(new TextBlockCollector()).getSearchText().toLowerCase();
return strings.stream().map(String::toLowerCase).allMatch(rowText::contains);
}
public Stream<RedactionEntity> streamEntitiesWhereRowHasHeaderAndValue(String header, String value) {
List<Integer> vertebrateStudyCols = streamHeaders().filter(headerNode -> headerNode.containsString(header)).map(TableCellNode::getCol).toList();
List<Integer> vertebrateStudyCols = streamHeaders().filter(headerNode -> headerNode.containsString(header)).map(TableCell::getCol).toList();
return streamTableCells().filter(tableCellNode -> vertebrateStudyCols.stream()
.anyMatch(vertebrateStudyCol -> getCell(tableCellNode.getRow(), vertebrateStudyCol).containsString(value)))
.map(TableCellNode::getEntities)
.flatMap(Collection::stream);
.anyMatch(vertebrateStudyCol -> getCell(tableCellNode.getRow(), vertebrateStudyCol).containsString(value))).map(TableCell::getEntities).flatMap(Collection::stream);
}
public Stream<RedactionEntity> streamEntitiesWhereRowHasHeaderAndAnyValue(String header, List<String> values) {
List<Integer> vertebrateStudyCols = streamHeaders().filter(headerNode -> headerNode.containsString(header)).map(TableCellNode::getCol).toList();
List<Integer> vertebrateStudyCols = streamHeaders().filter(headerNode -> headerNode.containsString(header)).map(TableCell::getCol).toList();
return streamTableCells().filter(tableCellNode -> vertebrateStudyCols.stream()
.anyMatch(vertebrateStudyCol -> getCell(tableCellNode.getRow(), vertebrateStudyCol).containsAnyString(values)))
.map(TableCellNode::getEntities)
.map(TableCell::getEntities)
.flatMap(Collection::stream);
}
@ -83,61 +81,61 @@ public class TableNode implements SemanticNode {
List<Integer> rowsWithEntityOfType = getEntities().stream()
.filter(redactionEntity -> types.stream().anyMatch(type -> type.equals(redactionEntity.getType())))
.map(RedactionEntity::getIntersectingNodes)
.filter(node -> node instanceof TableCellNode)
.map(node -> (TableCellNode) node)
.map(TableCellNode::getRow)
.filter(node -> node instanceof TableCell)
.map(node -> (TableCell) node)
.map(TableCell::getRow)
.toList();
return rowsWithEntityOfType.stream().flatMap(this::streamRow).map(TableCellNode::getEntities).flatMap(Collection::stream);
return rowsWithEntityOfType.stream().flatMap(this::streamRow).map(TableCell::getEntities).flatMap(Collection::stream);
}
public TableCellNode getCell(int row, int col) {
public TableCell getCell(int row, int col) {
if (numberOfRows - row < 0 || numberOfCols - col < 0) {
throw new IllegalArgumentException(format("row %d, col %d is out of bounds for number of rows of %d and number of cols %d", row, col, numberOfRows, numberOfCols));
}
int idx = row * numberOfCols + col;
return (TableCellNode) tableOfContents.getEntryById(tocId).getChildren().get(idx).getNode();
return (TableCell) documentTree.getEntryById(treeId).getChildren().get(idx).getNode();
}
public Stream<TableCellNode> streamTableCells() {
public Stream<TableCell> streamTableCells() {
return streamChildren().map(node -> (TableCellNode) node);
return streamChildren().map(node -> (TableCell) node);
}
public Stream<TableCellNode> streamTableCellsWithHeader(String header) {
public Stream<TableCell> streamTableCellsWithHeader(String header) {
return streamHeaders().filter(tableCellNode -> tableCellNode.buildTextBlock().getSearchText().contains(header))
.map(TableCellNode::getCol)
.map(TableCell::getCol)
.flatMap(this::streamCol)
.filter(tableCellNode -> !tableCellNode.isHeader());
}
public Stream<TableCellNode> streamCol(int col) {
public Stream<TableCell> streamCol(int col) {
return IntStream.range(0, numberOfRows).boxed().map(row -> getCell(row, col));
}
public Stream<TableCellNode> streamRow(int row) {
public Stream<TableCell> streamRow(int row) {
return IntStream.range(0, numberOfCols).boxed().map(col -> getCell(row, col));
}
public Stream<TableCellNode> streamHeaders() {
public Stream<TableCell> streamHeaders() {
return streamTableCells().filter(TableCellNode::isHeader);
return streamTableCells().filter(TableCell::isHeader);
}
public Stream<TableCellNode> streamHeadersForCell(int row, int col) {
public Stream<TableCell> streamHeadersForCell(int row, int col) {
return Stream.concat(streamRow(row), streamCol(col)).filter(TableCellNode::isHeader);
return Stream.concat(streamRow(row), streamCol(col)).filter(TableCell::isHeader);
}
@ -163,8 +161,8 @@ public class TableNode implements SemanticNode {
return redactionEntity.getIntersectingNodes()
.stream()
.filter(node -> node instanceof TableCellNode)
.map(node -> (TableCellNode) node)
.filter(node -> node instanceof TableCell)
.map(node -> (TableCell) node)
.flatMap(tableCellNode -> streamRow(tableCellNode.getRow()))
.map(cell -> cell.getEntitiesOfType(type))
.flatMap(Collection::stream)
@ -185,7 +183,7 @@ public class TableNode implements SemanticNode {
@Override
public String toString() {
return tocId.toString() + ": " + NodeType.TABLE + ": #cols: " + numberOfCols + ", #rows: " + numberOfRows + ", " + buildTextBlock().buildSummary();
return treeId.toString() + ": " + NodeType.TABLE + ": #cols: " + numberOfCols + ", #rows: " + numberOfRows + ", " + buildTextBlock().buildSummary();
}
}

View File

@ -8,7 +8,7 @@ import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlockCollector;
@ -24,9 +24,9 @@ import lombok.experimental.FieldDefaults;
@Builder
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class TableCellNode implements SemanticNode {
public class TableCell implements SemanticNode {
List<Integer> tocId;
List<Integer> treeId;
int row;
int col;
boolean header;
@ -40,7 +40,7 @@ public class TableCellNode implements SemanticNode {
TextBlock textBlock;
@EqualsAndHashCode.Exclude
TableOfContents tableOfContents;
DocumentTree documentTree;
@Builder.Default
@EqualsAndHashCode.Exclude
@ -48,9 +48,9 @@ public class TableCellNode implements SemanticNode {
@Override
public Map<PageNode, Rectangle2D> getBBox() {
public Map<Page, Rectangle2D> getBBox() {
Map<PageNode, Rectangle2D> bBoxPerPage = new HashMap<>();
Map<Page, Rectangle2D> bBoxPerPage = new HashMap<>();
getPages().forEach(page -> bBoxPerPage.put(page, bBox));
return bBoxPerPage;
}
@ -73,7 +73,7 @@ public class TableCellNode implements SemanticNode {
@Override
public String toString() {
return tocId + ": " + NodeType.TABLE_CELL + ": " + buildTextBlock().buildSummary();
return treeId + ": " + NodeType.TABLE_CELL + ": " + buildTextBlock().buildSummary();
}
@ -83,10 +83,10 @@ public class TableCellNode implements SemanticNode {
}
private Stream<TableCellNode> getHeaders() {
private Stream<TableCell> getHeaders() {
TableNode tableNode = (TableNode) getParent();
return tableNode.streamHeadersForCell(row, col);
Table table = (Table) getParent();
return table.streamHeadersForCell(row, col);
}
}

View File

@ -9,7 +9,7 @@ import java.util.List;
import java.util.Map;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.PageNode;
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.utils.RectangleTransformations;
@ -28,7 +28,7 @@ public class AtomicTextBlock implements TextBlock {
Long id;
Integer numberOnPage;
PageNode page;
Page page;
//string coordinates
Boundary boundary;
@ -119,14 +119,14 @@ public class AtomicTextBlock implements TextBlock {
}
public Map<PageNode, List<Rectangle2D>> getEntityPositionsPerPage(Boundary stringBoundary) {
public Map<Page, List<Rectangle2D>> getEntityPositionsPerPage(Boundary stringBoundary) {
List<Rectangle2D> rectanglesPerLine = stringBoundary.split(getLineBreaks().stream().map(lb -> lb + boundary.start()).filter(stringBoundary::contains).toList())
.stream()
.map(this::getPositions)
.map(RectangleTransformations::rectangleUnion)
.toList();
Map<PageNode, List<Rectangle2D>> rectanglePerLinePerPage = new HashMap<>();
Map<Page, List<Rectangle2D>> rectanglePerLinePerPage = new HashMap<>();
rectanglePerLinePerPage.put(page, rectanglesPerLine);
return rectanglePerLinePerPage;
}

View File

@ -10,7 +10,7 @@ import java.util.Map;
import java.util.stream.Stream;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.PageNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Page;
import lombok.AccessLevel;
import lombok.Data;
@ -137,7 +137,7 @@ public class ConcatenatedTextBlock implements TextBlock {
@Override
public Map<PageNode, List<Rectangle2D>> getEntityPositionsPerPage(Boundary stringBoundary) {
public Map<Page, List<Rectangle2D>> getEntityPositionsPerPage(Boundary stringBoundary) {
List<AtomicTextBlock> textBlocks = getAllAtomicTextBlocksPartiallyInStringBoundary(stringBoundary);
@ -146,8 +146,7 @@ public class ConcatenatedTextBlock implements TextBlock {
}
AtomicTextBlock firstTextBlock = textBlocks.get(0);
Map<PageNode, List<Rectangle2D>> rectanglesPerLinePerPage = firstTextBlock.getEntityPositionsPerPage(new Boundary(stringBoundary.start(),
firstTextBlock.getBoundary().end()));
Map<Page, List<Rectangle2D>> rectanglesPerLinePerPage = firstTextBlock.getEntityPositionsPerPage(new Boundary(stringBoundary.start(), firstTextBlock.getBoundary().end()));
for (AtomicTextBlock textBlock : textBlocks.subList(1, textBlocks.size() - 1)) {
rectanglesPerLinePerPage = mergeEntityPositionsWithSamePageNode(rectanglesPerLinePerPage, textBlock.getEntityPositionsPerPage(textBlock.getBoundary()));
@ -160,9 +159,9 @@ public class ConcatenatedTextBlock implements TextBlock {
}
private Map<PageNode, List<Rectangle2D>> mergeEntityPositionsWithSamePageNode(Map<PageNode, List<Rectangle2D>> map1, Map<PageNode, List<Rectangle2D>> map2) {
private Map<Page, List<Rectangle2D>> mergeEntityPositionsWithSamePageNode(Map<Page, List<Rectangle2D>> map1, Map<Page, List<Rectangle2D>> map2) {
Map<PageNode, List<Rectangle2D>> mergedMap = new HashMap<>(map1);
Map<Page, List<Rectangle2D>> mergedMap = new HashMap<>(map1);
map2.forEach((pageNode, rectangles) -> mergedMap.merge(pageNode, rectangles, (l1, l2) -> Stream.concat(l1.stream(), l2.stream()).toList()));
return mergedMap;
}

View File

@ -11,7 +11,7 @@ import java.util.Set;
import java.util.stream.Collectors;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.PageNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Page;
public interface TextBlock extends CharSequence {
@ -39,7 +39,7 @@ public interface TextBlock extends CharSequence {
List<Rectangle2D> getPositions(Boundary stringBoundary);
Map<PageNode, List<Rectangle2D>> getEntityPositionsPerPage(Boundary stringBoundary);
Map<Page, List<Rectangle2D>> getEntityPositionsPerPage(Boundary stringBoundary);
int numberOfLines();
@ -51,13 +51,13 @@ public interface TextBlock extends CharSequence {
}
default Set<PageNode> getPages() {
default Set<Page> getPages() {
return getAtomicTextBlocks().stream().map(AtomicTextBlock::getPage).collect(Collectors.toUnmodifiableSet());
}
default Set<PageNode> getPages(Boundary boundary) {
default Set<Page> getPages(Boundary boundary) {
return getAtomicTextBlocks().stream()
.filter(atomicTextBlock -> atomicTextBlock.getBoundary().intersects(boundary))

View File

@ -20,16 +20,16 @@ import org.springframework.stereotype.Service;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Engine;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.EntityType;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
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.PageNode;
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.TableCellNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.TableCell;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RedactionSearchUtility;
import com.iqser.red.service.redaction.v1.server.redaction.adapter.NerEntities;
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityType;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.SearchImplementation;
import com.iqser.red.service.redaction.v1.server.redaction.utils.IdBuilder;
@ -207,19 +207,17 @@ public class EntityCreationService {
}
public RedactionEntity byTableCellAsHighlight(TableCellNode tableCellNode, String type, EntityType entityType) {
public RedactionEntity byTableCellAsHighlight(TableCell tableCell, String type, EntityType entityType) {
RedactionEntity highlightEntity = RedactionEntity.initialEntityNode(new Boundary(tableCellNode.getBoundary().start(), tableCellNode.getBoundary().start()),
type,
entityType);
RedactionEntity highlightEntity = RedactionEntity.initialEntityNode(new Boundary(tableCell.getBoundary().start(), tableCell.getBoundary().start()), type, entityType);
String positionId = IdBuilder.buildId(tableCellNode.getBBox().keySet(), tableCellNode.getBBox().values().stream().toList());
highlightEntity.setRedactionPositionsPerPage(tableCellNode.getBBox()
String positionId = IdBuilder.buildId(tableCell.getBBox().keySet(), tableCell.getBBox().values().stream().toList());
highlightEntity.setRedactionPositionsPerPage(tableCell.getBBox()
.entrySet()
.stream()
.map(entry -> new RedactionPosition(positionId, entry.getKey(), List.of(entry.getValue())))
.toList());
addEntityToGraph(highlightEntity, tableCellNode);
addEntityToGraph(highlightEntity, tableCell);
return highlightEntity;
}
@ -232,24 +230,24 @@ public class EntityCreationService {
public void addEntityToGraph(RedactionEntity entity, SemanticNode node) {
TableOfContents tableOfContents = node.getTableOfContents();
DocumentTree documentTree = node.getDocumentTree();
try {
addEntityToGraph(entity, tableOfContents);
entity.addIntersectingNode(tableOfContents.getRoot().getNode());
tableOfContents.getRoot().getNode().getEntities().add(entity);
addEntityToGraph(entity, documentTree);
entity.addIntersectingNode(documentTree.getRoot().getNode());
documentTree.getRoot().getNode().getEntities().add(entity);
} catch (NoSuchElementException e) {
entity.setDeepestFullyContainingNode(tableOfContents.getRoot().getNode());
entity.setDeepestFullyContainingNode(documentTree.getRoot().getNode());
entityEnrichmentService.enrichEntity(entity, entity.getDeepestFullyContainingNode().buildTextBlock());
entity.addIntersectingNode(tableOfContents.getRoot().getNode());
entity.addIntersectingNode(documentTree.getRoot().getNode());
addToPages(entity);
addEntityToNodeEntitySets(entity);
}
}
public void addEntityToGraph(RedactionEntity entity, TableOfContents tableOfContents) {
public void addEntityToGraph(RedactionEntity entity, DocumentTree documentTree) {
SemanticNode containingNode = tableOfContents.streamChildrenNodes(Collections.emptyList())
SemanticNode containingNode = documentTree.streamChildrenNodes(Collections.emptyList())
.filter(node -> node.buildTextBlock().containsBoundary(entity.getBoundary()))
.findFirst()
.orElseThrow(() -> new NoSuchElementException("No containing Node found!"));
@ -267,7 +265,7 @@ public class EntityCreationService {
private static void addToPages(RedactionEntity entity) {
Set<PageNode> pages = entity.getDeepestFullyContainingNode().getPages(entity.getBoundary());
Set<Page> pages = entity.getDeepestFullyContainingNode().getPages(entity.getBoundary());
entity.getPages().addAll(pages);
pages.forEach(page -> page.getEntities().add(entity));
}

View File

@ -36,7 +36,7 @@ public class ManualRedactionApplicationService {
newStartOffset = entityToBeResized.getBoundary().start() + entityToBeResized.getValue().indexOf(manualResizeRedaction.getValue());
}
SemanticNode nodeToInsertInto = entityToBeResized.getDeepestFullyContainingNode().getTableOfContents().getRoot().getNode();
SemanticNode nodeToInsertInto = entityToBeResized.getDeepestFullyContainingNode().getDocumentTree().getRoot().getNode();
entityToBeResized.removeFromGraph();
entityToBeResized.setResized(true);
entityToBeResized.setRemoved(false);

View File

@ -6,12 +6,9 @@ import java.awt.geom.Rectangle2D;
import java.util.Arrays;
import java.util.List;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
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.Rectangle;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.AbstractPageBlock;
import com.iqser.red.service.redaction.v1.server.redaction.model.RedRectangle2D;
public class RectangleMapper {
@ -21,12 +18,6 @@ public class RectangleMapper {
}
public static Rectangle2D toRectangle2D(RedRectangle2D redRectangle2D) {
return new Rectangle2D.Double(redRectangle2D.getX(), redRectangle2D.getY(), redRectangle2D.getWidth(), redRectangle2D.getHeight());
}
public static Rectangle2D toRectangle2D(Rectangle redactionLogRectangle) {
return new Rectangle2D.Double(redactionLogRectangle.getTopLeft().getX(),
@ -36,12 +27,6 @@ public class RectangleMapper {
}
public static Rectangle2D toRectangle2D(PDRectangle rectangle) {
return new Rectangle2D.Double(rectangle.getLowerLeftX(), rectangle.getLowerLeftY(), rectangle.getWidth(), rectangle.getHeight());
}
public static Rectangle toRedactionLogRectangle(Rectangle2D rectangle2D, int pageNumber) {
return new Rectangle(new Point((float) rectangle2D.getMinX(), (float) rectangle2D.getMinY()), (float) rectangle2D.getWidth(), (float) rectangle2D.getHeight(), pageNumber);

View File

@ -1,18 +1,17 @@
package com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils;
import static java.lang.String.format;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
import com.iqser.red.service.redaction.v1.server.redaction.utils.Patterns;
import lombok.experimental.UtilityClass;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
import com.iqser.red.service.redaction.v1.server.redaction.utils.Patterns;
import lombok.experimental.UtilityClass;
import static java.lang.String.format;
@UtilityClass
public class RedactionSearchUtility {
@ -72,7 +71,7 @@ public class RedactionSearchUtility {
}
return expandedStart;
}
public static List<Boundary> findBoundariesByRegex(String regexPattern, TextBlock textBlock) {

View File

@ -14,8 +14,8 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.AnalyzeRequ
import com.iqser.red.service.persistence.service.v1.api.shared.model.AnalyzeResult;
import com.iqser.red.service.redaction.v1.model.StructureAnalyzeRequest;
import com.iqser.red.service.redaction.v1.server.client.FileStatusProcessingUpdateClient;
import com.iqser.red.service.redaction.v1.server.redaction.service.AnalyzeService;
import com.iqser.red.service.redaction.v1.server.redaction.service.ManualRedactionSurroundingTextService;
import com.iqser.red.service.redaction.v1.server.redaction.service.analyze.AnalyzeService;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;

View File

@ -12,8 +12,8 @@ import org.springframework.stereotype.Service;
import com.iqser.red.service.redaction.v1.server.client.model.EntityRecognitionEntity;
import com.iqser.red.service.redaction.v1.server.client.model.NerEntitiesModel;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SectionNode;
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.Section;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
import lombok.AccessLevel;
@ -38,13 +38,13 @@ public class NerEntitiesAdapter {
* Adds the appropriate Offsets to the entities from the NER Service.
*
* @param nerEntitiesModel the Entities just as the NER Service returns them
* @param documentGraph the document structure, from which the NER Service found the entities
* @param document the document structure, from which the NER Service found the entities
* @return a stream of validated entities
*/
public NerEntities toNerEntities(NerEntitiesModel nerEntitiesModel, DocumentGraph documentGraph) {
public NerEntities toNerEntities(NerEntitiesModel nerEntitiesModel, Document document) {
return new NerEntities(addOffsetsAndFlatten(getStringStartOffsetsForMainSections(documentGraph), nerEntitiesModel).map(nerEntityModel -> new NerEntities.NerEntity(
nerEntityModel.getValue(),
return new NerEntities(addOffsetsAndFlatten(getStringStartOffsetsForMainSections(document),
nerEntitiesModel).map(nerEntityModel -> new NerEntities.NerEntity(nerEntityModel.getValue(),
new Boundary(nerEntityModel.getStartOffset(), nerEntityModel.getEndOffset()),
nerEntityModel.getType())).toList());
}
@ -161,9 +161,9 @@ public class NerEntitiesAdapter {
}
private static List<Integer> getStringStartOffsetsForMainSections(DocumentGraph documentGraph) {
private static List<Integer> getStringStartOffsetsForMainSections(Document document) {
return documentGraph.getMainSections().stream().map(SectionNode::buildTextBlock).map(TextBlock::getBoundary).map(Boundary::start).toList();
return document.getMainSections().stream().map(Section::buildTextBlock).map(TextBlock::getBoundary).map(Boundary::start).toList();
}
}

View File

@ -1,53 +0,0 @@
package com.iqser.red.service.redaction.v1.server.redaction.model;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextPositionSequence;
import com.iqser.red.service.redaction.v1.server.redaction.utils.TextNormalizationUtilities;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CellValue {
private List<ClassificationTextBlock> textBlocks = new ArrayList<>();
private int rowSpanStart;
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
Iterator<ClassificationTextBlock> itty = textBlocks.iterator();
while (itty.hasNext()) {
ClassificationTextBlock textBlock = itty.next();
TextPositionSequence previous = null;
for (TextPositionSequence word : textBlock.getSequences()) {
if (previous != null) {
if (Math.abs(previous.getMaxYDirAdj() - word.getMaxYDirAdj()) > word.getTextHeight()) {
sb.append('\n');
} else {
sb.append(' ');
}
}
sb.append(word.toString());
previous = word;
}
if (itty.hasNext()) {
sb.append(' ');
}
}
return TextNormalizationUtilities.removeHyphenLineBreaks(sb.toString()).replaceAll("\n", " ").replaceAll(" {2}", " ");
}
}

View File

@ -1,23 +0,0 @@
package com.iqser.red.service.redaction.v1.server.redaction.model;
import java.util.HashSet;
import java.util.Set;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Entities {
@Builder.Default
private Set<Entity> entities = new HashSet<>();
@Builder.Default
private Set<Entity> nerEntities = new HashSet<>();
}

View File

@ -1,128 +0,0 @@
package com.iqser.red.service.redaction.v1.server.redaction.model;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Engine;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextPositionSequence;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Entity implements ReasonHolder {
private String word;
private String type;
private boolean redaction;
private boolean falsePositive;
private String redactionReason;
private String legalBasis;
private List<EntityPositionSequence> positionSequences = new ArrayList<>();
private List<TextPositionSequence> targetSequences;
@EqualsAndHashCode.Include
private Integer start;
@EqualsAndHashCode.Include
private Integer end;
@EqualsAndHashCode.Include
private String headline;
private int matchedRule;
@EqualsAndHashCode.Include
private int sectionNumber;
private boolean isDictionaryEntry;
private String textBefore;
private String textAfter;
private boolean isDossierDictionaryEntry;
private boolean ignored;
private Set<Engine> engines = new HashSet<>();
private Set<Entity> references = new HashSet<>();
private EntityType entityType;
private boolean resized;
private boolean skipRemoveEntitiesContainedInLarger;
public Entity(String word,
String type,
boolean redaction,
String redactionReason,
List<EntityPositionSequence> positionSequences,
String headline,
int matchedRule,
int sectionNumber,
String legalBasis,
boolean isDictionaryEntry,
String textBefore,
String textAfter,
Integer start,
Integer end,
boolean isDossierDictionaryEntry,
Set<Engine> engines,
Set<Entity> references,
EntityType entityType) {
this.word = word;
this.type = type;
this.redaction = redaction;
this.redactionReason = redactionReason;
this.positionSequences = positionSequences;
this.headline = headline;
this.matchedRule = matchedRule;
this.sectionNumber = sectionNumber;
this.legalBasis = legalBasis;
this.isDictionaryEntry = isDictionaryEntry;
this.textBefore = textBefore;
this.textAfter = textAfter;
this.start = start;
this.end = end;
this.isDossierDictionaryEntry = isDossierDictionaryEntry;
this.engines = engines;
this.references = references;
this.entityType = entityType;
}
public Entity(String word,
String type,
Integer start,
Integer end,
String headline,
int sectionNumber,
boolean isDictionaryEntry,
boolean isDossierDictionaryEntry,
Engine engine,
EntityType entityType) {
this.word = word;
this.type = type;
this.start = start;
this.end = end;
this.headline = headline;
this.sectionNumber = sectionNumber;
this.isDictionaryEntry = isDictionaryEntry;
this.isDossierDictionaryEntry = isDossierDictionaryEntry;
this.engines.add(engine);
this.entityType = entityType;
}
}

View File

@ -1,26 +0,0 @@
package com.iqser.red.service.redaction.v1.server.redaction.model;
import java.util.ArrayList;
import java.util.List;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextPositionSequence;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
@Data
@Builder
@RequiredArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class EntityPositionSequence {
private final String id;
@EqualsAndHashCode.Exclude
private List<TextPositionSequence> sequences = new ArrayList<>();
private int pageNumber;
}

View File

@ -1,20 +0,0 @@
package com.iqser.red.service.redaction.v1.server.redaction.model;
import java.util.Set;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileAttribute;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FindEntitiesResult {
private Set<Entity> entities;
private Set<FileAttribute> addedFileAttributes;
}

View File

@ -1,27 +0,0 @@
package com.iqser.red.service.redaction.v1.server.redaction.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Image implements ReasonHolder {
private String type;
private RedRectangle2D position;
private boolean redaction;
private String redactionReason;
private String legalBasis;
private int matchedRule;
private int sectionNumber;
private String section;
private int page;
private boolean ignored;
private boolean hasTransparency;
}

View File

@ -1,9 +0,0 @@
package com.iqser.red.service.redaction.v1.server.redaction.model;
public enum ImageType {
LOGO,
FORMULA,
SIGNATURE,
OTHER,
OCR
}

View File

@ -1,27 +0,0 @@
package com.iqser.red.service.redaction.v1.server.redaction.model;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class OffsetString {
private String value;
private int start;
private int end;
public OffsetString trim() {
String trimmed = this.value.trim();
int indexInUntrimmed = this.value.indexOf(trimmed);
int newStart = this.start + indexInUntrimmed;
int newEnd = newStart + trimmed.length();
return new OffsetString(trimmed, newStart, newEnd);
}
}

View File

@ -1,28 +0,0 @@
package com.iqser.red.service.redaction.v1.server.redaction.model;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileAttribute;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@Data
@AllArgsConstructor
public class PageEntities {
@Builder.Default
private Map<Integer, List<Entity>> entitiesPerPage = new HashMap<>();
@Builder.Default
private Map<Integer, Set<Image>> imagesPerPage = new HashMap<>();
@Builder.Default
private Set<FileAttribute> addedFileAttributes = new HashSet<>();
}

View File

@ -1,16 +0,0 @@
package com.iqser.red.service.redaction.v1.server.redaction.model;
public interface ReasonHolder {
String getRedactionReason();
void setRedactionReason(String reason);
boolean isRedaction();
void setRedaction(boolean value);
}

View File

@ -1,48 +0,0 @@
package com.iqser.red.service.redaction.v1.server.redaction.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RedRectangle2D {
public static final double THRESHOLD = 0.01;
private double x;
private double y;
private double width;
private double height;
@JsonIgnore
public boolean isEmpty() {
return width <= 0.0f || height <= 0.0f;
}
public boolean contains(double x, double y, double w, double h) {
if (isEmpty() || w <= 0 || h <= 0) {
return false;
}
double x0 = getX();
double y0 = getY();
return round(x) >= round(x0) && round(y) >= round(y0) && (x + w) - (x0 + getWidth()) <= THRESHOLD && (y + h) - (y0 + getHeight()) <= THRESHOLD;
}
private double round(double value) {
double d = Math.pow(10, 2);
return Math.round(value * d) / d;
}
}

View File

@ -1,252 +0,0 @@
package com.iqser.red.service.redaction.v1.server.redaction.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextPositionSequence;
import com.iqser.red.service.redaction.v1.server.redaction.utils.IdBuilder;
import com.iqser.red.service.redaction.v1.server.redaction.utils.SeparatorUtils;
import com.iqser.red.service.redaction.v1.server.redaction.utils.TextNormalizationUtilities;
import lombok.Getter;
public class SearchableText {
@JsonIgnore
private transient String stringRepresentation;
@Getter
private final List<TextPositionSequence> sequences = new ArrayList<>();
public void add(TextPositionSequence textPositionSequence) {
sequences.add(textPositionSequence);
}
public void addAll(List<TextPositionSequence> textPositionSequences) {
sequences.addAll(textPositionSequences);
}
public List<EntityPositionSequence> getSequences(String searchString, boolean caseInsensitive) {
return getSequences(searchString, caseInsensitive, null);
}
@SuppressWarnings("checkstyle:ModifiedControlVariable")
public List<EntityPositionSequence> getSequences(String searchString, boolean caseInsensitive, List<TextPositionSequence> sequencesSubList) {
String normalizedSearchString;
if (caseInsensitive) {
normalizedSearchString = searchString.toLowerCase();
} else {
normalizedSearchString = searchString;
}
char[] searchChars = normalizedSearchString.replaceAll("\\n", " ").toCharArray();
int counter = 0;
List<TextPositionSequence> crossSequenceParts = new ArrayList<>();
List<EntityPositionSequence> finalMatches = new ArrayList<>();
List<TextPositionSequence> searchSpace;
if (sequencesSubList != null) {
int subListIndex = Collections.indexOfSubList(sequences, sequencesSubList);
if (subListIndex != -1) {
searchSpace = sequences.subList(subListIndex, subListIndex + sequencesSubList.size());
} else {
searchSpace = sequences;
}
} else {
searchSpace = sequences;
}
for (int i = 0; i < searchSpace.size(); i++) {
TextPositionSequence partMatch = new TextPositionSequence(searchSpace.get(i).getPage());
for (int j = 0; j < searchSpace.get(i).length(); j++) {
if (i > 0 && j == 0 && searchSpace.get(i).charAt(0, caseInsensitive) == ' ' && searchSpace.get(i - 1)
.charAt(searchSpace.get(i - 1).length() - 1, caseInsensitive) == ' ' || j > 0 && searchSpace.get(i).charAt(j, caseInsensitive) == ' ' && searchSpace.get(i)
.charAt(j - 1, caseInsensitive) == ' ') {
if (j == searchSpace.get(i).length() - 1 && counter != 0 && !partMatch.getTextPositions().isEmpty()) {
crossSequenceParts.add(partMatch);
}
continue;
}
if (j == 0 && searchSpace.get(i).charAt(j, caseInsensitive) != ' ' && i != 0 && searchSpace.get(i - 1)
.charAt(searchSpace.get(i - 1).length() - 1, caseInsensitive) != ' ' && searchChars[counter] == ' ') {
counter++;
}
if (searchSpace.get(i).charAt(j, caseInsensitive) == searchChars[counter] || counter != 0 && searchSpace.get(i).charAt(j, caseInsensitive) == '-') {
if (counter != 0 || i == 0 && j == 0 || j != 0 && (SeparatorUtils.isSeparator(searchSpace.get(i).charAt(j - 1, caseInsensitive)) || SeparatorUtils.isSeparator(
searchSpace.get(i).charAt(j, caseInsensitive))) || j == 0 && i != 0 && SeparatorUtils.isSeparator(searchSpace.get(i - 1)
.charAt(searchSpace.get(i - 1).length() - 1, caseInsensitive)) || j == 0 && i != 0 && searchSpace.get(i - 1)
.charAt(searchSpace.get(i - 1).length() - 1, caseInsensitive) != ' ' && searchSpace.get(i).charAt(j, caseInsensitive) != ' ') {
partMatch.add(searchSpace.get(i), searchSpace.get(i).textPositionAt(j));
if (!(j == searchSpace.get(i).length() - 1 && searchSpace.get(i).charAt(j, caseInsensitive) == '-' && searchChars[counter] != '-')) {
counter++;
}
}
if (counter == searchString.length()) {
crossSequenceParts.add(partMatch);
if (i == searchSpace.size() - 1 && j == searchSpace.get(i).length() - 1 || j != searchSpace.get(i)
.length() - 1 && (SeparatorUtils.isSeparator(searchSpace.get(i).charAt(j + 1, caseInsensitive)) || SeparatorUtils.isSeparator(searchSpace.get(i)
.charAt(j, caseInsensitive))) || j == searchSpace.get(i).length() - 1 && SeparatorUtils.isSeparator(searchSpace.get(i + 1)
.charAt(0, caseInsensitive)) || j == searchSpace.get(i).length() - 1 && searchSpace.get(i)
.charAt(j, caseInsensitive) != ' ' && searchSpace.get(i + 1).charAt(0, caseInsensitive) != ' ') {
finalMatches.addAll(buildEntityPositionSequence(crossSequenceParts, normalizedSearchString));
}
counter = 0;
crossSequenceParts = new ArrayList<>();
partMatch = new TextPositionSequence(searchSpace.get(i).getPage());
}
} else {
counter = 0;
if (!crossSequenceParts.isEmpty()) {
j = j - partMatch.length() - 1;
}
crossSequenceParts = new ArrayList<>();
partMatch = new TextPositionSequence(searchSpace.get(i).getPage());
}
if (j == searchSpace.get(i).length() - 1 && counter != 0) {
crossSequenceParts.add(partMatch);
}
}
}
return finalMatches;
}
private List<EntityPositionSequence> buildEntityPositionSequence(List<TextPositionSequence> crossSequenceParts, String searchString) {
List<EntityPositionSequence> result = new ArrayList<>();
String asString = buildString(crossSequenceParts);
if (!asString.equalsIgnoreCase(searchString)) {
return result;
}
String plainId = IdBuilder.buildId(crossSequenceParts);
String id = plainId;
int currentPage = -1;
int idDiffentPageSuffix = 1;
EntityPositionSequence entityPositionSequence = new EntityPositionSequence(id);
for (TextPositionSequence textPositionSequence : crossSequenceParts) {
if (currentPage == -1) {
currentPage = textPositionSequence.getPage();
entityPositionSequence.setPageNumber(currentPage);
entityPositionSequence.getSequences().add(textPositionSequence);
} else if (currentPage == textPositionSequence.getPage()) {
entityPositionSequence.getSequences().add(textPositionSequence);
} else {
id = plainId + "-" + idDiffentPageSuffix;
idDiffentPageSuffix++;
result.add(entityPositionSequence);
entityPositionSequence = new EntityPositionSequence(id);
entityPositionSequence.setPageNumber(textPositionSequence.getPage());
entityPositionSequence.getSequences().add(textPositionSequence);
currentPage = textPositionSequence.getPage();
}
}
result.add(entityPositionSequence);
return result;
}
@Override
public String toString() {
return buildString(sequences);
}
public String asString() {
if (stringRepresentation == null) {
stringRepresentation = buildString(sequences);
}
return stringRepresentation;
}
public static String buildString(List<TextPositionSequence> sequences) {
StringBuilder sb = new StringBuilder();
for (TextPositionSequence word : sequences) {
sb.append(word.toString());
sb.append(' ');
}
return TextNormalizationUtilities.removeHyphenLineBreaks(sb.toString()).replaceAll("\n", " ").replaceAll(" {2}", " ");
}
public String getAsStringWithLinebreaksSorted() {
return getAsStringWithLinebreaksSorted(this.sequences);
}
public String getAsStringWithLinebreaksSorted(List<TextPositionSequence> sequences) {
var sorted = sequences.stream()
.sorted(Comparator.comparing(a -> a.getTextPositions().get(0).getXDirAdj()))
.sorted(Comparator.comparing(a -> a.getTextPositions().get(0).getYDirAdj()))
.sorted(Comparator.comparing(a -> a.getPage()))
.collect(Collectors.toList());
StringBuilder sb = new StringBuilder();
TextPositionSequence previous = null;
for (TextPositionSequence word : sorted) {
if (previous != null) {
if (Math.abs(previous.getMaxYDirAdj() - word.getMaxYDirAdj()) > word.getTextHeight()) {
sb.append('\n');
} else {
sb.append(' ');
}
}
sb.append(word.toString());
previous = word;
}
return sb.append("\n").toString();
}
public String getAsStringWithLinebreaks() {
StringBuilder sb = new StringBuilder();
TextPositionSequence previous = null;
for (TextPositionSequence word : sequences) {
if (previous != null) {
if (Math.abs(previous.getMaxYDirAdj() - word.getMaxYDirAdj()) > word.getTextHeight()) {
sb.append('\n');
} else {
sb.append(' ');
}
}
sb.append(word.toString());
previous = word;
}
return sb.append("\n").toString();
}
}

View File

@ -1,10 +1,8 @@
package com.iqser.red.service.redaction.v1.server.redaction.model;
package com.iqser.red.service.redaction.v1.server.redaction.model.dictionary;
import java.util.HashMap;
import java.util.Map;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.DictionaryRepresentation;
import lombok.Data;
import lombok.NoArgsConstructor;

View File

@ -1,39 +0,0 @@
package com.iqser.red.service.redaction.v1.server.redaction.rulebuilder;
import com.iqser.red.service.redaction.v1.model.Argument;
import com.iqser.red.service.redaction.v1.model.RuleBuilderModel;
import com.iqser.red.service.redaction.v1.model.RuleElement;
import com.iqser.red.service.redaction.v1.server.redaction.model.Section;
import org.springframework.stereotype.Service;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class RuleBuilderModelService {
public RuleBuilderModel getRuleBuilderModel() {
var whenConditions = Arrays.stream(Section.class.getDeclaredMethods()).filter(m -> m.isAnnotationPresent(Section.WhenCondition.class)).collect(Collectors.toList());
var thenActions = Arrays.stream(Section.class.getDeclaredMethods()).filter(m -> m.isAnnotationPresent(Section.ThenAction.class)).collect(Collectors.toList());
RuleBuilderModel ruleBuilderModel = new RuleBuilderModel();
ruleBuilderModel.setWhenClauses(whenConditions.stream().map(c -> new RuleElement(c.getName(), toArguments(c))).collect(Collectors.toList()));
ruleBuilderModel.setThenConditions(thenActions.stream().map(c -> new RuleElement(c.getName(), toArguments(c))).collect(Collectors.toList()));
return ruleBuilderModel;
}
private List<Argument> toArguments(Method c) {
return Arrays.stream(c.getParameters())
.map(parameter -> new Argument(parameter.getName(), parameter.getAnnotation(Section.Argument.class).value()))
.collect(Collectors.toList());
}
}

View File

@ -1,4 +1,4 @@
package com.iqser.red.service.redaction.v1.server.redaction.service.analyze;
package com.iqser.red.service.redaction.v1.server.redaction.service;
import static com.iqser.red.service.redaction.v1.server.redaction.service.ImportedRedactionService.IMPORTED_REDACTION_TYPE;
@ -20,6 +20,7 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.FileAttribu
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.legalbasis.LegalBasis;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLog;
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.redaction.v1.model.StructureAnalyzeRequest;
@ -27,31 +28,22 @@ 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.exception.RedactionException;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.adapter.ImageServiceResponseAdapter;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Document;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.ClassificationDocument;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.image.ClassifiedImage;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.SectionText;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.SimplifiedSectionText;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.SimplifiedText;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.service.PdfSegmentationService;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.mapper.DocumentDataMapper;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.mapper.DocumentGraphMapper;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.factory.DocumentGraphFactory;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SectionNode;
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.Section;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SemanticNode;
import com.iqser.red.service.redaction.v1.server.redaction.adapter.NerEntities;
import com.iqser.red.service.redaction.v1.server.redaction.adapter.NerEntitiesAdapter;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.Dictionary;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.DictionaryIncrement;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.DictionaryVersion;
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.redaction.service.ImportedRedactionService;
import com.iqser.red.service.redaction.v1.server.redaction.service.RedactionChangeLogService;
import com.iqser.red.service.redaction.v1.server.redaction.service.RedactionLogCreatorService;
import com.iqser.red.service.redaction.v1.server.redaction.service.SectionGridCreatorService;
import com.iqser.red.service.redaction.v1.server.redaction.service.SectionTextBuilderService;
import com.iqser.red.service.redaction.v1.server.redaction.service.entityredaction.EntityRedactionService;
import com.iqser.red.service.redaction.v1.server.settings.RedactionServiceSettings;
import com.iqser.red.service.redaction.v1.server.storage.RedactionStorageService;
@ -80,11 +72,10 @@ public class AnalyzeService {
RedactionChangeLogService redactionChangeLogService;
LegalBasisClient legalBasisClient;
RedactionServiceSettings redactionServiceSettings;
SectionTextBuilderService sectionTextBuilderService;
SectionGridCreatorService sectionGridCreatorService;
ImageServiceResponseAdapter imageServiceResponseAdapter;
ImportedRedactionService importedRedactionService;
SectionFinder sectionFinder;
SectionFinderService sectionFinderService;
FunctionTimerValues redactmanagerAnalyzePagewiseValues;
@ -95,7 +86,7 @@ public class AnalyzeService {
long startTime = System.currentTimeMillis();
var pageCount = 0;
Document classifiedDoc;
ClassificationDocument classifiedDoc;
try {
var storedObjectStream = redactionStorageService.getStoredObject(RedactionStorageService.StorageIdUtils.getStorageId(analyzeRequest.getDossierId(),
@ -113,22 +104,20 @@ public class AnalyzeService {
throw new RedactionException(e);
}
DocumentGraph documentGraph = DocumentGraphFactory.buildDocumentGraph(classifiedDoc);
Document document = DocumentGraphFactory.buildDocumentGraph(classifiedDoc);
List<SectionText> sectionTexts = sectionTextBuilderService.buildSectionText(classifiedDoc);
sectionGridCreatorService.createSectionGrid(classifiedDoc, pageCount, sectionTexts);
sectionGridCreatorService.createSectionGrid(classifiedDoc, pageCount);
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, DocumentDataMapper.toDocumentData(documentGraph));
redactionStorageService.storeObject(analyzeRequest.getDossierId(), analyzeRequest.getFileId(), FileType.SIMPLIFIED_TEXT, toSimplifiedText(documentGraph));
redactionStorageService.storeObject(analyzeRequest.getDossierId(), analyzeRequest.getFileId(), FileType.TEXT, DocumentDataMapper.toDocumentData(document));
redactionStorageService.storeObject(analyzeRequest.getDossierId(), analyzeRequest.getFileId(), FileType.SIMPLIFIED_TEXT, toSimplifiedText(document));
redactionStorageService.storeObject(analyzeRequest.getDossierId(), analyzeRequest.getFileId(), FileType.SECTION_GRID, classifiedDoc.getSectionGrid());
return AnalyzeResult.builder()
.dossierId(analyzeRequest.getDossierId())
.fileId(analyzeRequest.getFileId())
.duration(System.currentTimeMillis() - startTime)
.numberOfPages(documentGraph.getNumberOfPages())
.numberOfPages(document.getNumberOfPages())
.analysisVersion(redactionServiceSettings.getAnalysisVersion())
.build();
}
@ -138,9 +127,9 @@ public class AnalyzeService {
public AnalyzeResult analyze(AnalyzeRequest analyzeRequest) {
long startTime = System.currentTimeMillis();
DocumentGraph documentGraph = DocumentGraphMapper.toDocumentGraph(redactionStorageService.getDocumentData(analyzeRequest.getDossierId(), analyzeRequest.getFileId()));
Document document = DocumentGraphMapper.toDocumentGraph(redactionStorageService.getDocumentData(analyzeRequest.getDossierId(), analyzeRequest.getFileId()));
NerEntities nerEntities = getEntityRecognitionEntities(analyzeRequest, documentGraph);
NerEntities nerEntities = getEntityRecognitionEntities(analyzeRequest, document);
dictionaryService.updateDictionary(analyzeRequest.getDossierTemplateId(), analyzeRequest.getDossierId());
KieContainer kieContainer = droolsExecutionService.updateRules(analyzeRequest.getDossierTemplateId());
@ -149,11 +138,11 @@ public class AnalyzeService {
log.debug("Starting Dictionary Search");
long dictSearchStart = System.currentTimeMillis();
entityRedactionService.addDictionaryEntities(dictionary, documentGraph);
entityRedactionService.addDictionaryEntities(dictionary, document);
log.debug("Finished Dictionary Search in {} ms", System.currentTimeMillis() - dictSearchStart);
Set<FileAttribute> addedFileAttributes = entityRedactionService.addRuleEntities(dictionary, documentGraph, kieContainer, analyzeRequest, nerEntities);
Set<FileAttribute> addedFileAttributes = entityRedactionService.addRuleEntities(dictionary, document, kieContainer, analyzeRequest, nerEntities);
List<RedactionLogEntry> redactionLogEntries = redactionLogCreatorService.createRedactionLog(documentGraph, analyzeRequest.getDossierTemplateId());
List<RedactionLogEntry> redactionLogEntries = redactionLogCreatorService.createRedactionLog(document, analyzeRequest.getDossierTemplateId());
List<LegalBasis> legalBasis = legalBasisClient.getLegalBasisMapping(analyzeRequest.getDossierTemplateId());
RedactionLog redactionLog = new RedactionLog(redactionServiceSettings.getAnalysisVersion(),
@ -172,7 +161,7 @@ public class AnalyzeService {
true);
redactionLog.setRedactionLogEntry(importedRedactionFilteredEntries);
return finalizeAnalysis(analyzeRequest, startTime, redactionLog, documentGraph.getNumberOfPages(), dictionary.getVersion(), false, addedFileAttributes);
return finalizeAnalysis(analyzeRequest, startTime, redactionLog, document.getNumberOfPages(), dictionary.getVersion(), false, addedFileAttributes);
}
@ -181,26 +170,32 @@ public class AnalyzeService {
public AnalyzeResult reanalyze(@RequestBody AnalyzeRequest analyzeRequest) {
long startTime = System.currentTimeMillis();
RedactionLog redactionLog = redactionStorageService.getRedactionLog(analyzeRequest.getDossierId(), analyzeRequest.getFileId());
DocumentGraph documentGraph = DocumentGraphMapper.toDocumentGraph(redactionStorageService.getDocumentData(analyzeRequest.getDossierId(), analyzeRequest.getFileId()));
RedactionLog previousRedactionLog = redactionStorageService.getRedactionLog(analyzeRequest.getDossierId(), analyzeRequest.getFileId());
Document document = DocumentGraphMapper.toDocumentGraph(redactionStorageService.getDocumentData(analyzeRequest.getDossierId(), analyzeRequest.getFileId()));
// not yet ready for reanalysis
if (redactionLog == null || documentGraph == null || documentGraph.getNumberOfPages() == 0) {
if (previousRedactionLog == null || document == null || document.getNumberOfPages() == 0) {
return analyze(analyzeRequest);
}
DictionaryIncrement dictionaryIncrement = dictionaryService.getDictionaryIncrements(analyzeRequest.getDossierTemplateId(),
new DictionaryVersion(redactionLog.getDictionaryVersion(), redactionLog.getDossierDictionaryVersion()),
new DictionaryVersion(previousRedactionLog.getDictionaryVersion(), previousRedactionLog.getDossierDictionaryVersion()),
analyzeRequest.getDossierId());
Set<Integer> sectionsToReanalyseIds = getSectionsToReanalyseIds(analyzeRequest, redactionLog, documentGraph, dictionaryIncrement);
List<SemanticNode> sectionsToReAnalyse = getSectionsToReAnalyse(documentGraph, sectionsToReanalyseIds);
Set<Integer> sectionsToReanalyseIds = getSectionsToReanalyseIds(analyzeRequest, previousRedactionLog, document, dictionaryIncrement);
List<SemanticNode> sectionsToReAnalyse = getSectionsToReAnalyse(document, sectionsToReanalyseIds);
if (sectionsToReAnalyse.isEmpty()) {
return finalizeAnalysis(analyzeRequest, startTime, redactionLog, documentGraph.getNumberOfPages(), dictionaryIncrement.getDictionaryVersion(), true, new HashSet<>());
return finalizeAnalysis(analyzeRequest,
startTime,
previousRedactionLog,
document.getNumberOfPages(),
dictionaryIncrement.getDictionaryVersion(),
true,
new HashSet<>());
}
NerEntities nerEntities = getEntityRecognitionEntitiesFilteredBySectionIds(analyzeRequest, documentGraph, sectionsToReanalyseIds);
NerEntities nerEntities = getEntityRecognitionEntitiesFilteredBySectionIds(analyzeRequest, document, sectionsToReanalyseIds);
log.info("Reanalyze {} sections with {} Ner Entities", sectionsToReAnalyse.size(), nerEntities.getNerEntityList().size());
KieContainer kieContainer = droolsExecutionService.updateRules(analyzeRequest.getDossierTemplateId());
Dictionary dictionary = dictionaryService.getDeepCopyDictionary(analyzeRequest.getDossierTemplateId(), analyzeRequest.getDossierId());
@ -208,10 +203,10 @@ public class AnalyzeService {
sectionsToReAnalyse.forEach(node -> entityRedactionService.addDictionaryEntities(dictionary, node));
long ruleStart = System.currentTimeMillis();
Set<FileAttribute> addedFileAttributes = entityRedactionService.addRuleEntities(dictionary, documentGraph, sectionsToReAnalyse, kieContainer, analyzeRequest, nerEntities);
Set<FileAttribute> addedFileAttributes = entityRedactionService.addRuleEntities(dictionary, document, sectionsToReAnalyse, kieContainer, analyzeRequest, nerEntities);
log.info("Rule execution took {} ms", System.currentTimeMillis() - ruleStart);
List<RedactionLogEntry> newRedactionLogEntries = redactionLogCreatorService.createRedactionLog(documentGraph, analyzeRequest.getDossierTemplateId());
List<RedactionLogEntry> newRedactionLogEntries = redactionLogCreatorService.createRedactionLog(document, analyzeRequest.getDossierTemplateId());
var importedRedactionFilteredEntries = importedRedactionService.processImportedRedactions(analyzeRequest.getDossierTemplateId(),
analyzeRequest.getDossierId(),
@ -219,10 +214,17 @@ public class AnalyzeService {
newRedactionLogEntries,
false);
redactionLog.getRedactionLogEntry().removeIf(entry -> sectionsToReanalyseIds.contains(entry.getSectionNumber()) && !entry.getType().equals(IMPORTED_REDACTION_TYPE));
redactionLog.getRedactionLogEntry().addAll(importedRedactionFilteredEntries);
previousRedactionLog.getRedactionLogEntry()
.removeIf(entry -> sectionsToReanalyseIds.contains(entry.getSectionNumber()) && !entry.getType().equals(IMPORTED_REDACTION_TYPE));
previousRedactionLog.getRedactionLogEntry().addAll(importedRedactionFilteredEntries);
return finalizeAnalysis(analyzeRequest, startTime, redactionLog, documentGraph.getNumberOfPages(), dictionaryIncrement.getDictionaryVersion(), true, addedFileAttributes);
return finalizeAnalysis(analyzeRequest,
startTime,
previousRedactionLog,
document.getNumberOfPages(),
dictionaryIncrement.getDictionaryVersion(),
true,
addedFileAttributes);
}
@ -239,7 +241,7 @@ public class AnalyzeService {
excludeExcludedPages(redactionLog, analyzeRequest.getExcludedPages());
var redactionLogChange = redactionChangeLogService.computeChanges(analyzeRequest.getDossierId(),
RedactionLogChanges redactionLogChange = redactionChangeLogService.computeChanges(analyzeRequest.getDossierId(),
analyzeRequest.getFileId(),
redactionLog,
analyzeRequest.getAnalysisNumber());
@ -268,27 +270,27 @@ public class AnalyzeService {
}
private static List<SemanticNode> getSectionsToReAnalyse(DocumentGraph documentGraph, Set<Integer> sectionsToReanalyseIds) {
private static List<SemanticNode> getSectionsToReAnalyse(Document document, Set<Integer> sectionsToReanalyseIds) {
return documentGraph.streamChildren().filter(section -> sectionsToReanalyseIds.contains(section.getTocId().get(0))).collect(Collectors.toList());
return document.streamChildren().filter(section -> sectionsToReanalyseIds.contains(section.getTreeId().get(0))).collect(Collectors.toList());
}
private Set<Integer> getSectionsToReanalyseIds(AnalyzeRequest analyzeRequest, RedactionLog redactionLog, DocumentGraph documentGraph, DictionaryIncrement dictionaryIncrement) {
private Set<Integer> getSectionsToReanalyseIds(AnalyzeRequest analyzeRequest, RedactionLog redactionLog, Document document, DictionaryIncrement dictionaryIncrement) {
return analyzeRequest.getSectionsToReanalyse().isEmpty() //
? sectionFinder.findSectionsToReanalyse(dictionaryIncrement, redactionLog, documentGraph, analyzeRequest) //
? sectionFinderService.findSectionsToReanalyse(dictionaryIncrement, redactionLog, document, analyzeRequest) //
: analyzeRequest.getSectionsToReanalyse();
}
private NerEntities getEntityRecognitionEntitiesFilteredBySectionIds(AnalyzeRequest analyzeRequest, DocumentGraph documentGraph, Set<Integer> sectionsToReanalyseIds) {
private NerEntities getEntityRecognitionEntitiesFilteredBySectionIds(AnalyzeRequest analyzeRequest, Document document, Set<Integer> sectionsToReanalyseIds) {
NerEntities nerEntities;
if (redactionServiceSettings.isNerServiceEnabled()) {
NerEntitiesModel nerEntitiesModel = redactionStorageService.getNerEntities(analyzeRequest.getDossierId(), analyzeRequest.getFileId());
nerEntitiesModel = filterNerEntitiesModelBySectionIds(sectionsToReanalyseIds, nerEntitiesModel);
nerEntities = nerEntitiesAdapter.toNerEntities(nerEntitiesModel, documentGraph);
nerEntities = nerEntitiesAdapter.toNerEntities(nerEntitiesModel, document);
} else {
nerEntities = new NerEntities(Collections.emptyList());
}
@ -304,11 +306,11 @@ public class AnalyzeService {
}
private NerEntities getEntityRecognitionEntities(AnalyzeRequest analyzeRequest, DocumentGraph documentGraph) {
private NerEntities getEntityRecognitionEntities(AnalyzeRequest analyzeRequest, Document document) {
NerEntities nerEntities;
if (redactionServiceSettings.isNerServiceEnabled()) {
nerEntities = nerEntitiesAdapter.toNerEntities(redactionStorageService.getNerEntities(analyzeRequest.getDossierId(), analyzeRequest.getFileId()), documentGraph);
nerEntities = nerEntitiesAdapter.toNerEntities(redactionStorageService.getNerEntities(analyzeRequest.getDossierId(), analyzeRequest.getFileId()), document);
} else {
nerEntities = new NerEntities(Collections.emptyList());
}
@ -334,16 +336,16 @@ public class AnalyzeService {
}
private SimplifiedText toSimplifiedText(DocumentGraph documentGraph) {
private SimplifiedText toSimplifiedText(Document document) {
List<SimplifiedSectionText> simplifiedSectionTexts = documentGraph.getMainSections().stream().map(this::toSimplifiedSectionText).toList();
return SimplifiedText.builder().numberOfPages(documentGraph.getNumberOfPages()).sectionTexts(simplifiedSectionTexts).build();
List<SimplifiedSectionText> simplifiedSectionTexts = document.getMainSections().stream().map(this::toSimplifiedSectionText).toList();
return SimplifiedText.builder().numberOfPages(document.getNumberOfPages()).sectionTexts(simplifiedSectionTexts).build();
}
private SimplifiedSectionText toSimplifiedSectionText(SectionNode sectionNode) {
private SimplifiedSectionText toSimplifiedSectionText(Section section) {
return SimplifiedSectionText.builder().sectionNumber(sectionNode.getTocId().get(0)).text(sectionNode.buildTextBlock().getSearchText()).build();
return SimplifiedSectionText.builder().sectionNumber(section.getTreeId().get(0)).text(section.buildTextBlock().getSearchText()).build();
}
}

View File

@ -22,7 +22,6 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemp
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.DictionaryEntry;
import com.iqser.red.service.redaction.v1.server.client.DictionaryClient;
import com.iqser.red.service.redaction.v1.server.multitenancy.TenantContext;
import com.iqser.red.service.redaction.v1.server.redaction.model.TenantDictionary;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.Dictionary;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.DictionaryEntries;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.DictionaryIncrement;
@ -30,6 +29,7 @@ import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.Dict
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.DictionaryModel;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.DictionaryRepresentation;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.DictionaryVersion;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.TenantDictionary;
import com.iqser.red.service.redaction.v1.server.settings.RedactionServiceSettings;
import feign.FeignException;

View File

@ -23,14 +23,12 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.FileAttribu
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRedactions;
import com.iqser.red.service.redaction.v1.server.client.RulesClient;
import com.iqser.red.service.redaction.v1.server.exception.RulesValidationException;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
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.SemanticNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.services.EntityCreationService;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.services.ManualRedactionApplicationService;
import com.iqser.red.service.redaction.v1.server.redaction.adapter.NerEntities;
import com.iqser.red.service.redaction.v1.server.redaction.adapter.NerEntitiesAdapter;
import com.iqser.red.service.redaction.v1.server.redaction.model.Entity;
import com.iqser.red.service.redaction.v1.server.redaction.model.Section;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.Dictionary;
import io.micrometer.core.annotation.Timed;
@ -66,7 +64,7 @@ public class DroolsExecutionService {
@Timed("redactmanager_executeRules")
public List<FileAttribute> executeRules(KieContainer kieContainer,
DocumentGraph document,
Document document,
Dictionary dictionary,
List<FileAttribute> fileAttributes,
ManualRedactions manualRedactions,
@ -78,7 +76,7 @@ public class DroolsExecutionService {
@Timed("redactmanager_executeRules")
public List<FileAttribute> executeRules(KieContainer kieContainer,
DocumentGraph document,
Document document,
List<SemanticNode> sectionsToAnalyze,
Dictionary dictionary,
List<FileAttribute> fileAttributes,
@ -116,20 +114,6 @@ public class DroolsExecutionService {
}
@Timed("redactmanager_executeRules")
public Section executeRules(KieContainer kieContainer, Section section) {
KieSession kieSession = kieContainer.newKieSession();
kieSession.setGlobal("section", section);
kieSession.insert(section);
kieSession.fireAllRules();
kieSession.dispose();
return section;
}
public List<FileAttribute> getFileAttributes(KieSession kieSession) {
List<FileAttribute> fileAttributes = new LinkedList<>();
@ -141,17 +125,6 @@ public class DroolsExecutionService {
}
public List<Entity> getEntities(KieSession ks) {
List<Entity> entities = new LinkedList<>();
QueryResults entitiesResult = ks.getQueryResults("getEntities");
for (QueryResultsRow resultsRow : entitiesResult) {
entities.add((Entity) resultsRow.get("$result"));
}
return entities;
}
public KieContainer updateRules(String dossierTemplateId) {
long version = rulesClient.getVersion(dossierTemplateId);

View File

@ -1,4 +1,4 @@
package com.iqser.red.service.redaction.v1.server.redaction.service.entityredaction;
package com.iqser.red.service.redaction.v1.server.redaction.service;
import java.util.List;
import java.util.Set;
@ -10,16 +10,15 @@ import org.springframework.stereotype.Service;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AnalyzeRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileAttribute;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Engine;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.EntityType;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
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.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.redaction.adapter.NerEntities;
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityType;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.Dictionary;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.SearchImplementation;
import com.iqser.red.service.redaction.v1.server.redaction.service.DroolsExecutionService;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
@ -36,16 +35,12 @@ public class EntityRedactionService {
EntityCreationService entityCreationService;
public Set<FileAttribute> addRuleEntities(Dictionary dictionary,
DocumentGraph documentGraph,
KieContainer kieContainer,
AnalyzeRequest analyzeRequest,
NerEntities nerEntities) {
public Set<FileAttribute> addRuleEntities(Dictionary dictionary, Document document, KieContainer kieContainer, AnalyzeRequest analyzeRequest, NerEntities nerEntities) {
log.debug("Starting Drools Execution");
long droolsStart = System.currentTimeMillis();
List<FileAttribute> allFileAttributes = droolsExecutionService.executeRules(kieContainer,
documentGraph,
document,
dictionary,
analyzeRequest.getFileAttributes(),
analyzeRequest.getManualRedactions(),
@ -56,7 +51,7 @@ public class EntityRedactionService {
public Set<FileAttribute> addRuleEntities(Dictionary dictionary,
DocumentGraph documentGraph,
Document document,
List<SemanticNode> sectionsToReanalyze,
KieContainer kieContainer,
AnalyzeRequest analyzeRequest,
@ -65,7 +60,7 @@ public class EntityRedactionService {
log.debug("Starting Drools execution");
long ruleExecutionStart = System.currentTimeMillis();
List<FileAttribute> allFileAttributes = droolsExecutionService.executeRules(kieContainer,
documentGraph,
document,
sectionsToReanalyze,
dictionary,
analyzeRequest.getFileAttributes(),

View File

@ -1,8 +1,11 @@
package com.iqser.red.service.redaction.v1.server.redaction.service;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.stereotype.Service;
@ -11,18 +14,16 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.AnalyzeResu
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRedactions;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.Rectangle;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualRedactionEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Engine;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.section.SectionArea;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.SectionText;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.Text;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextPositionSequence;
import com.iqser.red.service.redaction.v1.server.redaction.model.Entity;
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityPositionSequence;
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityType;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.SearchImplementation;
import com.iqser.red.service.redaction.v1.server.redaction.utils.EntitySearchUtils;
import com.iqser.red.service.redaction.v1.server.redaction.utils.FindEntityDetails;
import com.iqser.red.service.redaction.v1.server.settings.RedactionServiceSettings;
import com.iqser.red.service.redaction.v1.server.exception.NotFoundException;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.mapper.DocumentGraphMapper;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.EntityType;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
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.graph.nodes.SemanticNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.services.EntityCreationService;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RectangleMapper;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RedactionSearchUtility;
import com.iqser.red.service.redaction.v1.server.storage.RedactionStorageService;
import io.micrometer.core.annotation.Timed;
@ -35,36 +36,34 @@ import lombok.extern.slf4j.Slf4j;
public class ManualRedactionSurroundingTextService {
private final RedactionStorageService redactionStorageService;
private final RedactionServiceSettings redactionServiceSettings;
private final EntityCreationService entityCreationService;
@Timed("redactmanager_surroundingTextAnalysis")
public AnalyzeResult addSurroundingText(String dossierId, String fileId, ManualRedactions manualRedactions) {
long startTime = System.currentTimeMillis();
Text text = redactionStorageService.getText(dossierId, fileId);
Document text = DocumentGraphMapper.toDocumentGraph(redactionStorageService.getDocumentData(dossierId, fileId));
List<ManualRedactionEntry> processedAddRedactions = new ArrayList<>();
for (SectionText sectionText : text.getSectionTexts()) {
for (SemanticNode node : text.streamChildren().toList()) {
if (manualRedactions.getEntriesToAdd().isEmpty()) {
break;
}
for (SectionArea sectionArea : sectionText.getSectionAreas()) {
var addItty = manualRedactions.getEntriesToAdd().iterator();
while (addItty.hasNext()) {
var manualAddRedaction = addItty.next();
if (sectionContainsEntry(sectionArea, manualAddRedaction.getPositions())) {
var surroundingText = findSurroundingText(sectionText, manualAddRedaction.getValue(), manualAddRedaction.getPositions());
manualAddRedaction.setTextBefore(surroundingText.getLeft());
manualAddRedaction.setTextAfter(surroundingText.getRight());
processedAddRedactions.add(manualAddRedaction);
addItty.remove();
}
var addItty = manualRedactions.getEntriesToAdd().iterator();
while (addItty.hasNext()) {
var manualAddRedaction = addItty.next();
if (sectionContainsEntry(node, manualAddRedaction.getPositions())) {
Pair<String, String> surroundingText = findSurroundingText(node, manualAddRedaction.getValue(), manualAddRedaction.getPositions());
manualAddRedaction.setTextBefore(surroundingText.getLeft());
manualAddRedaction.setTextAfter(surroundingText.getRight());
processedAddRedactions.add(manualAddRedaction);
addItty.remove();
}
}
}
manualRedactions.getEntriesToAdd().addAll(processedAddRedactions);
@ -73,43 +72,22 @@ public class ManualRedactionSurroundingTextService {
}
private Pair<String, String> findSurroundingText(SectionText sectionText, String value, List<Rectangle> toFindPositions) {
private Pair<String, String> findSurroundingText(SemanticNode node, String value, List<Rectangle> toFindPositions) {
Set<Entity> entities = EntitySearchUtils.find(sectionText.getText(),
new SearchImplementation(value, false),
new FindEntityDetails("dummy", sectionText.getHeadline(), sectionText.getSectionNumber(), false, false, Engine.DICTIONARY, EntityType.ENTITY));
Set<Entity> entitiesWithPositions = EntitySearchUtils.clearAndFindPositions(entities, sectionText.getSearchableText(), null, null);
Entity correctEntity = getEntityOnCorrectPosition(entitiesWithPositions, toFindPositions);
if (correctEntity == null) {
log.warn("Could not calculate surrounding text");
return Pair.of("", "");
}
if (sectionText.getCellStarts() != null && !sectionText.getCellStarts().isEmpty()) {
SurroundingWordsService.addSurroundingText(Set.of(correctEntity),
sectionText.getSearchableText(),
null,
sectionText.getCellStarts(),
redactionServiceSettings.getSurroundingWordsOffsetWindow(),
redactionServiceSettings.getNumberOfSurroundingWords());
} else {
SurroundingWordsService.addSurroundingText(Set.of(correctEntity),
sectionText.getSearchableText(),
null,
redactionServiceSettings.getSurroundingWordsOffsetWindow(),
redactionServiceSettings.getNumberOfSurroundingWords());
}
Set<RedactionEntity> entities = RedactionSearchUtility.findBoundariesByString(value, node.buildTextBlock())
.stream()
.map(boundary -> entityCreationService.byBoundary(boundary, "searchHelper", EntityType.RECOMMENDATION, node))
.collect(Collectors.toSet());
RedactionEntity correctEntity = getEntityOnCorrectPosition(entities, toFindPositions);
return Pair.of(correctEntity.getTextBefore(), correctEntity.getTextAfter());
}
private boolean sectionContainsEntry(SectionArea sectionArea, List<Rectangle> positions) {
private boolean sectionContainsEntry(SemanticNode semanticNode, List<Rectangle> positions) {
for (Rectangle position : positions) {
if (sectionArea.contains(position)) {
if (semanticNode.containsRectangle(RectangleMapper.toRectangle2D(position), position.getPage())) {
return true;
}
}
@ -117,30 +95,22 @@ public class ManualRedactionSurroundingTextService {
}
private Entity getEntityOnCorrectPosition(Set<Entity> entitiesWithPositions, List<Rectangle> toFindPositions) {
private RedactionEntity getEntityOnCorrectPosition(Set<RedactionEntity> entities, List<Rectangle> toFindPositions) {
for (Entity entityWithPos : entitiesWithPositions) {
for (EntityPositionSequence entityPositionSequence : entityWithPos.getPositionSequences()) {
for (TextPositionSequence textPositionSequence : entityPositionSequence.getSequences()) {
for (Rectangle manualRedactionRectangle : toFindPositions) {
if (intersects(manualRedactionRectangle, textPositionSequence.getRectangle())) {
return entityWithPos;
}
}
}
}
}
return null;
return entities.stream()
.filter(entity -> entity.getRedactionPositionsPerPage()
.stream()
.map(RedactionPosition::getRectanglePerLine)
.flatMap(Collection::stream)
.allMatch(rectangle2D -> toFindPositionsIntersectRectangle(toFindPositions, rectangle2D)))
.findFirst()
.orElseThrow(() -> new NotFoundException("No matching Entity could be found for positions" + toFindPositions));
}
public boolean intersects(Rectangle manualPosition, com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Rectangle textPositionRectangle) {
private static boolean toFindPositionsIntersectRectangle(List<Rectangle> toFindPositions, Rectangle2D rectangle2D) {
return textPositionRectangle.getTopLeft().getX() + textPositionRectangle.getWidth() > manualPosition.getTopLeftX() && textPositionRectangle.getTopLeft()
.getY() + textPositionRectangle.getHeight() > manualPosition.getTopLeftY() && textPositionRectangle.getTopLeft()
.getX() < manualPosition.getTopLeftX() + manualPosition.getWidth() && textPositionRectangle.getTopLeft()
.getY() < manualPosition.getTopLeftY() + manualPosition.getHeight();
return toFindPositions.stream().map(RectangleMapper::toRectangle2D).anyMatch(toFindRectangle -> toFindRectangle.intersects(rectangle2D));
}
}

View File

@ -11,12 +11,12 @@ import org.springframework.stereotype.Service;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Rectangle;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLogEntry;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.EntityType;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
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.DocumentGraph;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.ImageNode;
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityType;
import com.iqser.red.service.redaction.v1.server.redaction.model.ImageType;
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.Image;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.ImageType;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -29,15 +29,15 @@ public class RedactionLogCreatorService {
private final DictionaryService dictionaryService;
public List<RedactionLogEntry> createRedactionLog(DocumentGraph documentGraph, String dossierTemplateId) {
public List<RedactionLogEntry> createRedactionLog(Document document, String dossierTemplateId) {
List<RedactionLogEntry> entries = new ArrayList<>();
Set<String> processedIds = new HashSet<>();
documentGraph.getEntities()
document.getEntities()
.stream()
.filter(RedactionLogCreatorService::isEntityOrRecommendationType)
.forEach(entityNode -> entries.addAll(toRedactionLogEntries(entityNode, processedIds, dossierTemplateId)));
documentGraph.streamAllImages().forEach(imageNode -> entries.add(createRedactionLogEntry(imageNode, dossierTemplateId)));
document.streamAllImages().forEach(imageNode -> entries.add(createRedactionLogEntry(imageNode, dossierTemplateId)));
return entries;
}
@ -67,7 +67,7 @@ public class RedactionLogCreatorService {
List<Rectangle> rectanglesPerLine = redactionPosition.getRectanglePerLine()
.stream()
.map(rectangle2D -> toRedactionLogRectangle(rectangle2D, redactionPosition.getPageNode().getNumber()))
.map(rectangle2D -> toRedactionLogRectangle(rectangle2D, redactionPosition.getPage().getNumber()))
.toList();
redactionLogEntry.setPositions(rectanglesPerLine);
@ -85,7 +85,7 @@ public class RedactionLogCreatorService {
.stream()
.filter(redactionEntity -> !redactionEntity.isRemoved() && !redactionEntity.isIgnored())
.forEach(ref -> ref.getRedactionPositionsPerPage().forEach(pos -> referenceIds.add(pos.getId())));
int sectionNumber = entity.getDeepestFullyContainingNode().getTocId().isEmpty() ? -1 : entity.getDeepestFullyContainingNode().getTocId().get(0);
int sectionNumber = entity.getDeepestFullyContainingNode().getTreeId().isEmpty() ? -1 : entity.getDeepestFullyContainingNode().getTreeId().get(0);
return RedactionLogEntry.builder()
.color(getColor(entity.getType(), dossierTemplateId, entity.isRedaction()))
@ -112,7 +112,7 @@ public class RedactionLogCreatorService {
}
public RedactionLogEntry createRedactionLogEntry(ImageNode image, String dossierTemplateId) {
public RedactionLogEntry createRedactionLogEntry(Image image, String dossierTemplateId) {
String imageType = image.getImageType().equals(ImageType.OTHER) ? "image" : image.getImageType().toString().toLowerCase();
return RedactionLogEntry.builder()
@ -128,7 +128,7 @@ public class RedactionLogCreatorService {
.isDictionaryEntry(false)
.isRecommendation(false)
.positions(List.of(toRedactionLogRectangle(image.getPosition(), image.getPage().getNumber())))
.sectionNumber(image.getTocId().get(0))
.sectionNumber(image.getTreeId().get(0))
.section(image.getParent().buildTextBlock().getSearchText())
.imageHasTransparency(image.isTransparent())
.build();

View File

@ -0,0 +1,22 @@
package com.iqser.red.service.redaction.v1.server.redaction.service;
import java.util.Collections;
import org.springframework.stereotype.Service;
import com.iqser.red.service.redaction.v1.model.RuleBuilderModel;
@Service
public class RuleBuilderModelService {
public RuleBuilderModel getRuleBuilderModel() {
RuleBuilderModel ruleBuilderModel = new RuleBuilderModel();
ruleBuilderModel.setWhenClauses(Collections.emptyList());
ruleBuilderModel.setThenConditions(Collections.emptyList());
return ruleBuilderModel;
}
}

View File

@ -1,8 +1,6 @@
package com.iqser.red.service.redaction.v1.server.redaction.service.analyze;
package com.iqser.red.service.redaction.v1.server.redaction.service;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -16,12 +14,9 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualImageRecategorization;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualLegalBasisChange;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualResizeRedaction;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Rectangle;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLogEntry;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
import com.iqser.red.service.redaction.v1.server.redaction.model.Image;
import com.iqser.red.service.redaction.v1.server.redaction.model.RedRectangle2D;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Document;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.DictionaryIncrement;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.DictionaryIncrementValue;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.SearchImplementation;
@ -36,31 +31,27 @@ import lombok.extern.slf4j.Slf4j;
@Component
@RequiredArgsConstructor
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
class SectionFinder {
class SectionFinderService {
@Timed("redactmanager_findSectionsToReanalyse")
public Set<Integer> findSectionsToReanalyse(DictionaryIncrement dictionaryIncrement, RedactionLog redactionLog, DocumentGraph documentGraph, AnalyzeRequest analyzeRequest) {
public Set<Integer> findSectionsToReanalyse(DictionaryIncrement dictionaryIncrement, RedactionLog redactionLog, Document document, AnalyzeRequest analyzeRequest) {
long start = System.currentTimeMillis();
Set<String> relevantManuallyModifiedAnnotationIds = getRelevantManuallyModifiedAnnotationIds(analyzeRequest.getManualRedactions());
Set<Integer> sectionsToReanalyse = new HashSet<>();
Map<Integer, Set<Image>> imageEntries = new HashMap<>();
for (RedactionLogEntry entry : redactionLog.getRedactionLogEntry()) {
if (entry.isLocalManualRedaction() || relevantManuallyModifiedAnnotationIds.contains(entry.getId())) {
sectionsToReanalyse.add(entry.getSectionNumber());
}
if (entry.isImage()) {
imageEntries.computeIfAbsent(entry.getSectionNumber(), x -> new HashSet<>()).add(convert(entry));
}
}
var dictionaryIncrementsSearch = new SearchImplementation(dictionaryIncrement.getValues().stream().map(DictionaryIncrementValue::getValue).collect(Collectors.toList()),
true);
documentGraph.streamChildren().forEach(mainNode -> {
document.streamChildren().forEach(mainNode -> {
if (dictionaryIncrementsSearch.atLeastOneMatches(mainNode.buildTextBlock().getSearchText())) {
sectionsToReanalyse.add(mainNode.getTocId().get(0));
sectionsToReanalyse.add(mainNode.getTreeId().get(0));
}
});
@ -83,19 +74,4 @@ class SectionFinder {
manualRedactions.getForceRedactions().stream().map(ManualForceRedaction::getAnnotationId))))).collect(Collectors.toSet());
}
private static Image convert(RedactionLogEntry entry) {
Rectangle position = entry.getPositions().get(0);
return Image.builder()
.type(entry.getType())
.position(new RedRectangle2D(position.getTopLeft().getX(), position.getTopLeft().getY(), position.getWidth(), position.getHeight()))
.sectionNumber(entry.getSectionNumber())
.section(entry.getSection())
.page(position.getPage())
.hasTransparency(entry.isImageHasTransparency())
.build();
}
}

View File

@ -2,22 +2,18 @@ package com.iqser.red.service.redaction.v1.server.redaction.service;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
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.SectionArea;
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.Document;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Section;
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.ClassificationTextBlock;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.SectionText;
import lombok.RequiredArgsConstructor;
@ -25,28 +21,21 @@ import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class SectionGridCreatorService {
public void createSectionGrid(Document classifiedDoc, int numberOfPages, List<SectionText> sectionTexts) {
public void createSectionGrid(ClassificationDocument classifiedDoc, int numberOfPages) {
for (int page = 1; page <= numberOfPages; page++) {
addSectionGrid(classifiedDoc, page);
}
// enhance section grid with headline data
sectionTexts.forEach(sectionText -> classifiedDoc.getSectionGrid()
.getSections()
.add(new SectionGrid.SectionGridSection(sectionText.getSectionNumber(),
sectionText.getHeadline(),
sectionText.getSectionAreas().stream().map(SectionArea::getPage).collect(Collectors.toSet()),
sectionText.getSectionAreas())));
}
private void addSectionGrid(Document classifiedDoc, int page) {
private void addSectionGrid(ClassificationDocument classifiedDoc, int page) {
for (Section paragraph : classifiedDoc.getSections()) {
for (ClassificationSection section : classifiedDoc.getSections()) {
for (int i = 0; i <= paragraph.getPageBlocks().size() - 1; i++) {
for (int i = 0; i <= section.getPageBlocks().size() - 1; i++) {
AbstractPageBlock textBlock = paragraph.getPageBlocks().get(i);
AbstractPageBlock textBlock = section.getPageBlocks().get(i);
if (textBlock.getPage() != page) {
continue;
@ -62,7 +51,7 @@ public class SectionGridCreatorService {
tb.getPdfMaxX() - tb.getPdfMinX(),
tb.getPdfMaxY() - tb.getPdfMinY(),
i + 1,
paragraph.getPageBlocks().size(),
section.getPageBlocks().size(),
null));
} else if (textBlock instanceof TablePageBlock) {
@ -83,7 +72,7 @@ public class SectionGridCreatorService {
textBlock.getWidth(),
textBlock.getHeight(),
i + 1,
paragraph.getPageBlocks().size(),
section.getPageBlocks().size(),
cellRectangles));
}

View File

@ -1,210 +0,0 @@
package com.iqser.red.service.redaction.v1.server.redaction.service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.apache.commons.collections4.CollectionUtils;
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.SectionArea;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Document;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Footer;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Header;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Section;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.image.ClassifiedImage;
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 com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.SectionText;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.UnclassifiedText;
import com.iqser.red.service.redaction.v1.server.redaction.model.CellValue;
import com.iqser.red.service.redaction.v1.server.redaction.model.Image;
import com.iqser.red.service.redaction.v1.server.redaction.model.ImageType;
import com.iqser.red.service.redaction.v1.server.redaction.model.SearchableText;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
@RequiredArgsConstructor
public class SectionTextBuilderService {
public List<SectionText> buildSectionText(Document classifiedDoc) {
List<SectionText> sectionTexts = new ArrayList<>();
AtomicInteger sectionNumber = new AtomicInteger(1);
for (Section section : classifiedDoc.getSections()) {
List<TablePageBlock> tables = section.getTables();
for (TablePageBlock table : tables) {
sectionTexts.addAll(processTablePerRow(table, sectionNumber));
sectionNumber.incrementAndGet();
}
sectionTexts.add(processText(section.getSearchableText(), section.getTextBlocks(), section.getHeadline(), sectionNumber, section.getImages()));
sectionNumber.incrementAndGet();
}
for (Header header : classifiedDoc.getHeaders()) {
sectionTexts.add(processText(header.getSearchableText(), header.getTextBlocks(), "Header", sectionNumber, new ArrayList<>()));
sectionNumber.incrementAndGet();
}
for (Footer footer : classifiedDoc.getFooters()) {
sectionTexts.add(processText(footer.getSearchableText(), footer.getTextBlocks(), "Footer", sectionNumber, new ArrayList<>()));
sectionNumber.incrementAndGet();
}
for (UnclassifiedText unclassifiedText : classifiedDoc.getUnclassifiedTexts()) {
sectionTexts.add(processText(unclassifiedText.getSearchableText(), unclassifiedText.getTextBlocks(), "", sectionNumber, new ArrayList<>()));
sectionNumber.incrementAndGet();
}
return sectionTexts;
}
private List<SectionText> processTablePerRow(TablePageBlock table, AtomicInteger sectionNumber) {
List<SectionText> sectionTexts = new ArrayList<>();
boolean hasHeader = hasTableHeader(table);
for (List<Cell> row : table.getRows()) {
List<ClassificationTextBlock> textBlocks = new ArrayList<>();
List<SectionArea> areas = new ArrayList<>();
Map<String, CellValue> tabularData = new HashMap<>();
List<Integer> startOffsets = new ArrayList<>();
int startOffset = 0;
for (int cellNum = 0; cellNum < row.size(); cellNum++) {
Cell cell = row.get(cellNum);
if (CollectionUtils.isEmpty(cell.getTextBlocks())) {
continue;
}
SectionArea sectionArea = getSectionArea(cell);
areas.add(sectionArea);
if (!cell.isHeaderCell()) {
String headerName = getHeaderName(hasHeader, cell, table.getRows().get(0), row.size(), cellNum);
if (headerName != null) {
sectionArea.setHeader(headerName);
tabularData.put(headerName, new CellValue(cell.getTextBlocks(), startOffset));
}
}
textBlocks.addAll(cell.getTextBlocks());
startOffsets.add(startOffset);
startOffset = startOffset + cell.toString().trim().length() + 1;
}
sectionTexts.add(SectionText.builder()
.text(getRowText(textBlocks))
.headline(table.getHeadline())
.sectionNumber(sectionNumber.intValue())
.isTable(true)
.tabularData(tabularData)
.cellStarts(startOffsets)
.textBlocks(textBlocks)
.sectionAreas(areas)
.build());
sectionNumber.incrementAndGet();
}
return sectionTexts;
}
public String getRowText(List<ClassificationTextBlock> rowTextBlocks) {
return SearchableText.buildString(rowTextBlocks.stream().map(textBlock -> textBlock.getSequences()).flatMap(List::stream).collect(Collectors.toList()));
}
private boolean hasTableHeader(TablePageBlock table) {
return table.getRows().stream().anyMatch(row -> row.stream().anyMatch(cell -> !cell.isHeaderCell() && !cell.getHeaderCells().isEmpty()));
}
private SectionArea getSectionArea(Cell cell) {
return new SectionArea(new Point((float) cell.getX(), (float) cell.getY()),
(float) cell.getWidth(),
(float) cell.getHeight(),
cell.getTextBlocks().get(0).getSequences().get(0).getPage(),
null);
}
private String getHeaderName(boolean hasHeader, Cell currentCell, List<Cell> firstRow, int currentRowSize, int cellNum) {
if (hasHeader && !currentCell.getHeaderCells().isEmpty()) {
return buildHeaderName(currentCell.getHeaderCells().get(currentCell.getHeaderCells().size() - 1));
}
if (!hasHeader && currentRowSize == firstRow.size() && currentCell != firstRow.get(cellNum)) {
return buildHeaderName(firstRow.get(cellNum));
}
return null;
}
private String buildHeaderName(Cell cell) {
StringBuilder headerBuilder = new StringBuilder();
cell.getTextBlocks().forEach(textBlock -> headerBuilder.append(textBlock.getText()));
return headerBuilder.toString().replaceAll("\n", "").replaceAll(" ", "").replaceAll("-", "");
}
private SectionText processText(SearchableText searchableText,
List<ClassificationTextBlock> paragraphTextBlocks,
String headline,
AtomicInteger sectionNumber,
List<ClassifiedImage> images) {
SectionText sectionText = new SectionText();
for (ClassificationTextBlock paragraphTextBlock : paragraphTextBlocks) {
SectionArea sectionArea = new SectionArea(new Point(paragraphTextBlock.getMinX(), paragraphTextBlock.getMinY()),
paragraphTextBlock.getWidth(),
paragraphTextBlock.getHeight(),
paragraphTextBlock.getPage(),
null);
sectionText.getSectionAreas().add(sectionArea);
}
sectionText.setText(searchableText.asString());
sectionText.setHeadline(headline);
sectionText.setSectionNumber(sectionNumber.intValue());
sectionText.setTable(false);
sectionText.setImages(images.stream().map(image -> convertImage(image, sectionNumber.intValue(), headline)).collect(Collectors.toSet()));
sectionText.setTextBlocks(paragraphTextBlocks);
return sectionText;
}
private Image convertImage(ClassifiedImage classifiedImage, int sectionNumber, String headline) {
return Image.builder()
.type(classifiedImage.getImageType().equals(ImageType.OTHER) ? "image" : classifiedImage.getImageType().name().toLowerCase(Locale.ROOT))
.position(classifiedImage.getPosition())
.sectionNumber(sectionNumber)
.section(headline)
.page(classifiedImage.getPage())
.hasTransparency(classifiedImage.isHasTransparency())
.build();
}
}

View File

@ -1,138 +0,0 @@
package com.iqser.red.service.redaction.v1.server.redaction.service;
import java.util.List;
import java.util.Set;
import com.iqser.red.service.redaction.v1.server.redaction.model.Entity;
import com.iqser.red.service.redaction.v1.server.redaction.model.SearchableText;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.Dictionary;
import io.micrometer.core.annotation.Timed;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@UtilityClass
public class SurroundingWordsService {
@Timed("redactmanager_addSurroundingText")
public void addSurroundingText(Set<Entity> entities, SearchableText searchableText, Dictionary dictionary, int surroundingWordsOffsetWindow, int numberOfSurroundingWords) {
if (entities.isEmpty()) {
return;
}
try {
for (Entity entity : entities) {
if (dictionary != null && dictionary.isHint(entity.getType())) {
continue;
}
findSurroundingWords(entity, searchableText.asString(), entity.getStart(), entity.getEnd(), surroundingWordsOffsetWindow, numberOfSurroundingWords);
}
} catch (Exception e) {
log.warn("Could not get surrounding text!");
}
}
@Timed("redactmanager_addSurroundingTextTables")
public void addSurroundingText(Set<Entity> entities,
SearchableText searchableText,
Dictionary dictionary,
List<Integer> cellstarts,
int surroundingWordsOffsetWindow,
int numberOfSurroundingWords) {
if (entities.isEmpty()) {
return;
}
try {
String searchableString = searchableText.asString();
if (cellstarts != null) {
for (int i = 0; i < cellstarts.size(); i++) {
int startOffset = cellstarts.get(i);
int endOffset = -1;
if (i + 1 < cellstarts.size()) {
endOffset = cellstarts.get(i + 1);
} else {
endOffset = searchableString.length() - 1;
}
String text = searchableString.substring(startOffset, endOffset);
for (Entity entity : entities) {
if (dictionary != null && dictionary.isHint(entity.getType())) {
continue;
}
if (entity.getStart() >= startOffset && entity.getEnd() <= endOffset) {
int entityStartOffset = entity.getStart() - startOffset;
int entityEndOffset = entity.getEnd() - startOffset;
findSurroundingWords(entity, text, entityStartOffset, entityEndOffset, surroundingWordsOffsetWindow, numberOfSurroundingWords);
}
}
}
}
} catch (Exception e) {
log.warn("Could not get surrounding text!");
}
}
private void findSurroundingWords(Entity entity, String text, int entityStartOffset, int entityEndOffset, int surroundingWordsOffsetWindow, int numberOfSurroundingWords) {
int offsetBefore = entityStartOffset - surroundingWordsOffsetWindow < 0 ? 0 : entityStartOffset - surroundingWordsOffsetWindow;
String textBefore = text.substring(offsetBefore, entityStartOffset);
if (!textBefore.isBlank()) {
String[] wordsBefore = textBefore.split(" ");
int numberOfWordsBefore = wordsBefore.length > numberOfSurroundingWords ? numberOfSurroundingWords : wordsBefore.length;
if (wordsBefore.length > 0) {
entity.setTextBefore(concatWordsBefore(wordsBefore, numberOfWordsBefore, textBefore.endsWith(" ")));
}
}
int endOffset = entityEndOffset + surroundingWordsOffsetWindow > text.length() ? text.length() : entityEndOffset + surroundingWordsOffsetWindow;
String textAfter = text.substring(entityEndOffset, endOffset);
if (!textAfter.isBlank()) {
String[] wordsAfter = textAfter.split(" ");
int numberOfWordsAfter = wordsAfter.length > numberOfSurroundingWords ? numberOfSurroundingWords : wordsAfter.length;
if (wordsAfter.length > 0) {
entity.setTextAfter(concatWordsAfter(wordsAfter, numberOfWordsAfter, textAfter.startsWith(" ")));
}
}
}
private String concatWordsBefore(String[] words, int number, boolean endWithSpace) {
StringBuilder sb = new StringBuilder();
int startNumber = words.length > number ? words.length - number : 0;
for (int i = startNumber; i < words.length; i++) {
sb.append(words[i]).append(" ");
}
String result = sb.toString().trim();
return endWithSpace ? result + " " : result;
}
private String concatWordsAfter(String[] words, int number, boolean startWithSpace) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < number; i++) {
sb.append(words[i]).append(" ");
}
String result = sb.toString().trim();
return startWithSpace ? " " + result : result;
}
}

View File

@ -1,127 +0,0 @@
package com.iqser.red.service.redaction.v1.server.redaction.service.entityredaction;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRedactions;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Engine;
import com.iqser.red.service.redaction.v1.server.client.model.NerEntitiesModel;
import com.iqser.red.service.redaction.v1.server.redaction.model.Entities;
import com.iqser.red.service.redaction.v1.server.redaction.model.Entity;
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityType;
import com.iqser.red.service.redaction.v1.server.redaction.model.SearchableText;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.Dictionary;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.DictionaryModel;
import com.iqser.red.service.redaction.v1.server.redaction.utils.EntitySearchUtils;
import com.iqser.red.service.redaction.v1.server.redaction.utils.FindEntityDetails;
import com.iqser.red.service.redaction.v1.server.settings.RedactionServiceSettings;
import io.micrometer.core.annotation.Timed;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
@RequiredArgsConstructor
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
class EntityFinder {
RedactionServiceSettings redactionServiceSettings;
@Timed("redactmanager_findEntities")
public Entities findEntities(SearchableText searchableText,
String headline,
int sectionNumber,
Dictionary dictionary,
boolean local,
NerEntitiesModel nerEntitiesModel,
List<Integer> cellStarts,
ManualRedactions manualRedactions) {
Set<Entity> found = new HashSet<>();
String searchableString = searchableText.asString();
if (StringUtils.isEmpty(searchableString)) {
return new Entities(new HashSet<>(), new HashSet<>());
}
String lowercaseInputString = searchableString.toLowerCase();
for (DictionaryModel model : dictionary.getDictionaryModels()) {
var searchImplementation = local ? model.getLocalSearch() : model.getEntriesSearch();
var entities = EntitySearchUtils.findEntities(model.isCaseInsensitive() ? lowercaseInputString : searchableString,
searchImplementation,
model,
new FindEntityDetails(model.getType(),
headline,
sectionNumber,
!local,
model.isDossierDictionary(),
local ? Engine.RULE : Engine.DICTIONARY,
local ? EntityType.RECOMMENDATION : EntityType.ENTITY)).stream().filter(e -> !e.isFalsePositive()).collect(Collectors.toSet());
EntitySearchUtils.addOrAddEngine(found, entities);
}
Set<Entity> nerFound = new HashSet<>();
if (!local) {
nerFound.addAll(getNerValues(sectionNumber, nerEntitiesModel, cellStarts, headline));
}
var cleared = EntitySearchUtils.clearAndFindPositions(found, searchableText, dictionary, manualRedactions);
return new Entities(cleared.stream().filter(e -> !e.isFalsePositive()).collect(Collectors.toSet()), nerFound);
}
private Set<Entity> getNerValues(int sectionNumber, NerEntitiesModel nerEntitiesModel, List<Integer> cellStarts, String headline) {
Set<Entity> entities = new HashSet<>();
if (redactionServiceSettings.isNerServiceEnabled() && nerEntitiesModel.getData().containsKey(sectionNumber)) {
nerEntitiesModel.getData().get(sectionNumber).forEach(res -> {
if (cellStarts == null || cellStarts.isEmpty()) {
entities.add(new Entity(res.getValue(),
res.getType(),
res.getStartOffset(),
res.getEndOffset(),
headline,
sectionNumber,
false,
false,
Engine.NER,
EntityType.RECOMMENDATION));
} else {
boolean intersectsCellStart = false;
for (Integer cellStart : cellStarts) {
if (res.getStartOffset() < cellStart && cellStart < res.getEndOffset()) {
intersectsCellStart = true;
break;
}
}
if (!intersectsCellStart) {
entities.add(new Entity(res.getValue(),
res.getType(),
res.getStartOffset(),
res.getEndOffset(),
headline,
sectionNumber,
false,
false,
Engine.NER,
EntityType.RECOMMENDATION));
}
}
});
}
return entities;
}
}

View File

@ -1,385 +0,0 @@
package com.iqser.red.service.redaction.v1.server.redaction.utils;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.AnnotationStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRedactions;
import com.iqser.red.service.redaction.v1.server.redaction.model.Entity;
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityPositionSequence;
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityType;
import com.iqser.red.service.redaction.v1.server.redaction.model.Image;
import com.iqser.red.service.redaction.v1.server.redaction.model.SearchableText;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.Dictionary;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.DictionaryModel;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.SearchImplementation;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@UtilityClass
@SuppressWarnings("PMD")
public final class EntitySearchUtils {
public boolean sectionContainsAny(String sectionText, SearchImplementation searchImplementation) {
return searchImplementation.atLeastOneMatches(sectionText);
}
public void removeFalsePositives(Set<Entity> found, String inputString, DictionaryModel type, FindEntityDetails details) {
Set<Entity> falsePositives = find(inputString, type.getFalsePositiveSearch(), details.withEntityType(EntityType.FALSE_POSITIVE));
markFalsePositives(found, falsePositives);
found.removeIf(Entity::isFalsePositive);
}
public Set<Entity> findEntities(String inputString, SearchImplementation searchImplementation, DictionaryModel type, FindEntityDetails details) {
Set<Entity> found = find(inputString, searchImplementation, details);
if (details.getEntityType() == EntityType.RECOMMENDATION) {
Set<Entity> falseRecommendations = find(inputString, type.getFalseRecommendationsSearch(), details.withEntityType(EntityType.FALSE_RECOMMENDATION));
markFalsePositives(found, falseRecommendations);
} else {
Set<Entity> falsePositives = find(inputString, type.getFalsePositiveSearch(), details.withEntityType(EntityType.FALSE_POSITIVE));
markFalsePositives(found, falsePositives);
}
return found;
}
public Set<Entity> find(String inputString, SearchImplementation searchImplementation, FindEntityDetails findEntityDetails) {
Set<Entity> entities = new HashSet<>();
searchImplementation.getMatches(inputString).forEach(match -> validateAndAddEntity(entities, findEntityDetails, inputString, match.startIndex(), match.endIndex()));
return entities;
}
private void validateAndAddEntity(Set<Entity> entities, FindEntityDetails findEntityDetails, String inputString, int startIndex, int stopIndex) {
if ((startIndex == 0 || SeparatorUtils.isSeparator(inputString.charAt(startIndex - 1)) || SeparatorUtils.isSeparator(inputString.charAt(startIndex))) && (stopIndex == inputString.length() || SeparatorUtils.isSeparator(
inputString.charAt(stopIndex)) || SeparatorUtils.isSeparator(inputString.charAt(stopIndex - 1)))) {
entities.add(new Entity(inputString.substring(startIndex, stopIndex),
findEntityDetails.getType(),
startIndex,
stopIndex,
findEntityDetails.getHeadline(),
findEntityDetails.getSectionNumber(),
findEntityDetails.isDictionaryEntry(),
findEntityDetails.isDossierDictionary(),
findEntityDetails.getEngine(),
findEntityDetails.getEntityType()));
}
}
public Set<Entity> clearAndFindPositions(Set<Entity> entities, SearchableText text, Dictionary dictionary, ManualRedactions manualRedactions) {
Map<String, List<Entity>> entitiesByWord = new HashMap<>();
for (Entity entity : entities) {
entitiesByWord.computeIfAbsent(entity.getWord(), (x) -> new ArrayList<>()).add(entity);
}
for (String word : entitiesByWord.keySet()) {
List<Entity> orderedEntities = entitiesByWord.get(word).stream().sorted(Comparator.comparing(Entity::getStart)).collect(Collectors.toList());
Entity firstEntity = orderedEntities.get(0);
List<EntityPositionSequence> positionSequences = text.getSequences(word.trim(),
dictionary == null || dictionary.isCaseInsensitiveDictionary(firstEntity.getType()),
firstEntity.getTargetSequences());
Map<String, List<EntityPositionSequence>> multipartSequenceParts = new HashMap<>();
Iterator<EntityPositionSequence> itty = positionSequences.iterator();
while (itty.hasNext()) {
var positionSequence = itty.next();
if (positionSequence.getId().contains("-")) {
multipartSequenceParts.computeIfAbsent(positionSequence.getId().split("-")[0], (x) -> new ArrayList<>()).add(positionSequence);
itty.remove();
}
}
for (int i = 0; i <= orderedEntities.size() - 1; i++) {
try {
List<EntityPositionSequence> toAdd = new ArrayList<>();
var currentSeq = positionSequences.get(i);
toAdd.add(currentSeq);
if (multipartSequenceParts.containsKey(currentSeq.getId())) {
toAdd.addAll(multipartSequenceParts.get(currentSeq.getId()));
}
orderedEntities.get(i).setPositionSequences(toAdd);
} catch (Exception e) {
//log.warn("Mismatch between EntityPositionSequence and found Entity!");
}
}
}
if (manualRedactions != null && manualRedactions.getResizeRedactions() != null && !manualRedactions.getResizeRedactions().isEmpty()) {
applyResizeRedactions(entities, manualRedactions);
}
removeEntitiesContainedInLarger(entities);
return entities;
}
private void applyResizeRedactions(Set<Entity> entitiesWithPositions, ManualRedactions manualRedactions) {
if (manualRedactions == null || manualRedactions.getResizeRedactions() == null || manualRedactions.getResizeRedactions().isEmpty()) {
return;
}
entitiesWithPositions.forEach(e -> e.getPositionSequences()
.forEach(pos -> manualRedactions.getResizeRedactions().stream().filter(resize -> resize.getStatus().equals(AnnotationStatus.APPROVED)).forEach(resize -> {
if (resize.getAnnotationId().equals(pos.getId())) {
if (resize.getValue().length() < e.getWord().length() && e.getWord().contains(resize.getValue())) {
int start = e.getWord().indexOf(resize.getValue());
e.setStart(e.getStart() + start);
e.setEnd(e.getStart() + resize.getValue().length());
e.setResized(true);
e.setWord(resize.getValue());
} else if (resize.getValue().length() > e.getWord().length() && resize.getValue().contains(e.getWord())) {
int start = resize.getValue().indexOf(e.getWord());
e.setStart(e.getStart() - start);
e.setEnd(e.getStart() + resize.getValue().length());
e.setResized(true);
e.setWord(resize.getValue());
}
}
})));
}
public void markFalsePositives(Set<Entity> entities, Set<Entity> falsePositives) {
List<Entity> wordsToRemove = new ArrayList<>();
for (Entity word : falsePositives) {
for (Entity inner : entities) {
if (inner.getWord().length() < word.getWord()
.length() && inner.getStart() >= word.getStart() && inner.getEnd() <= word.getEnd() && word != inner && word.getSectionNumber() == inner.getSectionNumber()) {
wordsToRemove.add(inner);
}
}
}
wordsToRemove.forEach(e -> e.setFalsePositive(true));
for (var entity : entities) {
if (falsePositives.contains(entity)) {
entity.setFalsePositive(true);
}
}
}
public void removeEntitiesContainedInLarger(Set<Entity> entities) {
List<Entity> wordsToRemove = new ArrayList<>();
for (Entity outer : entities) {
for (Entity inner : entities) {
// // skip cross-type false positives
// if(word.getEntityType() == EntityType.FALSE_POSITIVE && inner.getEntityType() == EntityType.ENTITY && !inner.getType().equals(word.getType())){
// continue;
// }
// // skip cross-type false reccommendations
// if(word.getEntityType() == EntityType.FALSE_RECOMMENDATION && inner.getEntityType() == EntityType.RECOMMENDATION && !inner.getType().equals(word.getType())){
// continue;
// }
if ((inner.isSkipRemoveEntitiesContainedInLarger() || outer.isSkipRemoveEntitiesContainedInLarger()) && !inner.getType().equals(outer.getType())) {
continue;
}
if (inner.getWord().length() < outer.getWord()
.length() && inner.getStart() >= outer.getStart() && inner.getEnd() <= outer.getEnd() && outer != inner && outer.getSectionNumber() == inner.getSectionNumber()) {
if (outer.getEntityType().equals(EntityType.RECOMMENDATION) && inner.getEntityType().equals(EntityType.ENTITY)) {
wordsToRemove.add(outer);
} else if (!(inner.getEntityType() == EntityType.FALSE_RECOMMENDATION && outer.getEntityType() == EntityType.ENTITY || inner.getEntityType() == EntityType.ENTITY && outer.getEntityType() == EntityType.FALSE_RECOMMENDATION)) {
if (inner.isResized()) {
wordsToRemove.add(outer);
} else {
wordsToRemove.add(inner);
}
}
}
}
}
wordsToRemove.stream()
.filter(e -> !e.getEntityType().equals(EntityType.FALSE_POSITIVE) && !e.getEntityType().equals(EntityType.FALSE_RECOMMENDATION))
.forEach(entities::remove);
}
public static void removeEntitiesContainedInRedactedLogos(Map<Integer, Set<Image>> imagesPerPage, Map<Integer, List<Entity>> entitiesPerPage) {
if (entitiesPerPage != null && imagesPerPage != null) {
for (int page : imagesPerPage.keySet()) {
Set<Entity> removeEntitiesFromPage = new HashSet<>();
if (!entitiesPerPage.containsKey(page)) {
continue;
}
imagesPerPage.get(page).forEach(image -> {
if (image.isRedaction()) {
entitiesPerPage.get(page).forEach(entity -> {
if (isImageOverlappingEntity(image, entity)) {
log.info("Logo overlaps entity and entity will be removed");
removeEntitiesFromPage.add(entity);
}
});
}
});
entitiesPerPage.get(page).removeAll(removeEntitiesFromPage);
}
}
}
public void addEntitiesWithHigherRank(Set<Entity> entities, Set<Entity> found, Dictionary dictionary) {
found.forEach(f -> addEntitiesWithHigherRank(entities, f, dictionary));
}
public void addEntitiesWithHigherRank(Set<Entity> entities, Entity found, Dictionary dictionary) {
if (entities.contains(found)) {
Optional<Entity> existingOptional = entities.stream().filter(entity -> entity.equals(found)).findFirst();
if (existingOptional.isEmpty()) {
return;
}
var existing = existingOptional.get();
if (existing.getType().equals(found.getType())) {
existing.getEngines().addAll(found.getEngines());
existing.setLegalBasis(found.getLegalBasis());
existing.setMatchedRule(found.getMatchedRule());
existing.setRedactionReason(found.getRedactionReason());
if (isOneARecommendationAndTheOtherEntity(found, existing)) {
existing.setEntityType(EntityType.ENTITY);
if (found.isRedaction()) {
existing.setRedaction(true);
}
}
} else if (dictionary.getDictionaryRank(existing.getType()) <= dictionary.getDictionaryRank(found.getType()) &&
!(isOneARecommendationAndTheOtherEntity(found, existing) && existing.isRedaction() && found.isRedaction()) ) {
entities.remove(found);
entities.add(found);
}
} else {
entities.add(found);
}
}
private boolean isOneARecommendationAndTheOtherEntity(Entity entityOne, Entity entityTwo) {
var entityTypeOne = entityOne.getEntityType();
var entityTypeTwo = entityTwo.getEntityType();
return entityTypeTwo.equals(EntityType.RECOMMENDATION) && entityTypeOne.equals(EntityType.ENTITY)
|| entityTypeTwo.equals(EntityType.ENTITY) && entityTypeOne.equals(EntityType.RECOMMENDATION);
}
public void addEntitiesIgnoreRank(Set<Entity> entities, Set<Entity> found) {
// HashSet keeps old value but we want the new.
entities.removeAll(found);
entities.addAll(found);
}
private boolean isImageOverlappingEntity(Image image, Entity entity) {
return image.getPosition() != null && entity.getPositionSequences() != null && entity.getPositionSequences().get(0) != null && entity.getPositionSequences()
.get(0)
.getSequences() != null && entity.getPositionSequences().get(0).getSequences().get(0) != null && image.getPosition().getX() < entity.getPositionSequences()
.get(0)
.getSequences()
.get(0)
.getMinXDirAdj() && image.getPosition().getX() + image.getPosition().getWidth() > entity.getPositionSequences()
.get(0)
.getSequences()
.get(0)
.getMaxXDirAdj() && image.getPosition().getY() < entity.getPositionSequences().get(0).getSequences().get(0).getMinYDirAdj() && image.getPosition()
.getY() + image.getPosition().getHeight() > entity.getPositionSequences().get(0).getSequences().get(0).getMaxYDirAdj();
}
public Set<Entity> findNonOverlappingMatchEntities(Set<Entity> existingEntities, Set<Entity> foundEntities) {
Set<Entity> result = new HashSet<>();
if (existingEntities != null && foundEntities != null) {
for (Entity foundEntity : foundEntities) {
if (!overlaps(existingEntities, foundEntity)) {
result.add(foundEntity);
}
}
}
return result;
}
public void addOrAddEngine(Set<Entity> existing, Set<Entity> toBeAdded) {
for (Entity toAdd : toBeAdded) {
if (existing.contains(toAdd)) {
Optional<Entity> existingOptional = existing.stream().filter(entity -> entity.equals(toAdd)).findFirst();
if (existingOptional.isEmpty()) {
return;
}
var existingEntity = existingOptional.get();
existingEntity.getEngines().addAll(toAdd.getEngines());
existingEntity.setLegalBasis(toAdd.getLegalBasis());
existingEntity.setMatchedRule(toAdd.getMatchedRule());
existingEntity.setRedactionReason(toAdd.getRedactionReason());
} else {
existing.add(toAdd);
}
}
}
/*
* returns true if the found entity overlaps with an existing entity in a way
* that neither entity is a subset of the other
*/
private boolean overlaps(Set<Entity> existingEntities, Entity found) {
for (Entity existing : existingEntities) {
// skip if either start or end is equal
if (existing.getStart().equals(found.getStart()) || existing.getEnd().equals(found.getEnd())) {
continue;
}
for (int i = existing.getStart(); i <= existing.getEnd(); i++) {
for (int j = found.getStart(); j <= found.getEnd(); j++) {
if (i == j) {
return true;
}
}
}
}
return false;
}
}

View File

@ -1,28 +0,0 @@
package com.iqser.red.service.redaction.v1.server.redaction.utils;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Engine;
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityType;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class FindEntityDetails {
private String type;
private String headline;
private int sectionNumber;
private boolean isDictionaryEntry;
private boolean isDossierDictionary;
private Engine engine;
private EntityType entityType;
public FindEntityDetails withEntityType(EntityType entityType) {
this.entityType = entityType;
return this;
}
}

View File

@ -7,9 +7,7 @@ import java.util.Set;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextPositionSequence;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.PageNode;
import com.iqser.red.service.redaction.v1.server.redaction.model.RedRectangle2D;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Page;
import lombok.experimental.UtilityClass;
@ -19,18 +17,7 @@ public final class IdBuilder {
private final HashFunction hashFunction = Hashing.murmur3_128();
public String buildId(List<TextPositionSequence> crossSequenceParts) {
StringBuilder sb = new StringBuilder();
crossSequenceParts.forEach(sequencePart -> sequencePart.getTextPositions().forEach(textPosition -> {
sb.append(textPosition.getTextMatrix()).append(sequencePart.getPage());
}));
return hashFunction.hashString(sb.toString(), StandardCharsets.UTF_8).toString();
}
public String buildId(Set<PageNode> pages, List<Rectangle2D> rectanglesPerLine) {
public String buildId(Set<Page> pages, List<Rectangle2D> rectanglesPerLine) {
StringBuilder sb = new StringBuilder();
pages.forEach(pageNode -> sb.append(pageNode.getNumber()));
@ -39,24 +26,4 @@ public final class IdBuilder {
return hashFunction.hashString(sb.toString(), StandardCharsets.UTF_8).toString();
}
public String buildId(Rectangle2D rectangle2D, int page) {
return hashFunction.hashString("x" + rectangle2D.getX() + "y" + rectangle2D.getY() + "h" + rectangle2D.getHeight() + "w" + rectangle2D.getWidth() + "p" + page,
StandardCharsets.UTF_8).toString();
}
public String buildId(RedRectangle2D rectangle2D, int page) {
return hashFunction.hashString("x" + rectangle2D.getX() + "y" + rectangle2D.getY() + "h" + rectangle2D.getHeight() + "w" + rectangle2D.getWidth() + "p" + page,
StandardCharsets.UTF_8).toString();
}
public String getTypeId(String type, String dossierTemplateId, String dossierId) {
return type + ":" + dossierTemplateId + (dossierId != null ? ":" + dossierId : "");
}
}

View File

@ -1,56 +0,0 @@
package com.iqser.red.service.redaction.v1.server.redaction.utils;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import com.iqser.red.service.redaction.v1.server.redaction.model.OffsetString;
import lombok.experimental.UtilityClass;
@UtilityClass
public final class OffsetStringUtils {
/**
* Same logic as in StringUtils.redactBetween, but returns a list of object with offsets insteadof on the Strings only.
*
* @param str the String containing the substrings, null returns null, empty returns empty
* @param open the String identifying the startIdx of the substring, empty returns null
* @param close the String identifying the endIdx of the substring, empty returns null
* @return a list of Strings with their offsets
*/
public List<OffsetString> substringsBetween(final String str, final String open, final String close) {
if (str == null || StringUtils.isEmpty(open) || StringUtils.isEmpty(close)) {
return null;
}
final int strLen = str.length();
if (strLen == 0) {
return new ArrayList<>();
}
final int closeLen = close.length();
final int openLen = open.length();
final List<OffsetString> list = new ArrayList<>();
int pos = 0;
while (pos < strLen - closeLen) {
int start = str.indexOf(open, pos);
if (start < 0) {
break;
}
start += openLen;
final int end = str.indexOf(close, start);
if (end < 0) {
break;
}
list.add(new OffsetString(str.substring(start, end), start, end));
pos = end + closeLen;
}
if (list.isEmpty()) {
return null;
}
return list;
}
}

View File

@ -10,7 +10,6 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlo
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.section.SectionGrid;
import com.iqser.red.service.redaction.v1.server.client.model.NerEntitiesModel;
import com.iqser.red.service.redaction.v1.server.exception.NotFoundException;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.Text;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.DocumentData;
import com.iqser.red.service.redaction.v1.server.multitenancy.TenantContext;
import com.iqser.red.storage.commons.exception.StorageObjectDoesNotExist;
@ -72,32 +71,20 @@ public class RedactionStorageService {
try {
return storageService.readJSONObject(TenantContext.getTenantId(), StorageIdUtils.getStorageId(dossierId, fileId, FileType.REDACTION_LOG), RedactionLog.class);
} catch (StorageObjectDoesNotExist e) {
log.debug("Text not available.");
log.debug("RedactionLog not available.");
return null;
}
}
@Timed("redactmanager_getText")
public Text getText(String dossierId, String fileId) {
try {
return storageService.readJSONObject(TenantContext.getTenantId(), StorageIdUtils.getStorageId(dossierId, fileId, FileType.TEXT), Text.class);
} catch (StorageObjectDoesNotExist e) {
log.debug("Text not available.");
return null;
}
}
@Timed("redactmanager_getDocumentGraph")
public DocumentData getDocumentData(String dossierId, String fileId) {
try {
return storageService.readJSONObject(TenantContext.getTenantId(), StorageIdUtils.getStorageId(dossierId, fileId, FileType.TEXT), DocumentData.class);
} catch (StorageObjectDoesNotExist e) {
log.debug("Text not available.");
log.debug("DocumentData not available.");
return null;
}
}

View File

@ -12,10 +12,10 @@ import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree;
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.PageNode;
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;
@ -30,13 +30,13 @@ import lombok.experimental.UtilityClass;
@UtilityClass
public class PdfDraw {
public static void drawDocumentGraph(PDDocument document, DocumentGraph documentGraph) {
public static void drawDocumentGraph(PDDocument document, Document documentGraph) {
documentGraph.getTableOfContents().streamAllEntriesInOrder().forEach(entry -> drawNode(document, entry));
documentGraph.getDocumentTree().streamAllEntriesInOrder().forEach(entry -> drawNode(document, entry));
}
public static void drawNode(PDDocument document, TableOfContents.Entry entry) {
public static void drawNode(PDDocument document, DocumentTree.Entry entry) {
Options options = buildStandardOptionsForNodes(entry);
@ -124,7 +124,7 @@ public class PdfDraw {
}
private static Options buildStandardOptionsForNodes(TableOfContents.Entry entry) {
private static Options buildStandardOptionsForNodes(DocumentTree.Entry entry) {
return Options.builder().stroke(true).strokeColor(switch (entry.getType()) {
case DOCUMENT -> Color.LIGHT_GRAY;
@ -139,9 +139,9 @@ public class PdfDraw {
}
private static void drawBBoxAndLabelAndNumberOnPage(PDDocument document, TableOfContents.Entry entry, Options options) {
private static void drawBBoxAndLabelAndNumberOnPage(PDDocument document, DocumentTree.Entry entry, Options options) {
Map<PageNode, Rectangle2D> rectanglesPerPage = entry.getNode().getBBox();
Map<Page, Rectangle2D> rectanglesPerPage = entry.getNode().getBBox();
rectanglesPerPage.forEach((page, rectangle2D) -> {
Rectangle2D paddedRectangle2D = rectangle2D;
if (entry.getType() == NodeType.SECTION) {
@ -153,7 +153,7 @@ public class PdfDraw {
}
private static String buildString(TableOfContents.Entry entry) {
private static String buildString(DocumentTree.Entry entry) {
return entry.getNode().getNumberOnPage() + ": " + entry.getTocId() + ": " + entry.getType().toString();
}

View File

@ -11,9 +11,9 @@ 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.Document;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Page;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Section;
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;
@ -30,14 +30,14 @@ public class PdfVisualisationService {
private static final boolean DRAW_POSITIONS = false;
public void visualizeParagraphs(Document classifiedDoc, PDDocument document) throws IOException {
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 (Section paragraph : classifiedDoc.getSections()) {
for (ClassificationSection paragraph : classifiedDoc.getSections()) {
for (int i = 0; i <= paragraph.getPageBlocks().size() - 1; i++) {
@ -62,11 +62,11 @@ public class PdfVisualisationService {
}
public void visualizeClassifications(Document classifiedDoc, PDDocument document) throws IOException {
public void visualizeClassifications(ClassificationDocument classifiedDoc, PDDocument document) throws IOException {
for (int page = 1; page <= document.getNumberOfPages(); page++) {
Page analyzedPage = classifiedDoc.getPages().get(page - 1);
ClassificationPage analyzedPage = classifiedDoc.getPages().get(page - 1);
PDPage pdPage = document.getPage(page - 1);
PDPageContentStream contentStream = new PDPageContentStream(document, pdPage, PDPageContentStream.AppendMode.APPEND, true);

View File

@ -36,8 +36,8 @@ import com.iqser.red.service.redaction.v1.server.client.LegalBasisClient;
import com.iqser.red.service.redaction.v1.server.client.RulesClient;
import com.iqser.red.service.redaction.v1.server.controller.RedactionController;
import com.iqser.red.service.redaction.v1.server.multitenancy.TenantContext;
import com.iqser.red.service.redaction.v1.server.redaction.service.AnalyzeService;
import com.iqser.red.service.redaction.v1.server.redaction.service.ManualRedactionSurroundingTextService;
import com.iqser.red.service.redaction.v1.server.redaction.service.analyze.AnalyzeService;
import com.iqser.red.service.redaction.v1.server.redaction.utils.ResourceLoader;
import com.iqser.red.service.redaction.v1.server.redaction.utils.TextNormalizationUtilities;
import com.iqser.red.service.redaction.v1.server.storage.RedactionStorageService;
@ -240,7 +240,7 @@ public abstract class AbstractRedactionIntegrationTest {
URL resource = ResourceLoader.class.getClassLoader().getResource(path);
if (resource == null) {
throw new IllegalArgumentException("could not load classpath resource: drools/prod_rules_new.drl");
throw new IllegalArgumentException("could not load classpath resource:" + path);
}
List<String> stringList = Files.readAllLines(new File(resource.getPath()).toPath());
return String.join("\n", stringList);

View File

@ -32,17 +32,17 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.FileAttribu
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRedactions;
import com.iqser.red.service.persistence.service.v1.api.shared.model.common.JSONPrimitive;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.Type;
import com.iqser.red.service.redaction.v1.server.document.graph.BuildDocumentGraphIntegrationTest;
import com.iqser.red.service.redaction.v1.server.document.graph.BuildDocumentIntegrationTest;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.EntityType;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.PageNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SectionNode;
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.Page;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Section;
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.multitenancy.TenantContext;
import com.iqser.red.service.redaction.v1.server.redaction.adapter.NerEntities;
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityType;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.Dictionary;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.DictionaryModel;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.SearchImplementation;
@ -52,8 +52,8 @@ import com.iqser.red.service.redaction.v1.server.visualization.service.PdfDraw;
import lombok.SneakyThrows;
@Import(DocumentGraphPerformanceIntegrationTest.TestConfiguration.class)
public class DocumentGraphPerformanceIntegrationTest extends BuildDocumentGraphIntegrationTest {
@Import(DocumentPerformanceIntegrationTest.TestConfiguration.class)
public class DocumentPerformanceIntegrationTest extends BuildDocumentIntegrationTest {
private static final String RULES = "drools/rules.drl";
@ -71,7 +71,7 @@ public class DocumentGraphPerformanceIntegrationTest extends BuildDocumentGraphI
private KieContainer kieContainer;
@Configuration
@Import(BuildDocumentGraphIntegrationTest.TestConfiguration.class)
@Import(BuildDocumentIntegrationTest.TestConfiguration.class)
public static class TestConfiguration {
@Bean
@ -128,7 +128,7 @@ public class DocumentGraphPerformanceIntegrationTest extends BuildDocumentGraphI
public void testDroolsOnDocumentGraph() {
String filename = "files/Metolachlor/S-Metolachlor_RAR_01_Volume_1_2018-09-06";
DocumentGraph document = buildGraph(filename);
Document document = buildGraph(filename);
dictionaryService.updateDictionary(TEST_DOSSIER_TEMPLATE_ID, TEST_DOSSIER_ID);
Dictionary dictionary = dictionaryService.getDeepCopyDictionary(TEST_DOSSIER_TEMPLATE_ID, TEST_DOSSIER_ID);
@ -168,7 +168,7 @@ public class DocumentGraphPerformanceIntegrationTest extends BuildDocumentGraphI
int n = 10000;
String filename = "files/Metolachlor/S-Metolachlor_RAR_01_Volume_1_2018-09-06";
DocumentGraph document = buildGraph(filename);
Document document = buildGraph(filename);
var start = System.currentTimeMillis();
for (int i = 0; i < n; i++) {
TextBlock textBlock = document.getTextBlock();
@ -176,7 +176,7 @@ public class DocumentGraphPerformanceIntegrationTest extends BuildDocumentGraphI
float durationMillis = ((float) (System.currentTimeMillis() - start));
System.out.printf("%d calls of buildTextBlock() on document took %f s, average is %f ms\n", n, durationMillis / 1000, durationMillis / n);
SectionNode section = document.getMainSections().get(8);
Section section = document.getMainSections().get(8);
start = System.currentTimeMillis();
for (int i = 0; i < n; i++) {
section.buildTextBlock();
@ -184,7 +184,7 @@ public class DocumentGraphPerformanceIntegrationTest extends BuildDocumentGraphI
durationMillis = ((float) (System.currentTimeMillis() - start));
System.out.printf("%d calls of buildTextBlock() on section took %f s, average is %f ms\n", n, durationMillis / 1000, durationMillis / n);
SemanticNode paragraph = document.getTableOfContents().getEntryById(List.of(8, 1)).getNode();
SemanticNode paragraph = document.getDocumentTree().getEntryById(List.of(8, 1)).getNode();
start = System.currentTimeMillis();
for (int i = 0; i < n; i++) {
paragraph.buildTextBlock();
@ -199,7 +199,7 @@ public class DocumentGraphPerformanceIntegrationTest extends BuildDocumentGraphI
public void testDictionarySearchOnDocumentGraph() {
String filename = "files/Metolachlor/S-Metolachlor_RAR_01_Volume_1_2018-09-06";
DocumentGraph document = buildGraph(filename);
Document document = buildGraph(filename);
dictionaryService.updateDictionary(TEST_DOSSIER_TEMPLATE_ID, TEST_DOSSIER_ID);
Dictionary dictionary = dictionaryService.getDeepCopyDictionary(TEST_DOSSIER_TEMPLATE_ID, TEST_DOSSIER_ID);
@ -227,7 +227,7 @@ public class DocumentGraphPerformanceIntegrationTest extends BuildDocumentGraphI
totalSearchTime += searchTime;
var insertStart = System.currentTimeMillis();
DocumentGraph finalDocument = document;
Document finalDocument = document;
foundEntities.forEach(entity -> entityCreationService.addEntityToGraph(entity, finalDocument));
var insertTime = System.currentTimeMillis() - insertStart;
totalInsertTime += insertTime;
@ -245,20 +245,20 @@ public class DocumentGraphPerformanceIntegrationTest extends BuildDocumentGraphI
}
private static void drawAllEntities(String filename, DocumentGraph document) throws IOException {
private static void drawAllEntities(String filename, Document document) throws IOException {
var tmpFileName = "/tmp/" + filename.split("/")[2] + "_ENTITY_BBOX.pdf";
var fileResource = new ClassPathResource(filename + ".pdf");
try (var fileStream = fileResource.getInputStream()) {
PDDocument pdDocument = PDDocument.load(fileStream);
for (PageNode page : document.getPages()) {
for (Page page : document.getPages()) {
List<Rectangle2D> entityPositionsOnPage = page.getEntities()
.stream()
.filter(entityNode -> !entityNode.isRemoved())
.filter(RedactionEntity::isRedaction)
.flatMap(entityNode -> entityNode.getRedactionPositionsPerPage().stream())
.filter(entityPosition -> entityPosition.getPageNode().equals(page))
.filter(entityPosition -> entityPosition.getPage().equals(page))
.flatMap(entityPosition -> entityPosition.getRectanglePerLine().stream())
.toList();
@ -266,13 +266,13 @@ public class DocumentGraphPerformanceIntegrationTest extends BuildDocumentGraphI
drawRectangle2DList(pdDocument, page.getNumber(), entityPositionsOnPage, options);
}
for (PageNode page : document.getPages()) {
for (Page page : document.getPages()) {
List<Rectangle2D> entityPositionsOnPage = page.getEntities()
.stream()
.filter(entityNode -> !entityNode.isRemoved())
.filter(entityNode -> !entityNode.isRedaction())
.flatMap(entityNode -> entityNode.getRedactionPositionsPerPage().stream())
.filter(entityPosition -> entityPosition.getPageNode().equals(page))
.filter(entityPosition -> entityPosition.getPage().equals(page))
.flatMap(entityPosition -> entityPosition.getRectanglePerLine().stream())
.toList();
@ -286,13 +286,13 @@ public class DocumentGraphPerformanceIntegrationTest extends BuildDocumentGraphI
}
private void findEntitiesWithSearchImplementation(DocumentGraph documentGraph,
private void findEntitiesWithSearchImplementation(Document document,
SearchImplementation searchImplementation,
EntityType entityType,
List<RedactionEntity> foundEntities,
String type) {
TextBlock textBlock = documentGraph.getTextBlock();
TextBlock textBlock = document.getTextBlock();
searchImplementation.getBoundaries(textBlock, textBlock.getBoundary())
.stream()
.filter(boundary -> boundaryIsSurroundedBySeparators(textBlock, boundary))

View File

@ -34,7 +34,7 @@ import com.iqser.red.service.redaction.v1.model.StructureAnalyzeRequest;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.mapper.DocumentGraphMapper;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SemanticNode;
import com.iqser.red.service.redaction.v1.server.multitenancy.TenantContext;
import com.iqser.red.service.redaction.v1.server.redaction.service.analyze.AnalyzeService;
import com.iqser.red.service.redaction.v1.server.redaction.service.AnalyzeService;
import com.iqser.red.service.redaction.v1.server.storage.RedactionStorageService;
import com.iqser.red.storage.commons.StorageAutoConfiguration;
import com.iqser.red.storage.commons.service.StorageService;

View File

@ -65,13 +65,13 @@ import com.iqser.red.service.redaction.v1.server.annotate.AnnotateRequest;
import com.iqser.red.service.redaction.v1.server.annotate.AnnotateResponse;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.mapper.DocumentGraphMapper;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.EntityType;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SectionNode;
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.Section;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.services.EntityCreationService;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RectangleMapper;
import com.iqser.red.service.redaction.v1.server.multitenancy.TenantContext;
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityType;
import com.iqser.red.service.redaction.v1.server.redaction.utils.OsUtils;
import com.iqser.red.service.redaction.v1.server.storage.RedactionStorageService;
import com.iqser.red.storage.commons.StorageAutoConfiguration;
@ -218,7 +218,7 @@ public class RedactionIntegrationTest extends AbstractRedactionIntegrationTest {
@Test
public void titleExtraction() throws IOException {
AnalyzeRequest request = uploadFileToStorage("files/Metolachlor/S-Metolachlor_RAR_01_Volume_1_2018-09-06.pdf");
AnalyzeRequest request = uploadFileToStorage("files/new/crafted document.pdf");
System.out.println("Start Full integration test");
analyzeService.analyzeDocumentStructure(new StructureAnalyzeRequest(request.getDossierId(), request.getFileId()));
System.out.println("Finished structure analysis");
@ -338,7 +338,8 @@ public class RedactionIntegrationTest extends AbstractRedactionIntegrationTest {
@Test
@Disabled // TODO: this is already broken on master, no idea how to fix it. Most likely more responses need to be stubbed.
@Disabled
// TODO: this is already broken on master, no idea how to fix it. Most likely more responses need to be stubbed.
public void redactionTestSeparatedRedaction() throws IOException {
String fileName = "scanned/VV-380943_page38.pdf";
@ -366,18 +367,18 @@ public class RedactionIntegrationTest extends AbstractRedactionIntegrationTest {
System.out.println("first analysis duration: " + (end - start));
try (FileOutputStream fileOutputStream = new FileOutputStream(OsUtils.getTemporaryDirectory() + "/Test.json")) {
fileOutputStream.write(objectMapper.writeValueAsBytes(redactionStorageService.getText(TEST_DOSSIER_ID, TEST_FILE_ID)));
fileOutputStream.write(objectMapper.writeValueAsBytes(redactionStorageService.getDocumentData(TEST_DOSSIER_ID, TEST_FILE_ID)));
}
int correctFound = 0;
loop:
for (RedactionLogEntry redactionLogEntry : redactionLog.getRedactionLogEntry()) {
for (SectionNode section : documentGraph.getMainSections()) {
for (Section section : documentGraph.getMainSections()) {
if (redactionLogEntry.isImage()) {
correctFound++;
continue loop;
}
if (redactionLogEntry.getSectionNumber() == section.getTocId().get(0)) {
if (redactionLogEntry.getSectionNumber() == section.getTreeId().get(0)) {
String value = section.buildTextBlock().subSequence(new Boundary(redactionLogEntry.getStartOffset(), redactionLogEntry.getEndOffset())).toString();
if (redactionLogEntry.getValue().equalsIgnoreCase(value)) {
correctFound++;
@ -520,7 +521,7 @@ public class RedactionIntegrationTest extends AbstractRedactionIntegrationTest {
System.out.println("first analysis duration: " + (end - start));
try (FileOutputStream fileOutputStream = new FileOutputStream(OsUtils.getTemporaryDirectory() + "/Test.json")) {
fileOutputStream.write(objectMapper.writeValueAsBytes(redactionStorageService.getText(TEST_DOSSIER_ID, TEST_FILE_ID)));
fileOutputStream.write(objectMapper.writeValueAsBytes(redactionStorageService.getDocumentData(TEST_DOSSIER_ID, TEST_FILE_ID)));
}
List<String> valuesInDocument = redactionLog.getRedactionLogEntry()
@ -693,9 +694,9 @@ public class RedactionIntegrationTest extends AbstractRedactionIntegrationTest {
assertTrue(redactionLog.getRedactionLogEntry().stream().anyMatch(entry -> entry.getValue().equals("Desiree et al")));
assertTrue(redactionLog.getRedactionLogEntry().stream().anyMatch(entry -> entry.getValue().equals("Melanie et al.")));
DocumentGraph documentGraph = DocumentGraphMapper.toDocumentGraph(redactionStorageService.getDocumentData(TEST_DOSSIER_ID, TEST_FILE_ID));
Document document = DocumentGraphMapper.toDocumentGraph(redactionStorageService.getDocumentData(TEST_DOSSIER_ID, TEST_FILE_ID));
String expandedEntityKeyword = "Lorem ipsum dolor sit amet, consectetur adipiscing elit Desiree et al sed do eiusmod tempor incididunt ut labore et dolore magna aliqua Melanie et al. Reference No 12345 Lorem ipsum.";
RedactionEntity expandedEntity = entityCreationService.byString(expandedEntityKeyword, "PII", EntityType.ENTITY, documentGraph).findFirst().get();
RedactionEntity expandedEntity = entityCreationService.byString(expandedEntityKeyword, "PII", EntityType.ENTITY, document).findFirst().get();
String idToResize = redactionLog.getRedactionLogEntry().stream().filter(entry -> entry.getValue().equals("Desiree et al")).findFirst().get().getId();
List<Rectangle> resizedPositions = expandedEntity.getRedactionPositionsPerPage()

View File

@ -78,8 +78,8 @@ import com.iqser.red.service.redaction.v1.server.client.LegalBasisClient;
import com.iqser.red.service.redaction.v1.server.client.RulesClient;
import com.iqser.red.service.redaction.v1.server.controller.RedactionController;
import com.iqser.red.service.redaction.v1.server.multitenancy.TenantContext;
import com.iqser.red.service.redaction.v1.server.redaction.service.AnalyzeService;
import com.iqser.red.service.redaction.v1.server.redaction.service.ManualRedactionSurroundingTextService;
import com.iqser.red.service.redaction.v1.server.redaction.service.analyze.AnalyzeService;
import com.iqser.red.service.redaction.v1.server.redaction.utils.ResourceLoader;
import com.iqser.red.service.redaction.v1.server.redaction.utils.TextNormalizationUtilities;
import com.iqser.red.service.redaction.v1.server.storage.RedactionStorageService;
@ -95,7 +95,7 @@ import lombok.extern.slf4j.Slf4j;
@Import(RulesTest.RulesTestConfiguration.class)
public class RulesTest {
private static final String RULES_PATH = "drools/prod_rules_new.drl";
private static final String RULES_PATH = "drools/prod_syngenta_new.drl";
private static final String RULES = loadFromClassPath(RULES_PATH);
private static final String VERTEBRATE = "vertebrate";
private static final String ADDRESS = "CBI_address";
@ -797,7 +797,7 @@ public class RulesTest {
URL resource = ResourceLoader.class.getClassLoader().getResource(path);
if (resource == null) {
throw new IllegalArgumentException("could not load classpath resource: drools/prod_rules_new.drl");
throw new IllegalArgumentException("could not load classpath resource: " + path);
}
try (BufferedReader br = new BufferedReader(new InputStreamReader(resource.openStream(), StandardCharsets.UTF_8))) {
StringBuilder sb = new StringBuilder();

View File

@ -23,11 +23,11 @@ import com.iqser.red.service.redaction.v1.server.AbstractRedactionIntegrationTes
import com.iqser.red.service.redaction.v1.server.Application;
import com.iqser.red.service.redaction.v1.server.FileSystemBackedStorageService;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.adapter.ImageServiceResponseAdapter;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Document;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.ClassificationDocument;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.image.ClassifiedImage;
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.service.PdfSegmentationService;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.factory.DocumentGraphFactory;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Document;
import com.iqser.red.storage.commons.StorageAutoConfiguration;
import com.iqser.red.storage.commons.service.StorageService;
@ -35,8 +35,8 @@ import lombok.SneakyThrows;
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Import(BuildDocumentGraphIntegrationTest.TestConfiguration.class)
public class BuildDocumentGraphIntegrationTest extends AbstractRedactionIntegrationTest {
@Import(BuildDocumentIntegrationTest.TestConfiguration.class)
public class BuildDocumentIntegrationTest extends AbstractRedactionIntegrationTest {
@Configuration
@EnableAutoConfiguration(exclude = {RabbitAutoConfiguration.class})
@ -60,7 +60,7 @@ public class BuildDocumentGraphIntegrationTest extends AbstractRedactionIntegrat
@SneakyThrows
protected DocumentGraph buildGraph(String filename) {
protected Document buildGraph(String filename) {
if (!filename.endsWith(".pdf")) {
filename = filename + ".pdf";
@ -74,14 +74,14 @@ public class BuildDocumentGraphIntegrationTest extends AbstractRedactionIntegrat
try (InputStream inputStream = fileResource.getInputStream()) {
Map<Integer, List<ClassifiedImage>> pdfImages = imageServiceResponseAdapter.convertImages(TEST_DOSSIER_ID, TEST_FILE_ID);
Document classifiedDoc = segmentationService.parseDocument(TEST_DOSSIER_ID, TEST_FILE_ID, inputStream, pdfImages);
ClassificationDocument classifiedDoc = segmentationService.parseDocument(TEST_DOSSIER_ID, TEST_FILE_ID, inputStream, pdfImages);
return DocumentGraphFactory.buildDocumentGraph(classifiedDoc);
}
}
@SneakyThrows
protected DocumentGraph buildGraphNoImages(String filename) {
protected Document buildGraphNoImages(String filename) {
if (!filename.endsWith(".pdf")) {
filename = filename + ".pdf";
@ -93,7 +93,7 @@ public class BuildDocumentGraphIntegrationTest extends AbstractRedactionIntegrat
try (InputStream inputStream = fileResource.getInputStream()) {
Map<Integer, List<ClassifiedImage>> pdfImages = new HashMap<>();
Document classifiedDoc = segmentationService.parseDocument(TEST_DOSSIER_ID, TEST_FILE_ID, inputStream, pdfImages);
ClassificationDocument classifiedDoc = segmentationService.parseDocument(TEST_DOSSIER_ID, TEST_FILE_ID, inputStream, pdfImages);
return DocumentGraphFactory.buildDocumentGraph(classifiedDoc);
}
}

View File

@ -10,21 +10,21 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.EntityType;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.HeadlineNode;
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.Headline;
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.PageNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.ParagraphNode;
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.Paragraph;
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.TableCellNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.TableNode;
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 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.redaction.model.EntityType;
public class DocumentGraphEntityInsertionIntegrationTest extends BuildDocumentGraphIntegrationTest {
public class DocumentEntityInsertionIntegrationTest extends BuildDocumentIntegrationTest {
@Autowired
private EntityCreationService entityCreationService;
@ -33,25 +33,25 @@ public class DocumentGraphEntityInsertionIntegrationTest extends BuildDocumentGr
@Test
public void assertCollectAllEntitiesWorks() {
DocumentGraph documentGraph = buildGraph("files/new/crafted document.pdf");
createAndInsertEntity(documentGraph, "Clarissa");
createAndInsertEntity(documentGraph, "Lastname");
createAndInsertEntity(documentGraph, "David Ksenia");
createAndInsertEntity(documentGraph, "Michael N.");
createAndInsertEntity(documentGraph, "Page-Footer");
createAndInsertEntity(documentGraph, "CTL/with dictionary entry 1234 with Slash");
assertEquals(6, documentGraph.getEntities().size());
Document document = buildGraph("files/new/crafted document.pdf");
createAndInsertEntity(document, "Clarissa");
createAndInsertEntity(document, "Lastname");
createAndInsertEntity(document, "David Ksenia");
createAndInsertEntity(document, "Michael N.");
createAndInsertEntity(document, "Page-Footer");
createAndInsertEntity(document, "CTL/with dictionary entry 1234 with Slash");
assertEquals(6, document.getEntities().size());
}
private RedactionEntity createAndInsertEntity(DocumentGraph documentGraph, String searchTerm) {
private RedactionEntity createAndInsertEntity(Document document, String searchTerm) {
int start = documentGraph.getTextBlock().indexOf(searchTerm);
int start = document.getTextBlock().indexOf(searchTerm);
assert start != -1;
Boundary boundary = new Boundary(start, start + searchTerm.length());
RedactionEntity redactionEntity = RedactionEntity.initialEntityNode(boundary, "123", EntityType.ENTITY);
entityCreationService.addEntityToGraph(redactionEntity, documentGraph);
entityCreationService.addEntityToGraph(redactionEntity, document);
return redactionEntity;
}
@ -59,9 +59,9 @@ public class DocumentGraphEntityInsertionIntegrationTest extends BuildDocumentGr
@Test
public void assertTextBeforeAndTextAfterForParagraphCrafted() {
DocumentGraph documentGraph = buildGraph("files/new/crafted document");
Document document = buildGraph("files/new/crafted document");
String searchTerm = "Clarissa";
RedactionEntity redactionEntity = createAndInsertEntity(documentGraph, searchTerm);
RedactionEntity redactionEntity = createAndInsertEntity(document, searchTerm);
assertEquals("Expand to Hint ", redactionEntity.getTextBefore());
assertEquals("s Donut ←", redactionEntity.getTextAfter());
@ -70,7 +70,7 @@ public class DocumentGraphEntityInsertionIntegrationTest extends BuildDocumentGr
redactionEntity.getDeepestFullyContainingNode().getHeadline().buildTextBlock().getSearchText());
assertEquals(3, redactionEntity.getIntersectingNodes().size());
assertEquals(5, redactionEntity.getDeepestFullyContainingNode().getNumberOnPage());
assertInstanceOf(ParagraphNode.class, redactionEntity.getDeepestFullyContainingNode());
assertInstanceOf(Paragraph.class, redactionEntity.getDeepestFullyContainingNode());
assertSameOffsetInAllIntersectingNodes(searchTerm, redactionEntity);
}
@ -79,9 +79,9 @@ public class DocumentGraphEntityInsertionIntegrationTest extends BuildDocumentGr
@Test
public void assertTextBeforeAndTextAfterForHeadlineCrafted() {
DocumentGraph documentGraph = buildGraph("files/new/crafted document");
Document document = buildGraph("files/new/crafted document");
String searchTerm = "Rule 39:";
RedactionEntity redactionEntity = createAndInsertEntity(documentGraph, searchTerm);
RedactionEntity redactionEntity = createAndInsertEntity(document, searchTerm);
assertEquals("", redactionEntity.getTextBefore());
assertEquals(" Purity Hint", redactionEntity.getTextAfter());
@ -89,7 +89,7 @@ public class DocumentGraphEntityInsertionIntegrationTest extends BuildDocumentGr
assertEquals("Rule 39: Purity Hint ", redactionEntity.getDeepestFullyContainingNode().getHeadline().buildTextBlock().getSearchText());
assertEquals(3, redactionEntity.getIntersectingNodes().size());
assertEquals(6, redactionEntity.getDeepestFullyContainingNode().getNumberOnPage());
assertInstanceOf(HeadlineNode.class, redactionEntity.getDeepestFullyContainingNode());
assertInstanceOf(Headline.class, redactionEntity.getDeepestFullyContainingNode());
assertSameOffsetInAllIntersectingNodes(searchTerm, redactionEntity);
}
@ -98,9 +98,9 @@ public class DocumentGraphEntityInsertionIntegrationTest extends BuildDocumentGr
@Test
public void assertTextBeforeAndTextAfterForTableCellCrafted() {
DocumentGraph documentGraph = buildGraph("files/new/crafted document");
Document document = buildGraph("files/new/crafted document");
String searchTerm = "1998";
RedactionEntity redactionEntity = createAndInsertEntity(documentGraph, searchTerm);
RedactionEntity redactionEntity = createAndInsertEntity(document, searchTerm);
assertEquals("", redactionEntity.getTextBefore());
assertEquals("", redactionEntity.getTextAfter());
@ -108,7 +108,7 @@ public class DocumentGraphEntityInsertionIntegrationTest extends BuildDocumentGr
assertEquals("Rule 6-11 (Authors Table) ", redactionEntity.getDeepestFullyContainingNode().getHeadline().buildTextBlock().getSearchText());
assertEquals(5, redactionEntity.getIntersectingNodes().size());
assertEquals(15, redactionEntity.getDeepestFullyContainingNode().getNumberOnPage());
assertInstanceOf(TableCellNode.class, redactionEntity.getDeepestFullyContainingNode());
assertInstanceOf(TableCell.class, redactionEntity.getDeepestFullyContainingNode());
assertSameOffsetInAllIntersectingNodes(searchTerm, redactionEntity);
}
@ -117,28 +117,28 @@ public class DocumentGraphEntityInsertionIntegrationTest extends BuildDocumentGr
@Test
public void findAndCheckMultipleSearchTermsCrafted() {
DocumentGraph documentGraph = buildGraph("files/new/crafted document");
assertValueAndPageAndIntersectingNodes(documentGraph, "David", 1);
assertValueAndPageAndIntersectingNodes(documentGraph, "Weyland Industries", 2);
assertValueAndPageAndIntersectingNodes(documentGraph, "Desiree", 3);
assertValueAndPageAndIntersectingNodes(documentGraph, "kawasaki@me.com", 4);
assertValueAndPageAndIntersectingNodes(documentGraph, "Central Research Industry", 5);
Document document = buildGraph("files/new/crafted document");
assertValueAndPageAndIntersectingNodes(document, "David", 1);
assertValueAndPageAndIntersectingNodes(document, "Weyland Industries", 2);
assertValueAndPageAndIntersectingNodes(document, "Desiree", 3);
assertValueAndPageAndIntersectingNodes(document, "kawasaki@me.com", 4);
assertValueAndPageAndIntersectingNodes(document, "Central Research Industry", 5);
}
@Test
public void assertTableStructure() {
DocumentGraph documentGraph = buildGraph("files/new/crafted document");
TableNode table = (TableNode) documentGraph.getTableOfContents()//
Document document = buildGraph("files/new/crafted document");
Table table = (Table) document.getDocumentTree()//
.streamAllEntriesInOrder()//
.filter(entry -> entry.getType().equals(NodeType.TABLE))//
.map(TableOfContents.Entry::getNode)//
.map(DocumentTree.Entry::getNode)//
.findFirst().orElseThrow();
assertEquals(5, table.getNumberOfCols());
assertEquals(4, table.getNumberOfRows());
assertEquals(5, table.streamHeaders().toList().size());
CharSequence firstHeader = table.streamHeadersForCell(1, 1).map(TableCellNode::buildTextBlock).map(TextBlock::getSearchText).findFirst().orElseThrow();
CharSequence firstHeader = table.streamHeadersForCell(1, 1).map(TableCell::buildTextBlock).map(TextBlock::getSearchText).findFirst().orElseThrow();
assertEquals("Author(s)", firstHeader.toString().stripTrailing());
}
@ -146,31 +146,31 @@ public class DocumentGraphEntityInsertionIntegrationTest extends BuildDocumentGr
@Test
public void findAndCheckMultipleSearchTermsMetolachlor() {
DocumentGraph documentGraph = buildGraph("files/Metolachlor/S-Metolachlor_RAR_01_Volume_1_2018-09-06");
assertValueAndPageAndIntersectingNodes(documentGraph, "sideeffects", 4);
assertValueAndPageAndIntersectingNodes(documentGraph, "Commission Regulation", 9);
assertValueAndPageAndIntersectingNodes(documentGraph, "Pre-emergence", 15);
assertValueAndPageAndIntersectingNodes(documentGraph, "LiChrosorb CN +", 22);
assertValueAndPageAndIntersectingNodes(documentGraph, "RCC856132", 22);
assertValueAndPageAndIntersectingNodes(documentGraph, "Number of references included", 33);
Document document = buildGraph("files/Metolachlor/S-Metolachlor_RAR_01_Volume_1_2018-09-06");
assertValueAndPageAndIntersectingNodes(document, "sideeffects", 4);
assertValueAndPageAndIntersectingNodes(document, "Commission Regulation", 9);
assertValueAndPageAndIntersectingNodes(document, "Pre-emergence", 15);
assertValueAndPageAndIntersectingNodes(document, "LiChrosorb CN +", 22);
assertValueAndPageAndIntersectingNodes(document, "RCC856132", 22);
assertValueAndPageAndIntersectingNodes(document, "Number of references included", 33);
}
@Test
public void assertTableStructureMetolachlor() {
DocumentGraph documentGraph = buildGraph("files/Metolachlor/S-Metolachlor_RAR_01_Volume_1_2018-09-06");
TableNode table = (TableNode) documentGraph.getTableOfContents()
Document document = buildGraph("files/Metolachlor/S-Metolachlor_RAR_01_Volume_1_2018-09-06");
Table table = (Table) document.getDocumentTree()
.streamAllEntriesInOrder()
.filter(entry -> entry.getNode().getPages().stream().anyMatch(page -> page.getNumber() == 22))
.filter(entry -> entry.getType().equals(NodeType.TABLE))
.map(TableOfContents.Entry::getNode)
.map(DocumentTree.Entry::getNode)
.findFirst()
.orElseThrow();
assertEquals(5, table.getNumberOfCols());
assertEquals(14, table.getNumberOfRows());
assertEquals(10, table.streamHeaders().toList().size());
List<String> twoHeaders = table.streamHeadersForCell(2, 1).map(TableCellNode::buildTextBlock).map(TextBlock::getSearchText).toList();
List<String> twoHeaders = table.streamHeadersForCell(2, 1).map(TableCell::buildTextBlock).map(TextBlock::getSearchText).toList();
assertEquals(2, twoHeaders.size());
assertEquals("Component of residue definition: S-Metolachlor", twoHeaders.get(0).stripTrailing());
assertEquals("Method type", twoHeaders.get(1).stripTrailing());
@ -180,9 +180,9 @@ public class DocumentGraphEntityInsertionIntegrationTest extends BuildDocumentGr
@Test
public void assertTextBeforeAndTextAfterForParagraphMetolachlor() {
DocumentGraph documentGraph = buildGraph("files/Metolachlor/S-Metolachlor_RAR_01_Volume_1_2018-09-06");
Document document = buildGraph("files/Metolachlor/S-Metolachlor_RAR_01_Volume_1_2018-09-06");
String searchTerm = "Cucurbit";
RedactionEntity redactionEntity = createAndInsertEntity(documentGraph, searchTerm);
RedactionEntity redactionEntity = createAndInsertEntity(document, searchTerm);
assertEquals("except Cranberry; Vegetable, ", redactionEntity.getTextBefore());
assertEquals(", Group 9;", redactionEntity.getTextAfter());
@ -192,7 +192,7 @@ public class DocumentGraphEntityInsertionIntegrationTest extends BuildDocumentGr
assertEquals(3, redactionEntity.getIntersectingNodes().size());
assertEquals(5, redactionEntity.getDeepestFullyContainingNode().getNumberOnPage());
assertTrue(redactionEntity.getPages().stream().allMatch(pageNode -> pageNode.getNumber() == 10));
assertInstanceOf(ParagraphNode.class, redactionEntity.getDeepestFullyContainingNode());
assertInstanceOf(Paragraph.class, redactionEntity.getDeepestFullyContainingNode());
assertSameOffsetInAllIntersectingNodes(searchTerm, redactionEntity);
}
@ -201,16 +201,16 @@ public class DocumentGraphEntityInsertionIntegrationTest extends BuildDocumentGr
@Test
public void assertTextBeforeAndTextAfterForHeadlineMetolachlor() {
DocumentGraph documentGraph = buildGraph("files/Metolachlor/S-Metolachlor_RAR_01_Volume_1_2018-09-06");
Document document = buildGraph("files/Metolachlor/S-Metolachlor_RAR_01_Volume_1_2018-09-06");
String searchTerm = "absorption, distribution, metabolism";
int start = documentGraph.getTextBlock().indexOf(searchTerm);
int start = document.getTextBlock().indexOf(searchTerm);
assert start != -1;
start = documentGraph.getTextBlock().indexOf(searchTerm, start + 1);
start = document.getTextBlock().indexOf(searchTerm, start + 1);
assert start != -1;
Boundary boundary = new Boundary(start, start + searchTerm.length());
RedactionEntity redactionEntity = RedactionEntity.initialEntityNode(boundary, "123", EntityType.ENTITY);
entityCreationService.addEntityToGraph(redactionEntity, documentGraph);
entityCreationService.addEntityToGraph(redactionEntity, document);
assertEquals("2.6.1 Summary of ", redactionEntity.getTextBefore());
assertEquals(" and excretion in", redactionEntity.getTextAfter());
@ -220,7 +220,7 @@ public class DocumentGraphEntityInsertionIntegrationTest extends BuildDocumentGr
assertEquals(3, redactionEntity.getIntersectingNodes().size());
assertEquals(4, redactionEntity.getDeepestFullyContainingNode().getNumberOnPage());
assertTrue(redactionEntity.getPages().stream().allMatch(pageNode -> pageNode.getNumber() == 33));
assertInstanceOf(HeadlineNode.class, redactionEntity.getDeepestFullyContainingNode());
assertInstanceOf(Headline.class, redactionEntity.getDeepestFullyContainingNode());
assertSameOffsetInAllIntersectingNodes(searchTerm, redactionEntity);
}
@ -229,9 +229,9 @@ public class DocumentGraphEntityInsertionIntegrationTest extends BuildDocumentGr
@Test
public void assertTextBeforeAndTextAfterForTableCellMetolachlor() {
DocumentGraph documentGraph = buildGraph("files/Metolachlor/S-Metolachlor_RAR_01_Volume_1_2018-09-06");
Document document = buildGraph("files/Metolachlor/S-Metolachlor_RAR_01_Volume_1_2018-09-06");
String searchTerm = "N-deacetylation product";
RedactionEntity redactionEntity = createAndInsertEntity(documentGraph, searchTerm);
RedactionEntity redactionEntity = createAndInsertEntity(document, searchTerm);
assertEquals("2-[(2-(1-hydroxy-ethyl)-6methyl-phenyl-amino]propan-1-ol (", redactionEntity.getTextBefore());
assertEquals(" of metabolite of", redactionEntity.getTextAfter());
@ -242,7 +242,7 @@ public class DocumentGraphEntityInsertionIntegrationTest extends BuildDocumentGr
assertTrue(redactionEntity.getPages().stream().allMatch(pageNode -> pageNode.getNumber() == 54));
assertEquals(26, redactionEntity.getDeepestFullyContainingNode().getNumberOnPage());
assertInstanceOf(TableCellNode.class, redactionEntity.getDeepestFullyContainingNode());
assertInstanceOf(TableCell.class, redactionEntity.getDeepestFullyContainingNode());
assertSameOffsetInAllIntersectingNodes(searchTerm, redactionEntity);
}
@ -260,20 +260,20 @@ public class DocumentGraphEntityInsertionIntegrationTest extends BuildDocumentGr
}
private void assertValueAndPageAndIntersectingNodes(DocumentGraph documentGraph, String searchTerm, int pageNumber) {
private void assertValueAndPageAndIntersectingNodes(Document document, String searchTerm, int pageNumber) {
int start = documentGraph.getTextBlock().indexOf(searchTerm);
int start = document.getTextBlock().indexOf(searchTerm);
assert start != -1;
Boundary boundary = new Boundary(start, start + searchTerm.length());
RedactionEntity redactionEntity = RedactionEntity.initialEntityNode(boundary, "123", EntityType.ENTITY);
entityCreationService.addEntityToGraph(redactionEntity, documentGraph);
PageNode pageNode = documentGraph.getPages().stream().filter(page -> page.getNumber() == pageNumber).findFirst().orElseThrow();
entityCreationService.addEntityToGraph(redactionEntity, document);
Page pageNode = document.getPages().stream().filter(page -> page.getNumber() == pageNumber).findFirst().orElseThrow();
assertEquals(redactionEntity.getValue(), searchTerm);
assertTrue(pageNode.getEntities().contains(redactionEntity));
assertTrue(documentGraph.getPages().stream().filter(page -> page != pageNode).noneMatch(page -> page.getEntities().contains(redactionEntity)));
assertTrue(document.getPages().stream().filter(page -> page != pageNode).noneMatch(page -> page.getEntities().contains(redactionEntity)));
assertTrue(redactionEntity.getPages().contains(pageNode));
assertSameOffsetInAllIntersectingNodes(searchTerm, redactionEntity);
assertTrue(redactionEntity.getIntersectingNodes().stream().allMatch(node -> node.getEntities().contains(redactionEntity)));

View File

@ -1,35 +0,0 @@
package com.iqser.red.service.redaction.v1.server.document.graph;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.PageNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.TableCellNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.TableNode;
public class DocumentGraphTableNodeIntegrationTest extends BuildDocumentGraphIntegrationTest {
@Test
public void testAllTableCellAccessesCorrect() {
DocumentGraph documentGraph = buildGraph("files/Metolachlor/S-Metolachlor_RAR_02_Volume_2_2018-09-06");
PageNode pageFive = documentGraph.getPages().stream().filter(pageNode -> pageNode.getNumber() == 5).findFirst().get();
TableNode tableNode = pageFive.getMainBody()
.stream()
.filter(semanticNode -> semanticNode instanceof TableNode)
.map(semanticNode -> (TableNode) semanticNode)
.findFirst()
.get();
for (int row = 0; row < tableNode.getNumberOfRows(); row++) {
for (int col = 0; col < tableNode.getNumberOfCols(); col++) {
TableCellNode tableCellNode = tableNode.getCell(row, col);
assertEquals(col, tableCellNode.getCol());
assertEquals(row, tableCellNode.getRow());
}
}
}
}

View File

@ -8,19 +8,19 @@ import org.junit.jupiter.api.Test;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.AtomicPositionBlockData;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.AtomicTextBlockData;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.DocumentData;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.DocumentTreeData;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.PageData;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.TableOfContentsData;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.mapper.DocumentDataMapper;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.mapper.DocumentGraphMapper;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.mapper.PropertiesMapper;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
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.TableNode;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.Table;
import com.iqser.red.service.redaction.v1.server.multitenancy.TenantContext;
import lombok.SneakyThrows;
public class DocumentGraphMappingIntegrationTest extends BuildDocumentGraphIntegrationTest {
public class DocumentMappingIntegrationTest extends BuildDocumentIntegrationTest {
@Test
@SneakyThrows
@ -28,56 +28,56 @@ public class DocumentGraphMappingIntegrationTest extends BuildDocumentGraphInteg
String filename = "files/new/crafted document";
DocumentGraph document = buildGraph(filename);
Document document = buildGraph(filename);
DocumentData documentData = DocumentDataMapper.toDocumentData(document);
storageService.storeJSONObject(TenantContext.getTenantId(), filename + "_PAGES" + ".json", documentData.getPages());
storageService.storeJSONObject(TenantContext.getTenantId(), filename + "_TEXT" + ".json", documentData.getAtomicTextBlocks());
storageService.storeJSONObject(TenantContext.getTenantId(), filename + "_POSITIONS" + ".json", documentData.getAtomicPositionBlocks());
storageService.storeJSONObject(TenantContext.getTenantId(), filename + "_STRUCTURE" + ".json", documentData.getTableOfContents());
storageService.storeJSONObject(TenantContext.getTenantId(), filename + "_STRUCTURE" + ".json", documentData.getDocumentTreeData());
PageData[] pageData = storageService.readJSONObject(TenantContext.getTenantId(), filename + "_PAGES" + ".json", PageData[].class);
AtomicTextBlockData[] atomicTextBlockData = storageService.readJSONObject(TenantContext.getTenantId(), filename + "_TEXT" + ".json", AtomicTextBlockData[].class);
AtomicPositionBlockData[] atomicPositionBlockData = storageService.readJSONObject(TenantContext.getTenantId(),
filename + "_POSITIONS" + ".json",
AtomicPositionBlockData[].class);
TableOfContentsData tableOfContentsData = storageService.readJSONObject(TenantContext.getTenantId(), filename + "_STRUCTURE" + ".json", TableOfContentsData.class);
DocumentTreeData documentTreeData = storageService.readJSONObject(TenantContext.getTenantId(), filename + "_STRUCTURE" + ".json", DocumentTreeData.class);
DocumentData documentData2 = DocumentData.builder()
.pages(pageData)
.tableOfContents(tableOfContentsData)
.documentTreeData(documentTreeData)
.atomicTextBlocks(atomicTextBlockData)
.atomicPositionBlocks(atomicPositionBlockData)
.build();
DocumentGraph newDocumentGraph = DocumentGraphMapper.toDocumentGraph(documentData2);
Document newDocument = DocumentGraphMapper.toDocumentGraph(documentData2);
assertTrue(allTablesHavePositiveNumberOfRowsAndColumns(document));
assertTrue(allTablesHavePositiveNumberOfRowsAndColumns(documentData));
assertTrue(allTablesHavePositiveNumberOfRowsAndColumns(documentData2));
assertTrue(allTablesHavePositiveNumberOfRowsAndColumns(newDocumentGraph));
assertTrue(allTablesHavePositiveNumberOfRowsAndColumns(newDocument));
assertEquals(document.toString(), newDocumentGraph.toString());
assertEquals(document.getTableOfContents().toString(), newDocumentGraph.getTableOfContents().toString());
assertEquals(document.toString(), newDocument.toString());
assertEquals(document.getDocumentTree().toString(), newDocument.getDocumentTree().toString());
}
private static boolean allTablesHavePositiveNumberOfRowsAndColumns(DocumentGraph documentGraph) {
private static boolean allTablesHavePositiveNumberOfRowsAndColumns(Document document) {
return documentGraph.streamAllSubNodes()
.filter(semanticNode -> semanticNode instanceof TableNode)
.map(semanticNode -> (TableNode) semanticNode)
return document.streamAllSubNodes()
.filter(semanticNode -> semanticNode instanceof Table)
.map(semanticNode -> (Table) semanticNode)
.allMatch(tableNode -> tableNode.getNumberOfCols() > 0 && tableNode.getNumberOfRows() > 0);
}
private static boolean allTablesHavePositiveNumberOfRowsAndColumns(DocumentData documentData) {
return documentData.getTableOfContents()
return documentData.getDocumentTreeData()
.streamAllEntries()
.filter(entryData -> entryData.getType().equals(NodeType.TABLE))
.map(TableOfContentsData.EntryData::getProperties)
.map(DocumentTreeData.EntryData::getProperties)
.map(properties -> {
var builder = TableNode.builder();
var builder = Table.builder();
PropertiesMapper.parseTableProperties(properties, builder);
return builder.build();
})

View File

@ -0,0 +1,30 @@
package com.iqser.red.service.redaction.v1.server.document.graph;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
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.Page;
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;
public class DocumentTableIntegrationTest extends BuildDocumentIntegrationTest {
@Test
public void testAllTableCellAccessesCorrect() {
Document document = buildGraph("files/Metolachlor/S-Metolachlor_RAR_02_Volume_2_2018-09-06");
Page pageFive = document.getPages().stream().filter(pageNode -> pageNode.getNumber() == 5).findFirst().get();
Table table = pageFive.getMainBody().stream().filter(semanticNode -> semanticNode instanceof Table).map(semanticNode -> (Table) semanticNode).findFirst().get();
for (int row = 0; row < table.getNumberOfRows(); row++) {
for (int col = 0; col < table.getNumberOfCols(); col++) {
TableCell tableCell = table.getCell(row, col);
assertEquals(col, tableCell.getCol());
assertEquals(row, tableCell.getRow());
}
}
}
}

View File

@ -9,13 +9,13 @@ import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
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 lombok.SneakyThrows;
public class DocumentGraphVisualizationIntegrationTest extends BuildDocumentGraphIntegrationTest {
public class DocumentVisualizationIntegrationTest extends BuildDocumentIntegrationTest {
@Test
@SneakyThrows
@ -24,10 +24,10 @@ public class DocumentGraphVisualizationIntegrationTest extends BuildDocumentGrap
String filename = "files/Metolachlor/S-Metolachlor_RAR_01_Volume_1_2018-09-06";
DocumentGraph documentGraph = buildGraph(filename);
TextBlock textBlock = documentGraph.buildTextBlock();
Document document = buildGraph(filename);
TextBlock textBlock = document.buildTextBlock();
visualizeSemanticNodes(filename, documentGraph, textBlock);
visualizeSemanticNodes(filename, document, textBlock);
}
@ -38,10 +38,10 @@ public class DocumentGraphVisualizationIntegrationTest extends BuildDocumentGrap
String filename = "files/Fludioxonil/56 Fludioxonil_RAR_12_Volume_3CA_B-7_2018-02-21";
DocumentGraph documentGraph = buildGraph(filename);
TextBlock textBlock = documentGraph.buildTextBlock();
Document document = buildGraph(filename);
TextBlock textBlock = document.buildTextBlock();
visualizeSemanticNodes(filename, documentGraph, textBlock);
visualizeSemanticNodes(filename, document, textBlock);
}
@ -52,10 +52,10 @@ public class DocumentGraphVisualizationIntegrationTest extends BuildDocumentGrap
String filename = "files/new/test1S1T1";
DocumentGraph documentGraph = buildGraph(filename);
TextBlock textBlock = documentGraph.buildTextBlock();
Document document = buildGraph(filename);
TextBlock textBlock = document.buildTextBlock();
visualizeSemanticNodes(filename, documentGraph, textBlock);
visualizeSemanticNodes(filename, document, textBlock);
}
@ -66,21 +66,21 @@ public class DocumentGraphVisualizationIntegrationTest extends BuildDocumentGrap
String filename = "files/new/crafted document";
DocumentGraph documentGraph = buildGraph(filename);
TextBlock textBlock = documentGraph.buildTextBlock();
Document document = buildGraph(filename);
TextBlock textBlock = document.buildTextBlock();
visualizeSemanticNodes(filename, documentGraph, textBlock);
visualizeSemanticNodes(filename, document, textBlock);
}
private static void visualizeSemanticNodes(String filename, DocumentGraph documentGraph, TextBlock textBlock) throws IOException {
private static void visualizeSemanticNodes(String filename, Document document, TextBlock textBlock) throws IOException {
var tmpFileName = "/tmp/" + filename.split("/")[2] + "_SEMANTIC_NODES_BBOX.pdf";
var fileResource = new ClassPathResource(filename + ".pdf");
try (var fileStream = fileResource.getInputStream()) {
PDDocument pdDocument = PDDocument.load(fileStream);
PdfDraw.drawDocumentGraph(pdDocument, documentGraph);
PdfDraw.drawDocumentGraph(pdDocument, document);
PdfDraw.drawTextBlock(pdDocument, textBlock, PdfDraw.Options.builder().stroke(true).strokeWidth(0.1f).strokeColor(Color.YELLOW).build());
File outputFile = new File(tmpFileName);
pdDocument.save(outputFile);

Some files were not shown because too many files have changed in this diff Show More