RED-6009 - Document Tree Structure
* fixed javadoc and delombok plugins * removed unused classes * refactored names
This commit is contained in:
parent
c68c6f9ba9
commit
088a5cde1a
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -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();
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -13,7 +13,7 @@ import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Data
|
||||
@RequiredArgsConstructor
|
||||
public class Page {
|
||||
public class ClassificationPage {
|
||||
|
||||
@NonNull
|
||||
private List<AbstractPageBlock> textBlocks;
|
||||
@ -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<>();
|
||||
@ -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;
|
||||
|
||||
@ -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}", " ");
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<>();
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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()) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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());
|
||||
|
||||
|
||||
@ -15,6 +15,6 @@ public class DocumentData {
|
||||
PageData[] pages;
|
||||
AtomicTextBlockData[] atomicTextBlocks;
|
||||
AtomicPositionBlockData[] atomicPositionBlocks;
|
||||
TableOfContentsData tableOfContents;
|
||||
DocumentTreeData documentTreeData;
|
||||
|
||||
}
|
||||
|
||||
@ -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(",");
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
|
||||
@ -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")));
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
@ -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());
|
||||
|
||||
@ -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;
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
package com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes;
|
||||
|
||||
public enum ImageType {
|
||||
LOGO,
|
||||
FORMULA,
|
||||
SIGNATURE,
|
||||
OTHER,
|
||||
OCR
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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) {
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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}", " ");
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<>();
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
package com.iqser.red.service.redaction.v1.server.redaction.model;
|
||||
|
||||
public enum ImageType {
|
||||
LOGO,
|
||||
FORMULA,
|
||||
SIGNATURE,
|
||||
OTHER,
|
||||
OCR
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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<>();
|
||||
|
||||
}
|
||||
@ -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);
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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(),
|
||||
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -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));
|
||||
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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 : "");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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))
|
||||
@ -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;
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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)));
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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();
|
||||
})
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user