Compare commits

...
23 Commits
Author SHA1 Message Date
Kilian Schuettler f57c27a7e5 try native build 2023-07-27 13:50:32 +02:00
Kilian Schüttler 2ea58f5e9f Merge branch 'fixGradleDeploy' into 'main'
Fix gradle deploy

See merge request fforesight/layout-parser!3
2023-07-27 13:42:10 +02:00
Kilian Schuettler 510ec7ce45 remove native build 2023-07-27 13:29:14 +02:00
Kilian Schuettler c186927e3d add reflection config 2023-07-27 13:21:01 +02:00
Kilian Schuettler 1a494b0dea add buildArgs to include logback at runtime 2023-07-27 13:00:26 +02:00
Kilian Schuettler 19552ddf69 added gradle.properties.kts 2023-07-27 12:32:10 +02:00
Kilian Schuettler 41267a0f98 ported to gradle 2023-07-27 12:27:30 +02:00
Kilian Schuettler 270129cd73 outputs almost equal current redaction-service in regards to RedactManager
* 3/200 files have minimal whitespace/sorting errors, most likely rounding errors
2023-07-25 18:12:57 +02:00
Kilian Schuettler a41c13fdd6 add LayoutParsingType to BodyTextFrameService 2023-07-25 17:20:52 +02:00
Kilian Schuettler 65ab5eca22 update to redaction-service state 2023-07-25 16:10:57 +02:00
Kilian Schuettler 143ebee25e move and fix layout tests from redaction-service 2023-07-24 19:43:25 +02:00
Kilian Schuettler 47fd8e05d1 rename Data classes 2023-07-24 18:36:27 +02:00
Kilian Schüttler 653f280fd1 Merge branch 'TAAS-41' into 'main'
TAAS-41: TAAS Document Structure

See merge request fforesight/layout-parser!2
2023-07-24 16:09:18 +02:00
Kilian Schuettler daa68f3fa6 TAAS-41: disable experimental tests 2023-07-24 16:07:27 +02:00
Kilian Schuettler ed66043856 TAAS-41: add test files 2023-07-24 16:04:51 +02:00
Kilian Schuettler 526b1c5ad3 TAAS-41: add (inactive) experimental services 2023-07-24 15:58:06 +02:00
Kilian Schuettler 241a32cb4f TAAS-41/ RED-6725: integrate layoutparser into redactmanager 2023-07-24 15:55:31 +02:00
Timo Bejan 9c8501e76a Changes 2023-07-13 18:55:13 +03:00
Timo Bejan 3bc88bc9b7 store new document type 2023-07-13 13:01:01 +03:00
Kilian Schuettler 15a6d46f5c RED-7081: getBBox() Performance Improvement 2023-07-13 13:01:01 +03:00
Kilian Schuettler 788613c92e TAAS-41: TAAS Document Structure
* added linebreaks to ParagraphData
* moved List<String> cellText to List<ParagraphData> cellTexts
2023-07-13 13:01:01 +03:00
Kilian Schuettler 7f0aa32d1b TAAS-41: TAAS Document Structure
* added more testFiles
* hacked a workaround for CMMException
2023-07-13 13:01:01 +03:00
Kilian Schuettler f08c4ced43 TAAS-41: TAAS Document Structure
* changed TextPageBlock splitting
* changed Header and Footer Classification
* added TAAS Document Structure Prototype
2023-07-13 13:01:01 +03:00
255 changed files with 61103 additions and 1168 deletions
+1
View File
@@ -18,6 +18,7 @@ target/
.settings
.springBeans
.sts4-cache
.gradle
### IntelliJ IDEA ###
.idea
+1 -1
View File
@@ -1,4 +1,4 @@
include:
- project: 'gitlab/gitlab'
ref: 'main'
file: 'ci-templates/maven_java.yml'
file: 'ci-templates/gradle_java.yml'
BIN
View File
Binary file not shown.
-18
View File
@@ -1,18 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.7/apache-maven-3.8.7-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar
+7
View File
@@ -0,0 +1,7 @@
plugins {
`kotlin-dsl`
}
repositories {
gradlePluginPortal()
}
@@ -0,0 +1,56 @@
plugins {
java
pmd
checkstyle
jacoco
}
group = "com.knecon.fforesight"
version = "0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_17
java.targetCompatibility = JavaVersion.VERSION_17
tasks.jacocoTestReport {
reports {
xml.required.set(false)
csv.required.set(false)
html.outputLocation.set(layout.buildDirectory.dir("jacocoHtml"))
}
}
tasks.pmdMain {
pmd.ruleSetFiles = files("${rootDir}/config/pmd/pmd.xml")
}
tasks.pmdTest {
pmd.ruleSetFiles = files("${rootDir}/config/pmd/test_pmd.xml")
}
tasks.named<Test>("test") {
useJUnitPlatform()
}
tasks.test {
finalizedBy(tasks.jacocoTestReport) // report is always generated after tests run
}
tasks.jacocoTestReport {
dependsOn(tasks.test) // tests are required to run before generating the report
reports {
xml.required.set(true)
csv.required.set(false)
}
}
repositories {
mavenLocal()
mavenCentral()
maven {
url = uri("https://nexus.knecon.com/repository/gindev/");
credentials {
username = providers.gradleProperty("mavenUser").getOrNull();
password = providers.gradleProperty("mavenPassword").getOrNull();
}
}
}
+39
View File
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<module name="Checker">
<property
name="severity"
value="error"/>
<module name="TreeWalker">
<module name="SuppressWarningsHolder"/>
<module name="MissingDeprecated"/>
<module name="MissingOverride"/>
<module name="AnnotationLocation"/>
<module name="JavadocStyle"/>
<module name="NonEmptyAtclauseDescription"/>
<module name="IllegalImport"/>
<module name="RedundantImport"/>
<module name="RedundantModifier"/>
<module name="EmptyBlock"/>
<module name="DefaultComesLast"/>
<module name="EmptyStatement"/>
<module name="EqualsHashCode"/>
<module name="ExplicitInitialization"/>
<module name="IllegalInstantiation"/>
<module name="ModifiedControlVariable"/>
<module name="MultipleVariableDeclarations"/>
<module name="PackageDeclaration"/>
<module name="ParameterAssignment"/>
<module name="SimplifyBooleanExpression"/>
<module name="SimplifyBooleanReturn"/>
<module name="StringLiteralEquality"/>
<module name="OneStatementPerLine"/>
<module name="FinalClass"/>
<module name="ArrayTypeStyle"/>
<module name="UpperEll"/>
<module name="OuterTypeFilename"/>
</module>
<module name="FileTabCharacter"/>
<module name="SuppressWarningsFilter"/>
</module>
+21
View File
@@ -0,0 +1,21 @@
<?xml version="1.0"?>
<ruleset name="Custom ruleset"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd">
<description>
Knecon ruleset checks the code for bad stuff
</description>
<rule ref="category/java/errorprone.xml">
<exclude name="DataflowAnomalyAnalysis"/>
<exclude name="MissingSerialVersionUID"/>
<exclude name="NullAssignment"/>
<exclude name="BeanMembersShouldSerialize"/>
<exclude name="AvoidLiteralsInIfCondition"/>
<exclude name="AvoidDuplicateLiterals"/>
<exclude name="AvoidFieldNameMatchingMethodName"/>
</rule>
</ruleset>
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0"?>
<ruleset name="Custom ruleset"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd">
<description>
Knecon test ruleset checks the code for bad stuff
</description>
<rule ref="category/java/errorprone.xml">
<exclude name="DataflowAnomalyAnalysis"/>
<exclude name="MissingSerialVersionUID"/>
<exclude name="NullAssignment"/>
<exclude name="BeanMembersShouldSerialize"/>
<exclude name="AvoidLiteralsInIfCondition"/>
<exclude name="AvoidDuplicateLiterals"/>
<exclude name="AvoidFieldNameMatchingMethodName"/>
<exclude name="AvoidFieldNameMatchingTypeName"/>
</rule>
</ruleset>
+1
View File
@@ -0,0 +1 @@
version = 0.1.0
-99
View File
@@ -1,99 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.knecon.fforesight</groupId>
<artifactId>platform-docker-dependency</artifactId>
<version>0.1.0</version>
<relativePath/>
</parent>
<groupId>com.knecon.fforesight</groupId>
<artifactId>layoutparser-service-image</artifactId>
<version>0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<service.server>layoutparser-service-server</service.server>
<platform.jar>${service.server}.jar</platform.jar>
<docker.skip.push>false</docker.skip.push>
<docker.image.prefix>ff</docker.image.prefix>
<docker.image.name>${docker.image.prefix}/${service.server}</docker.image.name>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>download-platform-jar</id>
<phase>prepare-package</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>${service.server}</artifactId>
<version>${project.version}</version>
<type>jar</type>
<overWrite>true</overWrite>
<destFileName>${platform.jar}</destFileName>
</dependency>
</artifactItems>
<outputDirectory>${docker.build.directory}</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<configuration>
<images>
<image>
<name>${docker.image.name}</name>
<build>
<dockerFileDir>${docker.build.directory}</dockerFileDir>
<args>
<PLATFORM_JAR>${platform.jar}</PLATFORM_JAR>
</args>
<tags>
<tag>${docker.image.version}</tag>
<tag>latest</tag>
</tags>
</build>
</image>
</images>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
@@ -1,9 +0,0 @@
FROM red/base-image:2.0.2
ARG PLATFORM_JAR
ENV PLATFORM_JAR ${PLATFORM_JAR}
ENV USES_ELASTICSEARCH false
COPY ["${PLATFORM_JAR}", "/"]
@@ -0,0 +1,6 @@
plugins {
id("com.knecon.fforesight.java-conventions")
id("io.freefair.lombok") version "8.1.0"
}
description = "layoutparser-service-internal-api"
@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.knecon.fforesight</groupId>
<artifactId>layoutparser-service</artifactId>
<version>0.1-SNAPSHOT</version>
</parent>
<artifactId>layoutparser-service-internal-api</artifactId>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
</dependencies>
</project>
@@ -1,4 +1,4 @@
package com.knecon.fforesight.service.layoutparser.internal.api.data;
package com.knecon.fforesight.service.layoutparser.internal.api.data.redaction;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
@@ -12,10 +12,9 @@ import lombok.experimental.FieldDefaults;
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class DocumentData {
PageData[] pages;
AtomicTextBlockData[] atomicTextBlocks;
AtomicPositionBlockData[] atomicPositionBlocks;
DocumentTreeData documentTreeData;
DocumentPage[] documentPages;
DocumentTextData[] documentTextData;
DocumentPositionData[] documentPositions;
DocumentStructure documentStructure;
}
@@ -1,16 +1,18 @@
package com.knecon.fforesight.service.layoutparser.internal.api.data;
package com.knecon.fforesight.service.layoutparser.internal.api.data.redaction;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class PageData {
@FieldDefaults(level = AccessLevel.PRIVATE)
public class DocumentPage {
int number;
int height;
@@ -1,16 +1,18 @@
package com.knecon.fforesight.service.layoutparser.internal.api.data;
package com.knecon.fforesight.service.layoutparser.internal.api.data.redaction;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class AtomicPositionBlockData {
@FieldDefaults(level = AccessLevel.PRIVATE)
public class DocumentPositionData {
Long id;
int[] stringIdxToPositionIdx;
@@ -1,4 +1,4 @@
package com.knecon.fforesight.service.layoutparser.internal.api.data;
package com.knecon.fforesight.service.layoutparser.internal.api.data.redaction;
import java.util.List;
import java.util.Map;
@@ -8,16 +8,15 @@ import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class DocumentTreeData {
public class DocumentStructure {
EntryData root;
@@ -37,7 +36,7 @@ public class DocumentTreeData {
public Stream<EntryData> streamAllEntries() {
return Stream.concat(Stream.of(root), root.children.stream()).flatMap(DocumentTreeData::flatten);
return Stream.concat(Stream.of(root), root.children.stream()).flatMap(DocumentStructure::flatten);
}
@@ -49,14 +48,15 @@ public class DocumentTreeData {
private static Stream<EntryData> flatten(EntryData entry) {
return Stream.concat(Stream.of(entry), entry.children.stream().flatMap(DocumentTreeData::flatten));
return Stream.concat(Stream.of(entry), entry.children.stream().flatMap(DocumentStructure::flatten));
}
@Data
@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
@FieldDefaults(level = AccessLevel.PRIVATE)
public static class EntryData {
NodeType type;
@@ -1,4 +1,4 @@
package com.knecon.fforesight.service.layoutparser.internal.api.data;
package com.knecon.fforesight.service.layoutparser.internal.api.data.redaction;
@@ -6,13 +6,15 @@ import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class AtomicTextBlockData {
@FieldDefaults(level = AccessLevel.PRIVATE)
public class DocumentTextData {
Long id;
Long page;
@@ -22,6 +24,4 @@ public class AtomicTextBlockData {
int end;
int[] lineBreaks;
}
@@ -1,4 +1,4 @@
package com.knecon.fforesight.service.layoutparser.internal.api.data;
package com.knecon.fforesight.service.layoutparser.internal.api.data.redaction;
import java.util.Locale;
@@ -0,0 +1,21 @@
package com.knecon.fforesight.service.layoutparser.internal.api.data.taas;
import java.util.List;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class ParagraphData {
private String text;
List<Range> boldTextBoundaries;
List<Range> italicTextBoundaries;
List<Integer> linebreaks;
private String classification;
private String orientation;
private int textDirection;
}
@@ -0,0 +1,5 @@
package com.knecon.fforesight.service.layoutparser.internal.api.data.taas;
public record Range(int start, int end) {
}
@@ -0,0 +1,16 @@
package com.knecon.fforesight.service.layoutparser.internal.api.data.taas;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@Builder
@Data
@AllArgsConstructor
public class ResearchDocumentData {
String originalFile;
List<StructureObject> structureObjects;
}
@@ -0,0 +1,15 @@
package com.knecon.fforesight.service.layoutparser.internal.api.data.taas;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class RowData {
boolean header;
List<ParagraphData> cellText;
float[] bBox;
}
@@ -0,0 +1,19 @@
package com.knecon.fforesight.service.layoutparser.internal.api.data.taas;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
@AllArgsConstructor
public class StructureObject {
Integer structureObjectNumber;
int page;
int stringOffset;
float[] boundingBox;
ParagraphData paragraph;
TableData table;
}
@@ -0,0 +1,16 @@
package com.knecon.fforesight.service.layoutparser.internal.api.data.taas;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class TableData {
List<RowData> rowData;
Integer numberOfCols;
Integer numberOfRows;
}
@@ -2,7 +2,7 @@ package com.knecon.fforesight.service.layoutparser.internal.api.queue;
public class LayoutParsingQueueNames {
public static final String LAYOUT_PARSING_REQUEST_QUEUE = "LAYOUTPARSING_REQUEST_QUEUE";
public static final String LAYOUT_PARSING_DLQ = "LAYOUTPARSING_DLQ";
public static final String LAYOUT_PARSING_FINISHED_EVENT_QUEUE = "LAYOUTPARSING_FINISHED_EVENT_QUEUE";
public static final String LAYOUT_PARSING_REQUEST_QUEUE = "layout_parsing_request_queue";
public static final String LAYOUT_PARSING_DLQ = "layout_parsing_dead_letter_queue";
public static final String LAYOUT_PARSING_FINISHED_EVENT_QUEUE = "layout_parsing_response_queue";
}
@@ -8,13 +8,16 @@ import lombok.Builder;
@Builder
public record LayoutParsingRequest(
LayoutParsingType layoutParsingType,
Map<String, String> identifier,
String originFileStorageId,
Optional<String> tablesFileStorageId,
Optional<String> imagesFileStorageId,
String structureFileStorageId,
String researchDocumentStorageId,
String textBlockFileStorageId,
String positionBlockFileStorageId,
String pageFileStorageId) {
String pageFileStorageId,
String sectionGridStorageId) {
}
@@ -0,0 +1,7 @@
package com.knecon.fforesight.service.layoutparser.internal.api.queue;
public enum LayoutParsingType {
REDACT_MANAGER,
TAAS,
DOCUMINE
}
@@ -0,0 +1,16 @@
<Configuration>
<Appenders>
<Console name="CONSOLE" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="warn">
<AppenderRef ref="CONSOLE"/>
</Root>
<Logger name="com.iqser" level="info"/>
</Loggers>
</Configuration>
@@ -0,0 +1,21 @@
plugins {
id("com.knecon.fforesight.java-conventions")
id("io.freefair.lombok") version "8.1.0"
}
dependencies {
implementation(project(":layoutparser-service-internal-api"))
implementation("com.iqser.red.service:persistence-service-shared-api-v1:2.36.0")
implementation("com.knecon.fforesight:tenant-commons:0.10.0")
implementation("com.iqser.red.commons:storage-commons:2.1.0")
implementation("org.apache.pdfbox:pdfbox:3.0.0-alpha2")
implementation("org.apache.pdfbox:pdfbox-tools:3.0.0-alpha2")
implementation("com.fasterxml.jackson.module:jackson-module-afterburner:2.15.0-rc2")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.0-rc2")
implementation("org.springframework.boot:spring-boot-starter-web:3.0.6")
implementation("org.springframework.boot:spring-boot-starter-amqp:3.0.6")
}
description = "layoutparser-service-processor"
@@ -1,65 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.knecon.fforesight</groupId>
<artifactId>layoutparser-service</artifactId>
<version>0.1-SNAPSHOT</version>
</parent>
<artifactId>layoutparser-service-processor</artifactId>
<dependencies>
<dependency>
<groupId>com.iqser.red.service</groupId>
<artifactId>persistence-service-shared-api-v1</artifactId>
<version>2.36.0</version>
</dependency>
<dependency>
<groupId>com.knecon.fforesight</groupId>
<artifactId>tenant-commons</artifactId>
<version>${tennat-commons.version}</version>
</dependency>
<dependency>
<groupId>com.knecon.fforesight</groupId>
<artifactId>layoutparser-service-internal-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.iqser.red.commons</groupId>
<artifactId>storage-commons</artifactId>
<version>${storage-commons.version}</version>
</dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>${pdfbox.version}</version>
</dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox-tools</artifactId>
<version>${pdfbox.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-afterburner</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,143 @@
package com.knecon.fforesight.service.layoutparser.processor;
import static java.lang.String.format;
import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.stereotype.Service;
import com.knecon.fforesight.service.layoutparser.internal.api.queue.LayoutParsingFinishedEvent;
import com.knecon.fforesight.service.layoutparser.internal.api.queue.LayoutParsingRequest;
import com.knecon.fforesight.service.layoutparser.internal.api.queue.LayoutParsingType;
import com.knecon.fforesight.service.layoutparser.processor.adapter.CvTableParsingAdapter;
import com.knecon.fforesight.service.layoutparser.processor.adapter.ImageServiceResponseAdapter;
import com.knecon.fforesight.service.layoutparser.processor.adapter.model.image.ImageServiceResponse;
import com.knecon.fforesight.service.layoutparser.processor.adapter.model.table.TableServiceResponse;
import com.knecon.fforesight.service.layoutparser.processor.factory.DocumentGraphFactory;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.Document;
import com.knecon.fforesight.service.layoutparser.processor.mapper.redaction.DocumentDataMapper;
import com.knecon.fforesight.service.layoutparser.processor.mapper.taas.TaasDocumentDataMapper;
import com.knecon.fforesight.service.layoutparser.processor.model.ClassificationDocument;
import com.knecon.fforesight.service.layoutparser.processor.services.PdfParsingService;
import com.knecon.fforesight.service.layoutparser.processor.services.SectionGridCreatorService;
import com.knecon.fforesight.service.layoutparser.processor.services.SectionsBuilderService;
import com.knecon.fforesight.service.layoutparser.processor.services.classification.DocuMineClassificationService;
import com.knecon.fforesight.service.layoutparser.processor.services.classification.RedactManagerClassificationService;
import com.knecon.fforesight.service.layoutparser.processor.services.classification.TaasClassificationService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
@RequiredArgsConstructor
public class LayoutParsingPipeline {
private final ImageServiceResponseAdapter imageServiceResponseAdapter;
private final CvTableParsingAdapter cvTableParsingAdapter;
private final LayoutParsingStorageService layoutParsingStorageService;
private final PdfParsingService pdfParsingService;
private final SectionsBuilderService sectionsBuilderService;
private final SectionGridCreatorService sectionGridCreatorService;
private final TaasClassificationService taasClassificationService;
private final RedactManagerClassificationService redactManagerClassificationService;
private final DocuMineClassificationService docuMineClassificationService;
public LayoutParsingFinishedEvent parseLayoutAndSaveFilesToStorage(LayoutParsingRequest layoutParsingRequest) throws IOException {
long start = System.currentTimeMillis();
try (PDDocument originDocument = layoutParsingStorageService.getOriginFile(layoutParsingRequest.originFileStorageId())) {
ImageServiceResponse imageServiceResponse = new ImageServiceResponse();
if (layoutParsingRequest.imagesFileStorageId().isPresent()) {
imageServiceResponse = layoutParsingStorageService.getImagesFile(layoutParsingRequest.pageFileStorageId());
}
TableServiceResponse tableServiceResponse = new TableServiceResponse();
if (layoutParsingRequest.tablesFileStorageId().isPresent()) {
tableServiceResponse = layoutParsingStorageService.getTablesFile(layoutParsingRequest.pageFileStorageId());
}
Document documentGraph = parseLayout(layoutParsingRequest.layoutParsingType(), originDocument, imageServiceResponse, tableServiceResponse);
int numberOfPages = originDocument.getNumberOfPages();
layoutParsingStorageService.storeSectionGrid(layoutParsingRequest, sectionGridCreatorService.createSectionGrid(documentGraph));
layoutParsingStorageService.storeDocumentData(layoutParsingRequest, DocumentDataMapper.toDocumentData(documentGraph));
if (layoutParsingRequest.layoutParsingType().equals(LayoutParsingType.TAAS)) {
var researchDocumentData = TaasDocumentDataMapper.fromDocument(documentGraph);
layoutParsingStorageService.storeResearchDocumentData(layoutParsingRequest, researchDocumentData);
}
return LayoutParsingFinishedEvent.builder()
.identifier(layoutParsingRequest.identifier())
.numberOfPages(numberOfPages)
.duration(System.currentTimeMillis() - start)
.message(format("Layout parsing is finished and files have been saved with Ids:\n Structure: %s\nText: %s\nPositions: %s\nPageData: %s",
layoutParsingRequest.structureFileStorageId(),
layoutParsingRequest.textBlockFileStorageId(),
layoutParsingRequest.positionBlockFileStorageId(),
layoutParsingRequest.pageFileStorageId()))
.build();
}
}
public Document parseLayout(LayoutParsingType layoutParsingType,
PDDocument originDocument,
ImageServiceResponse imageServiceResponse,
TableServiceResponse tableServiceResponse) {
ClassificationDocument classificationDocument = pdfParsingService.parseDocument(layoutParsingType,
originDocument,
cvTableParsingAdapter.buildCvParsedTablesPerPage(tableServiceResponse),
imageServiceResponseAdapter.buildClassifiedImagesPerPage(imageServiceResponse));
switch (layoutParsingType) {
case TAAS -> taasClassificationService.classifyDocument(classificationDocument);
case DOCUMINE -> docuMineClassificationService.classifyDocument(classificationDocument);
case REDACT_MANAGER -> redactManagerClassificationService.classifyDocument(classificationDocument);
}
sectionsBuilderService.buildSections(classificationDocument);
sectionsBuilderService.addImagesToSections(classificationDocument);
return DocumentGraphFactory.buildDocumentGraph(classificationDocument);
}
public Document parseLayoutWithTimer(LayoutParsingType layoutParsingType,
PDDocument originDocument,
ImageServiceResponse imageServiceResponse,
TableServiceResponse tableServiceResponse) {
long start = System.currentTimeMillis();
ClassificationDocument classificationDocument = pdfParsingService.parseDocument(layoutParsingType,
originDocument,
cvTableParsingAdapter.buildCvParsedTablesPerPage(tableServiceResponse),
imageServiceResponseAdapter.buildClassifiedImagesPerPage(imageServiceResponse));
System.out.printf("parsed %d ms", System.currentTimeMillis() - start);
start = System.currentTimeMillis();
switch (layoutParsingType) {
case TAAS -> taasClassificationService.classifyDocument(classificationDocument);
case DOCUMINE -> docuMineClassificationService.classifyDocument(classificationDocument);
case REDACT_MANAGER -> redactManagerClassificationService.classifyDocument(classificationDocument);
}
System.out.printf(", classified %d ms", System.currentTimeMillis() - start);
start = System.currentTimeMillis();
sectionsBuilderService.buildSections(classificationDocument);
System.out.printf(", sections built %d ms", System.currentTimeMillis() - start);
start = System.currentTimeMillis();
Document document = DocumentGraphFactory.buildDocumentGraph(classificationDocument);
System.out.printf(", graph constructed %d ms", System.currentTimeMillis() - start);
return document;
}
}
@@ -1,87 +0,0 @@
package com.knecon.fforesight.service.layoutparser.processor;
import static java.lang.String.format;
import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.stereotype.Service;
import com.knecon.fforesight.service.layoutparser.internal.api.queue.LayoutParsingFinishedEvent;
import com.knecon.fforesight.service.layoutparser.internal.api.queue.LayoutParsingRequest;
import com.knecon.fforesight.service.layoutparser.processor.adapter.CvTableParsingAdapter;
import com.knecon.fforesight.service.layoutparser.processor.adapter.ImageServiceResponseAdapter;
import com.knecon.fforesight.service.layoutparser.processor.adapter.model.image.ImageServiceResponse;
import com.knecon.fforesight.service.layoutparser.processor.adapter.model.table.TableServiceResponse;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.ClassificationDocument;
import com.knecon.fforesight.service.layoutparser.processor.classification.service.ClassificationService;
import com.knecon.fforesight.service.layoutparser.processor.classification.service.PdfParsingService;
import com.knecon.fforesight.service.layoutparser.processor.classification.service.SectionsBuilderService;
import com.knecon.fforesight.service.layoutparser.processor.factory.DocumentGraphFactory;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.Document;
import com.knecon.fforesight.service.layoutparser.processor.mapper.DocumentDataMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
@RequiredArgsConstructor
public class LayoutParsingService {
private final ImageServiceResponseAdapter imageServiceResponseAdapter;
private final CvTableParsingAdapter cvTableParsingAdapter;
private final LayoutParsingStorageService layoutParsingStorageService;
private final PdfParsingService pdfParsingService;
private final ClassificationService classificationService;
private final SectionsBuilderService sectionsBuilderService;
public LayoutParsingFinishedEvent parseLayoutAndSaveFilesToStorage(LayoutParsingRequest layoutParsingRequest) throws IOException {
long start = System.currentTimeMillis();
PDDocument originDocument = layoutParsingStorageService.getOriginFile(layoutParsingRequest.originFileStorageId());
ImageServiceResponse imageServiceResponse = new ImageServiceResponse();
if (layoutParsingRequest.imagesFileStorageId().isPresent()) {
imageServiceResponse = layoutParsingStorageService.getImagesFile(layoutParsingRequest.pageFileStorageId());
}
TableServiceResponse tableServiceResponse = new TableServiceResponse();
if (layoutParsingRequest.tablesFileStorageId().isPresent()) {
tableServiceResponse = layoutParsingStorageService.getTablesFile(layoutParsingRequest.pageFileStorageId());
}
Document documentGraph = parseLayout(originDocument, imageServiceResponse, tableServiceResponse);
int numberOfPages = originDocument.getNumberOfPages();
originDocument.close();
layoutParsingStorageService.storeDocumentData(layoutParsingRequest, DocumentDataMapper.toDocumentData(documentGraph));
return LayoutParsingFinishedEvent.builder()
.identifier(layoutParsingRequest.identifier())
.numberOfPages(numberOfPages)
.duration(System.currentTimeMillis() - start)
.message(format("Layout parsing is finished and files have been saved with Ids:\n Structure: %s\nText: %s\nPositions: %s\nPageData: %s",
layoutParsingRequest.structureFileStorageId(),
layoutParsingRequest.textBlockFileStorageId(),
layoutParsingRequest.positionBlockFileStorageId(),
layoutParsingRequest.pageFileStorageId()))
.build();
}
public Document parseLayout(PDDocument originDocument, ImageServiceResponse imageServiceResponse, TableServiceResponse tableServiceResponse) {
ClassificationDocument classificationDocument = pdfParsingService.parseDocument(originDocument,
cvTableParsingAdapter.buildCvParsedTablesPerPage(tableServiceResponse),
imageServiceResponseAdapter.buildClassifiedImagesPerPage(imageServiceResponse));
classificationService.classifyDocument(classificationDocument);
sectionsBuilderService.buildSections(classificationDocument);
return DocumentGraphFactory.buildDocumentGraph(classificationDocument);
}
}
@@ -13,12 +13,14 @@ import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.section.SectionGrid;
import com.iqser.red.storage.commons.service.StorageService;
import com.knecon.fforesight.service.layoutparser.internal.api.data.AtomicPositionBlockData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.AtomicTextBlockData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.DocumentData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.DocumentTreeData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.PageData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.DocumentPositionData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.DocumentTextData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.DocumentData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.DocumentStructure;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.DocumentPage;
import com.knecon.fforesight.service.layoutparser.internal.api.data.taas.ResearchDocumentData;
import com.knecon.fforesight.service.layoutparser.internal.api.queue.LayoutParsingRequest;
import com.knecon.fforesight.service.layoutparser.processor.adapter.model.image.ImageServiceResponse;
import com.knecon.fforesight.service.layoutparser.processor.adapter.model.table.TableServiceResponse;
@@ -67,34 +69,43 @@ public class LayoutParsingStorageService {
}
public void storeDocumentData(LayoutParsingRequest layoutParsingRequest, DocumentData documentData) throws IOException {
public void storeDocumentData(LayoutParsingRequest layoutParsingRequest, DocumentData documentData) {
storageService.storeJSONObject(TenantContext.getTenantId(), layoutParsingRequest.structureFileStorageId(), documentData.getDocumentTreeData());
storageService.storeJSONObject(TenantContext.getTenantId(), layoutParsingRequest.textBlockFileStorageId(), documentData.getAtomicTextBlocks());
storageService.storeJSONObject(TenantContext.getTenantId(), layoutParsingRequest.positionBlockFileStorageId(), documentData.getAtomicPositionBlocks());
storageService.storeJSONObject(TenantContext.getTenantId(), layoutParsingRequest.pageFileStorageId(), documentData.getPages());
storageService.storeJSONObject(TenantContext.getTenantId(), layoutParsingRequest.structureFileStorageId(), documentData.getDocumentStructure());
storageService.storeJSONObject(TenantContext.getTenantId(), layoutParsingRequest.textBlockFileStorageId(), documentData.getDocumentTextData());
storageService.storeJSONObject(TenantContext.getTenantId(), layoutParsingRequest.positionBlockFileStorageId(), documentData.getDocumentPositions());
storageService.storeJSONObject(TenantContext.getTenantId(), layoutParsingRequest.pageFileStorageId(), documentData.getDocumentPages());
}
public void storeSectionGrid(LayoutParsingRequest layoutParsingRequest, SectionGrid sectionGrid) {
storageService.storeJSONObject(TenantContext.getTenantId(), layoutParsingRequest.sectionGridStorageId(), sectionGrid);
}
public void storeResearchDocumentData(LayoutParsingRequest layoutParsingRequest, ResearchDocumentData researchDocumentData) {
storageService.storeJSONObject(TenantContext.getTenantId(), layoutParsingRequest.researchDocumentStorageId(), researchDocumentData);
}
public DocumentData readDocumentData(LayoutParsingRequest layoutParsingRequest) throws IOException {
PageData[] pageData = storageService.readJSONObject(TenantContext.getTenantId(), layoutParsingRequest.pageFileStorageId(), PageData[].class);
AtomicTextBlockData[] atomicTextBlockData = storageService.readJSONObject(TenantContext.getTenantId(),
DocumentPage[] documentPageData = storageService.readJSONObject(TenantContext.getTenantId(), layoutParsingRequest.pageFileStorageId(), DocumentPage[].class);
DocumentTextData[] documentTextDataBlockData = storageService.readJSONObject(TenantContext.getTenantId(),
layoutParsingRequest.textBlockFileStorageId(),
AtomicTextBlockData[].class);
AtomicPositionBlockData[] atomicPositionBlockData = storageService.readJSONObject(TenantContext.getTenantId(),
DocumentTextData[].class);
DocumentPositionData[] atomicPositionBlockData = storageService.readJSONObject(TenantContext.getTenantId(),
layoutParsingRequest.positionBlockFileStorageId(),
AtomicPositionBlockData[].class);
DocumentTreeData tableOfContentsData = storageService.readJSONObject(TenantContext.getTenantId(),
layoutParsingRequest.structureFileStorageId(),
DocumentTreeData.class);
DocumentPositionData[].class);
DocumentStructure tableOfContentsData = storageService.readJSONObject(TenantContext.getTenantId(), layoutParsingRequest.structureFileStorageId(), DocumentStructure.class);
return DocumentData.builder()
.documentTreeData(tableOfContentsData)
.atomicPositionBlocks(atomicPositionBlockData)
.atomicTextBlocks(atomicTextBlockData)
.pages(pageData)
.documentStructure(tableOfContentsData)
.documentPositions(atomicPositionBlockData)
.documentTextData(documentTextDataBlockData)
.documentPages(documentPageData)
.build();
}
@@ -10,8 +10,8 @@ import java.util.Map;
import org.springframework.stereotype.Service;
import com.knecon.fforesight.service.layoutparser.processor.adapter.model.image.ImageServiceResponse;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.ClassificationPage;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.image.ClassifiedImage;
import com.knecon.fforesight.service.layoutparser.processor.model.ClassificationPage;
import com.knecon.fforesight.service.layoutparser.processor.model.image.ClassifiedImage;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.ImageType;
import lombok.RequiredArgsConstructor;
@@ -1,8 +0,0 @@
package com.knecon.fforesight.service.layoutparser.processor.classification.model;
public enum Orientation {
NONE,
LEFT,
RIGHT
}
@@ -1,56 +0,0 @@
package com.knecon.fforesight.service.layoutparser.processor.classification.utils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@UtilityClass
public class FileUtils {
public File createTempFile(String filenamePrefix, String filenameSuffix) throws IOException {
File tempFile = Files.createTempFile(filenamePrefix, filenameSuffix).toFile();
setRWPermissionsOnlyForOwner(tempFile);
return tempFile;
}
/**
* Deletes a file; logs a message with the reason if the deletion fails.
* This method is null-safe.
*
* @param file The file to delete. Can be null.
*/
public void deleteFile(File file) {
if (file != null) {
try {
Files.deleteIfExists(file.toPath());
} catch (IOException ex) {
log.warn("Could not delete file!", ex);
}
}
}
// We don't need to check the results of the permission setters below,
// since we're manipulating a file we created ourselves.
@SuppressWarnings({"ResultOfMethodCallIgnored", "squid:S899"})
private void setRWPermissionsOnlyForOwner(File tempFile) {
try {
tempFile.setReadable(true, true);
tempFile.setWritable(true, true);
tempFile.setExecutable(false);
} catch (SecurityException ex) {
// This should never happen since we're creating a temp file ourselves.
log.warn("Caught an exception during temp file creation. This should not happend. Check the code.", ex);
}
}
}
@@ -13,13 +13,13 @@ import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.AbstractPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.ClassificationDocument;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.ClassificationFooter;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.ClassificationHeader;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.ClassificationPage;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.image.ClassifiedImage;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.text.TextPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.model.AbstractPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.model.ClassificationDocument;
import com.knecon.fforesight.service.layoutparser.processor.model.ClassificationFooter;
import com.knecon.fforesight.service.layoutparser.processor.model.ClassificationHeader;
import com.knecon.fforesight.service.layoutparser.processor.model.ClassificationPage;
import com.knecon.fforesight.service.layoutparser.processor.model.image.ClassifiedImage;
import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.graph.DocumentTree;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.Document;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.Footer;
@@ -81,8 +81,9 @@ public class DocumentGraphFactory {
page.getMainBody().add(node);
List<TextPageBlock> textBlocks = new ArrayList<>(textBlocksToMerge);
List<TextPageBlock> textBlocks = new ArrayList<>();
textBlocks.add(originalTextBlock);
textBlocks.addAll(textBlocksToMerge);
AtomicTextBlock textBlock = context.textBlockFactory.buildAtomicTextBlock(TextPositionOperations.mergeAndSortTextPositionSequenceByYThenX(textBlocks), node, context, page);
List<Integer> treeId = context.documentTree.createNewChildEntryAndReturnId(parentNode, node);
node.setLeafTextBlock(textBlock);
@@ -4,6 +4,8 @@ import java.awt.geom.Rectangle2D;
import java.util.Collections;
import java.util.List;
import com.knecon.fforesight.service.layoutparser.processor.graph.Boundary;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
@@ -16,7 +18,9 @@ public class SearchTextWithTextPositionDto {
String searchText;
List<Integer> lineBreaks;
List<Integer> stringCoordsToPositionCoords;
List<Integer> stringIdxToPositionIdx;
List<Boundary> boldTextBoundaries;
List<Boundary> italicTextBoundaries;
List<Rectangle2D> positions;
@@ -26,7 +30,7 @@ public class SearchTextWithTextPositionDto {
.searchText("")
.lineBreaks(Collections.emptyList())
.positions(Collections.emptyList())
.stringCoordsToPositionCoords(Collections.emptyList())
.stringIdxToPositionIdx(Collections.emptyList())
.build();
}
@@ -2,13 +2,16 @@ package com.knecon.fforesight.service.layoutparser.processor.factory;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.text.RedTextPosition;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.text.TextDirection;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.text.TextPositionSequence;
import com.knecon.fforesight.service.layoutparser.processor.model.text.RedTextPosition;
import com.knecon.fforesight.service.layoutparser.processor.model.text.TextDirection;
import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPositionSequence;
import com.knecon.fforesight.service.layoutparser.processor.graph.Boundary;
import lombok.experimental.UtilityClass;
@@ -24,7 +27,7 @@ public class SearchTextWithTextPositionFactory {
public final int MAX_HYPHEN_LINEBREAK_DISTANCE = 3;
public SearchTextWithTextPositionDto buildSearchTextToTextPositionModel(List<TextPositionSequence> sequences) {
public SearchTextWithTextPositionDto buildSearchTextToTextPositionDto(List<TextPositionSequence> sequences) {
if (sequences.isEmpty() || sequences.stream().allMatch(sequence -> sequence.getTextPositions().isEmpty())) {
return SearchTextWithTextPositionDto.empty();
@@ -69,8 +72,10 @@ public class SearchTextWithTextPositionFactory {
return SearchTextWithTextPositionDto.builder()
.searchText(context.stringBuilder.toString())
.lineBreaks(context.lineBreaksStringIdx)
.stringCoordsToPositionCoords(context.stringIdxToPositionIdx)
.stringIdxToPositionIdx(context.stringIdxToPositionIdx)
.positions(positions)
.boldTextBoundaries(mergeToBoundaries(context.boldTextsStringIdx))
.italicTextBoundaries(mergeToBoundaries(context.italicTextStringIdx))
.build();
}
@@ -82,6 +87,8 @@ public class SearchTextWithTextPositionFactory {
// unicode characters with more than 16-bit encoding have a length > 1 in java strings
for (int j = 0; j < currentTextPosition.getUnicode().length(); j++) {
context.stringIdxToPositionIdx.add(context.positionIdx);
addTextPositionWithFontType(currentTextPosition, "bold", context.boldTextsStringIdx, context.stringIdx);
addTextPositionWithFontType(currentTextPosition, "italic", context.italicTextStringIdx, context.stringIdx);
}
context.stringIdx += currentTextPosition.getUnicode().length();
}
@@ -103,6 +110,33 @@ public class SearchTextWithTextPositionFactory {
return context.stringIdx - context.lastHyphenIdx < MAX_HYPHEN_LINEBREAK_DISTANCE;
}
private static List<Boundary> mergeToBoundaries(List<Integer> integers) {
if (integers.isEmpty()) {
return Collections.emptyList();
}
List<Boundary> boundaries = new LinkedList<>();
int start = integers.get(0);
int end = integers.get(0) + 1;
for (int current : integers) {
if (current > end + 1) {
boundaries.add(new Boundary(start, end));
start = current;
}
end = current + 1;
}
if (boundaries.isEmpty())
boundaries.add(new Boundary(start, end));
return boundaries;
}
private static void addTextPositionWithFontType(RedTextPosition currentTextPosition, String fontType, List<Integer> fontTypePositions, int stringIdx) {
if (currentTextPosition.getFontName().toLowerCase(Locale.ROOT).contains(fontType)) {
fontTypePositions.add(stringIdx);
}
}
private boolean isLineBreak(RedTextPosition currentTextPosition, RedTextPosition previousTextPosition) {
@@ -173,6 +207,8 @@ public class SearchTextWithTextPositionFactory {
List<Integer> stringIdxToPositionIdx = new LinkedList<>();
List<Integer> lineBreaksStringIdx = new LinkedList<>();
List<Integer> boldTextsStringIdx = new LinkedList<>();
List<Integer> italicTextStringIdx = new LinkedList<>();
StringBuilder stringBuilder = new StringBuilder();
int stringIdx;
@@ -10,10 +10,10 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.AbstractPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.image.ClassifiedImage;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.table.TablePageBlock;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.text.TextPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.model.AbstractPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.model.image.ClassifiedImage;
import com.knecon.fforesight.service.layoutparser.processor.model.table.TablePageBlock;
import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.GenericSemanticNode;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.Page;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.Section;
@@ -80,7 +80,7 @@ public class SectionNodeFactory {
remainingBlocks.removeAll(alreadyMerged);
if (abstractPageBlock instanceof TextPageBlock) {
List<TextPageBlock> textBlocks = findTextBlocksWithSameClassificationAndAlignsY(abstractPageBlock, remainingBlocks);
List<TextPageBlock> textBlocks = findTextBlocksWithSameClassificationAndAlignsY((TextPageBlock) abstractPageBlock, remainingBlocks);
alreadyMerged.addAll(textBlocks);
DocumentGraphFactory.addParagraphOrHeadline(section, (TextPageBlock) abstractPageBlock, context, textBlocks);
} else if (abstractPageBlock instanceof TablePageBlock tablePageBlock) {
@@ -123,7 +123,7 @@ public class SectionNodeFactory {
List<AbstractPageBlock> previousList = splitList.get(i - 1);
AbstractPageBlock lastPageBlockInPreviousList = previousList.get(previousList.size() - 1);
if (lastPageBlockInPreviousList.isHeadline()) {
previousList.remove(i - 1);
previousList.remove(previousList.size() - 1);
splitList.get(i).add(0, lastPageBlockInPreviousList);
}
}
@@ -162,7 +162,7 @@ public class SectionNodeFactory {
}
private List<TextPageBlock> findTextBlocksWithSameClassificationAndAlignsY(AbstractPageBlock atc, List<AbstractPageBlock> pageBlocks) {
private List<TextPageBlock> findTextBlocksWithSameClassificationAndAlignsY(TextPageBlock atc, List<AbstractPageBlock> pageBlocks) {
return pageBlocks.stream()
.filter(abstractTextContainer -> !abstractTextContainer.equals(atc))
@@ -170,6 +170,7 @@ public class SectionNodeFactory {
.filter(abstractTextContainer -> abstractTextContainer instanceof TextPageBlock)
.filter(abstractTextContainer -> abstractTextContainer.intersectsY(atc))
.map(abstractTextContainer -> (TextPageBlock) abstractTextContainer)
.filter(abstractTextContainer -> abstractTextContainer.getDir() == atc.getDir())
.toList();
}
@@ -7,10 +7,10 @@ import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.AbstractPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.table.Cell;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.table.TablePageBlock;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.text.TextPositionSequence;
import com.knecon.fforesight.service.layoutparser.processor.model.AbstractPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.model.table.Cell;
import com.knecon.fforesight.service.layoutparser.processor.model.table.TablePageBlock;
import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPositionSequence;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.GenericSemanticNode;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.Page;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.SemanticNode;
@@ -2,7 +2,7 @@ package com.knecon.fforesight.service.layoutparser.processor.factory;
import java.util.List;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.text.TextPositionSequence;
import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPositionSequence;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.Page;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.SemanticNode;
import com.knecon.fforesight.service.layoutparser.processor.graph.textblock.AtomicTextBlock;
@@ -26,12 +26,33 @@ public class TextBlockFactory {
public AtomicTextBlock buildAtomicTextBlock(List<TextPositionSequence> sequences, SemanticNode parent, Integer numberOnPage, Page page) {
SearchTextWithTextPositionDto searchTextWithTextPositionDto = SearchTextWithTextPositionFactory.buildSearchTextToTextPositionModel(sequences);
SearchTextWithTextPositionDto searchTextWithTextPositionDto = SearchTextWithTextPositionFactory.buildSearchTextToTextPositionDto(sequences);
int offset = stringOffset;
stringOffset += searchTextWithTextPositionDto.getSearchText().length();
long idx = textBlockIdx;
textBlockIdx++;
return AtomicTextBlock.fromSearchTextWithTextPositionDto(searchTextWithTextPositionDto, parent, offset, idx, numberOnPage, page);
String orientation;
int textDirection;
if (sequences.isEmpty()) {
orientation = null;
textDirection = 0;
} else {
orientation = sequences.get(0).getDir().toString();
textDirection = sequences.get(0).getRotation();
}
return AtomicTextBlock.fromSearchTextWithTextPosition(searchTextWithTextPositionDto.getSearchText(),
searchTextWithTextPositionDto.getLineBreaks(),
searchTextWithTextPositionDto.getBoldTextBoundaries(),
searchTextWithTextPositionDto.getItalicTextBoundaries(),
searchTextWithTextPositionDto.getPositions(),
searchTextWithTextPositionDto.getStringIdxToPositionIdx(),
idx,
parent,
numberOnPage,
page,
offset,
orientation,
textDirection);
}
@@ -5,8 +5,7 @@ import static java.lang.String.format;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import com.knecon.fforesight.service.layoutparser.processor.graph.textblock.TextBlock;
import java.util.stream.IntStream;
import lombok.EqualsAndHashCode;
import lombok.Setter;
@@ -109,6 +108,10 @@ public class Boundary implements Comparable<Boundary> {
return splitBoundaries;
}
public IntStream intStream() {
return IntStream.range(start, end);
}
public static Boundary merge(Collection<Boundary> boundaries) {
@@ -138,26 +141,4 @@ public class Boundary implements Comparable<Boundary> {
return 0;
}
/**
* shrinks the boundary, such that textBlock.subSequence(boundary) returns a string without whitespaces.
*
* @param textBlock TextBlock to check whitespaces against
* @return boundary
*/
public Boundary trim(TextBlock textBlock) {
int trimmedStart = this.start;
while (Character.isWhitespace(textBlock.charAt(trimmedStart))) {
trimmedStart++;
}
int trimmedEnd = this.end;
while (Character.isWhitespace(textBlock.charAt(trimmedEnd - 1))) {
trimmedEnd--;
}
return new Boundary(trimmedStart, Math.max(trimmedEnd, trimmedStart));
}
}
@@ -7,7 +7,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.stream.Stream;
import com.knecon.fforesight.service.layoutparser.internal.api.data.NodeType;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.NodeType;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.Document;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.GenericSemanticNode;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.SemanticNode;
@@ -11,7 +11,7 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.amazonaws.services.kms.model.NotFoundException;
import com.knecon.fforesight.service.layoutparser.internal.api.data.NodeType;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.NodeType;
import com.knecon.fforesight.service.layoutparser.processor.graph.DocumentTree;
import com.knecon.fforesight.service.layoutparser.processor.graph.entity.RedactionEntity;
import com.knecon.fforesight.service.layoutparser.processor.graph.textblock.TextBlock;
@@ -4,7 +4,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.knecon.fforesight.service.layoutparser.internal.api.data.NodeType;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.NodeType;
import com.knecon.fforesight.service.layoutparser.processor.graph.DocumentTree;
import com.knecon.fforesight.service.layoutparser.processor.graph.entity.RedactionEntity;
import com.knecon.fforesight.service.layoutparser.processor.graph.textblock.TextBlock;
@@ -4,7 +4,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.knecon.fforesight.service.layoutparser.internal.api.data.NodeType;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.NodeType;
import com.knecon.fforesight.service.layoutparser.processor.graph.DocumentTree;
import com.knecon.fforesight.service.layoutparser.processor.graph.entity.RedactionEntity;
import com.knecon.fforesight.service.layoutparser.processor.graph.textblock.TextBlock;
@@ -4,7 +4,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.knecon.fforesight.service.layoutparser.internal.api.data.NodeType;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.NodeType;
import com.knecon.fforesight.service.layoutparser.processor.graph.DocumentTree;
import com.knecon.fforesight.service.layoutparser.processor.graph.entity.RedactionEntity;
import com.knecon.fforesight.service.layoutparser.processor.graph.textblock.TextBlock;
@@ -8,7 +8,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import com.knecon.fforesight.service.layoutparser.internal.api.data.NodeType;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.NodeType;
import com.knecon.fforesight.service.layoutparser.processor.graph.DocumentTree;
import com.knecon.fforesight.service.layoutparser.processor.graph.entity.RedactionEntity;
import com.knecon.fforesight.service.layoutparser.processor.graph.textblock.TextBlock;
@@ -1,5 +1,7 @@
package com.knecon.fforesight.service.layoutparser.processor.graph.nodes;
import java.util.Locale;
public enum ImageType {
LOGO,
FORMULA,
@@ -10,7 +12,7 @@ public enum ImageType {
public static ImageType fromString(String imageType) {
return switch (imageType.toLowerCase()) {
return switch (imageType.toLowerCase(Locale.ROOT)) {
case "logo" -> ImageType.LOGO;
case "formula" -> ImageType.FORMULA;
case "signature" -> ImageType.SIGNATURE;
@@ -5,7 +5,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.ClassificationPage;
import com.knecon.fforesight.service.layoutparser.processor.model.ClassificationPage;
import com.knecon.fforesight.service.layoutparser.processor.graph.entity.RedactionEntity;
import com.knecon.fforesight.service.layoutparser.processor.graph.textblock.TextBlock;
import com.knecon.fforesight.service.layoutparser.processor.graph.textblock.TextBlockCollector;
@@ -4,7 +4,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.knecon.fforesight.service.layoutparser.internal.api.data.NodeType;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.NodeType;
import com.knecon.fforesight.service.layoutparser.processor.graph.DocumentTree;
import com.knecon.fforesight.service.layoutparser.processor.graph.entity.RedactionEntity;
import com.knecon.fforesight.service.layoutparser.processor.graph.textblock.TextBlock;
@@ -4,7 +4,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.knecon.fforesight.service.layoutparser.internal.api.data.NodeType;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.NodeType;
import com.knecon.fforesight.service.layoutparser.processor.graph.DocumentTree;
import com.knecon.fforesight.service.layoutparser.processor.graph.entity.RedactionEntity;
import com.knecon.fforesight.service.layoutparser.processor.graph.textblock.TextBlock;
@@ -3,21 +3,23 @@ package com.knecon.fforesight.service.layoutparser.processor.graph.nodes;
import static java.lang.String.format;
import java.awt.geom.Rectangle2D;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.knecon.fforesight.service.layoutparser.internal.api.data.NodeType;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.NodeType;
import com.knecon.fforesight.service.layoutparser.processor.graph.Boundary;
import com.knecon.fforesight.service.layoutparser.processor.graph.DocumentTree;
import com.knecon.fforesight.service.layoutparser.processor.graph.entity.EntityType;
import com.knecon.fforesight.service.layoutparser.processor.graph.entity.RedactionEntity;
import com.knecon.fforesight.service.layoutparser.processor.graph.textblock.AtomicTextBlock;
import com.knecon.fforesight.service.layoutparser.processor.graph.textblock.TextBlock;
import com.knecon.fforesight.service.layoutparser.processor.services.RectangleTransformations;
import com.knecon.fforesight.service.layoutparser.processor.utils.RectangleTransformations;
public interface SemanticNode {
@@ -59,6 +61,12 @@ public interface SemanticNode {
}
default Page getFirstPage() {
return getTextBlock().getPages().stream().min(Comparator.comparingInt(Page::getNumber)).orElseThrow(() -> new IllegalStateException("SemanticNode has no Page!"));
}
/**
* Each AtomicTextBlock is assigned a page, so to get the pages for this boundary, it collects the PageNodes from each AtomicTextBlock belonging to this node's TextBlock.
*
@@ -278,7 +286,7 @@ public interface SemanticNode {
*/
default boolean containsStringIgnoreCase(String string) {
return getTextBlock().getSearchText().toLowerCase().contains(string.toLowerCase());
return getTextBlock().getSearchText().toLowerCase(Locale.ROOT).contains(string.toLowerCase(Locale.ROOT));
}
@@ -306,7 +314,6 @@ public interface SemanticNode {
}
/**
* This function is used during insertion of EntityNodes into the graph, it checks if the boundary of the RedactionEntity intersects or even contains the RedactionEntity.
* It sets the fields accordingly and recursively calls this function on all its children.
@@ -5,11 +5,12 @@ import static java.lang.String.format;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import com.knecon.fforesight.service.layoutparser.internal.api.data.NodeType;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.NodeType;
import com.knecon.fforesight.service.layoutparser.processor.graph.DocumentTree;
import com.knecon.fforesight.service.layoutparser.processor.graph.entity.RedactionEntity;
import com.knecon.fforesight.service.layoutparser.processor.graph.textblock.TextBlock;
@@ -67,7 +68,7 @@ public class Table implements SemanticNode {
*/
public boolean rowContainsStringsIgnoreCase(Integer row, List<String> strings) {
String rowText = streamRow(row).map(TableCell::getTextBlock).collect(new TextBlockCollector()).getSearchText().toLowerCase();
String rowText = streamRow(row).map(TableCell::getTextBlock).collect(new TextBlockCollector()).getSearchText().toLowerCase(Locale.ROOT);
return strings.stream().map(String::toLowerCase).allMatch(rowText::contains);
}
@@ -7,7 +7,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import com.knecon.fforesight.service.layoutparser.internal.api.data.NodeType;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.NodeType;
import com.knecon.fforesight.service.layoutparser.processor.graph.DocumentTree;
import com.knecon.fforesight.service.layoutparser.processor.graph.entity.RedactionEntity;
import com.knecon.fforesight.service.layoutparser.processor.graph.textblock.TextBlock;
@@ -3,6 +3,7 @@ package com.knecon.fforesight.service.layoutparser.processor.graph.textblock;
import static java.lang.String.format;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -10,9 +11,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.knecon.fforesight.service.layoutparser.internal.api.data.AtomicPositionBlockData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.AtomicTextBlockData;
import com.knecon.fforesight.service.layoutparser.processor.factory.SearchTextWithTextPositionDto;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.DocumentPositionData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.DocumentTextData;
import com.knecon.fforesight.service.layoutparser.processor.graph.Boundary;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.Page;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.SemanticNode;
@@ -38,11 +38,20 @@ public class AtomicTextBlock implements TextBlock {
//string coordinates
Boundary boundary;
String searchText;
List<Integer> lineBreaks;
@Builder.Default
List<Integer> lineBreaks = new ArrayList<>();
@Builder.Default
List<Boundary> boldTextBoundaries = new ArrayList<>();
@Builder.Default
List<Boundary> italicTextBoundaries = new ArrayList<>();
String orientation;
int textDirection;
//position coordinates
List<Integer> stringIdxToPositionIdx;
List<Rectangle2D> positions;
@Builder.Default
List<Integer> stringIdxToPositionIdx = new ArrayList<>();
@Builder.Default
List<Rectangle2D> positions = new ArrayList<>();
@EqualsAndHashCode.Exclude
SemanticNode parent;
@@ -55,23 +64,34 @@ public class AtomicTextBlock implements TextBlock {
}
public static AtomicTextBlock fromSearchTextWithTextPositionDto(SearchTextWithTextPositionDto searchTextWithTextPositionDto,
SemanticNode parent,
int stringOffset,
Long textBlockIdx,
Integer numberOnPage,
Page page) {
public static AtomicTextBlock fromSearchTextWithTextPosition(String searchText,
List<Integer> lineBreaks,
List<Boundary> boldTextBoundaries,
List<Boundary> italicTextBoundaries,
List<Rectangle2D> positions,
List<Integer> stringIdxToPositionIdx,
long idx,
SemanticNode parent,
int numberOnPage,
Page page,
int offset,
String orientation,
int textDirection) {
return AtomicTextBlock.builder()
.id(textBlockIdx)
.id(idx)
.parent(parent)
.searchText(searchTextWithTextPositionDto.getSearchText())
.searchText(searchText)
.numberOnPage(numberOnPage)
.page(page)
.lineBreaks(searchTextWithTextPositionDto.getLineBreaks())
.positions(searchTextWithTextPositionDto.getPositions())
.stringIdxToPositionIdx(searchTextWithTextPositionDto.getStringCoordsToPositionCoords())
.boundary(new Boundary(stringOffset, stringOffset + searchTextWithTextPositionDto.getSearchText().length()))
.lineBreaks(lineBreaks)
.boldTextBoundaries(boldTextBoundaries)
.italicTextBoundaries(italicTextBoundaries)
.positions(positions)
.stringIdxToPositionIdx(stringIdxToPositionIdx)
.boundary(new Boundary(offset, offset + searchText.length()))
.textDirection(textDirection)
.orientation(orientation)
.build();
}
@@ -82,30 +102,27 @@ public class AtomicTextBlock implements TextBlock {
.id(textBlockIdx)
.boundary(new Boundary(stringOffset, stringOffset))
.searchText("")
.lineBreaks(Collections.emptyList())
.page(page)
.numberOnPage(numberOnPage)
.stringIdxToPositionIdx(Collections.emptyList())
.positions(Collections.emptyList())
.parent(parent)
.build();
}
public static AtomicTextBlock fromAtomicTextBlockData(AtomicTextBlockData atomicTextBlockData,
AtomicPositionBlockData atomicPositionBlockData,
public static AtomicTextBlock fromAtomicTextBlockData(DocumentTextData documentTextData,
DocumentPositionData documentPositionData,
SemanticNode parent,
Page page) {
return AtomicTextBlock.builder()
.id(atomicTextBlockData.getId())
.numberOnPage(atomicTextBlockData.getNumberOnPage())
.id(documentTextData.getId())
.numberOnPage(documentTextData.getNumberOnPage())
.page(page)
.boundary(new Boundary(atomicTextBlockData.getStart(), atomicTextBlockData.getEnd()))
.searchText(atomicTextBlockData.getSearchText())
.lineBreaks(Arrays.stream(atomicTextBlockData.getLineBreaks()).boxed().toList())
.stringIdxToPositionIdx(Arrays.stream(atomicPositionBlockData.getStringIdxToPositionIdx()).boxed().toList())
.positions(toRectangle2DList(atomicPositionBlockData.getPositions()))
.boundary(new Boundary(documentTextData.getStart(), documentTextData.getEnd()))
.searchText(documentTextData.getSearchText())
.lineBreaks(Arrays.stream(documentTextData.getLineBreaks()).boxed().toList())
.stringIdxToPositionIdx(Arrays.stream(documentPositionData.getStringIdxToPositionIdx()).boxed().toList())
.positions(toRectangle2DList(documentPositionData.getPositions()))
.parent(parent)
.build();
}
@@ -191,7 +208,7 @@ public class AtomicTextBlock implements TextBlock {
List<Rectangle2D> rectanglesPerLine = stringBoundary.split(getAllLineBreaksInBoundary(stringBoundary))
.stream()
.map(this::getPositions)
.map(RectangleTransformations::rectangleUnionWithGaps)
.map(RectangleTransformations::rectangleBBoxWithGaps)
.flatMap(Collection::stream)
.toList();
Map<Page, List<Rectangle2D>> rectanglePerLinePerPage = new HashMap<>();
@@ -3,6 +3,7 @@ package com.knecon.fforesight.service.layoutparser.processor.graph.textblock;
import static java.lang.String.format;
import java.awt.geom.Rectangle2D;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
@@ -182,4 +183,38 @@ public class ConcatenatedTextBlock implements TextBlock {
return getSearchText();
}
@Override
public List<Boundary> getBoldTextBoundaries() {
return getAtomicTextBlocks().stream().map(AtomicTextBlock::getBoldTextBoundaries).flatMap(Collection::stream).toList();
}
@Override
public List<Boundary> getItalicTextBoundaries() {
return getAtomicTextBlocks().stream().map(AtomicTextBlock::getItalicTextBoundaries).flatMap(Collection::stream).toList();
}
@Override
public String getOrientation() {
if (atomicTextBlocks.isEmpty()) {
return "";
}
return atomicTextBlocks.get(0).getOrientation();
}
@Override
public int getTextDirection() {
if (atomicTextBlocks.isEmpty()) {
return 0;
}
return atomicTextBlocks.get(0).getTextDirection();
}
}
@@ -21,6 +21,18 @@ public interface TextBlock extends CharSequence {
List<AtomicTextBlock> getAtomicTextBlocks();
List<Boundary> getBoldTextBoundaries();
List<Boundary> getItalicTextBoundaries();
String getOrientation();
int getTextDirection();
Boundary getBoundary();
@@ -1,15 +1,17 @@
package com.knecon.fforesight.service.layoutparser.processor.mapper;
package com.knecon.fforesight.service.layoutparser.processor.mapper.redaction;
import java.awt.geom.Rectangle2D;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import com.knecon.fforesight.service.layoutparser.internal.api.data.AtomicPositionBlockData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.AtomicTextBlockData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.DocumentData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.PageData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.DocumentTreeData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.DocumentData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.DocumentPage;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.DocumentPositionData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.DocumentStructure;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.DocumentTextData;
import com.knecon.fforesight.service.layoutparser.processor.graph.DocumentTree;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.Document;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.Image;
@@ -26,36 +28,38 @@ public class DocumentDataMapper {
public DocumentData toDocumentData(Document document) {
List<AtomicTextBlockData> atomicTextBlockData = document.streamTerminalTextBlocksInOrder()
List<DocumentTextData> documentTextData = document.streamTerminalTextBlocksInOrder()
.flatMap(textBlock -> textBlock.getAtomicTextBlocks().stream())
.distinct()
.map(DocumentDataMapper::toAtomicTextBlockData)
.toList();
List<AtomicPositionBlockData> atomicPositionBlockData = document.streamTerminalTextBlocksInOrder()
List<DocumentPositionData> atomicPositionBlockData = document.streamTerminalTextBlocksInOrder()
.flatMap(textBlock -> textBlock.getAtomicTextBlocks().stream())
.distinct()
.map(DocumentDataMapper::toAtomicPositionBlockData)
.toList();
List<PageData> pageData = document.getPages().stream().map(DocumentDataMapper::toPageData).toList();
DocumentTreeData tableOfContentsData = toDocumentTreeData(document.getDocumentTree());
Set<Long> nonEmptyTextBlocks = documentTextData.stream().mapToLong(DocumentTextData::getId).boxed().collect(Collectors.toSet());
List<DocumentPage> documentPageData = document.getPages().stream().map(DocumentDataMapper::toPageData).toList();
DocumentStructure tableOfContentsData = toDocumentTreeData(document.getDocumentTree());
return DocumentData.builder()
.atomicTextBlocks(atomicTextBlockData.toArray(new AtomicTextBlockData[0]))
.atomicPositionBlocks(atomicPositionBlockData.toArray(new AtomicPositionBlockData[0]))
.pages(pageData.toArray(new PageData[0]))
.documentTreeData(tableOfContentsData)
.documentTextData(documentTextData.toArray(new DocumentTextData[0]))
.documentPositions(atomicPositionBlockData.toArray(new DocumentPositionData[0]))
.documentPages(documentPageData.toArray(new DocumentPage[0]))
.documentStructure(tableOfContentsData)
.build();
}
private DocumentTreeData toDocumentTreeData(DocumentTree documentTree) {
private DocumentStructure toDocumentTreeData(DocumentTree documentTree) {
return new DocumentTreeData(toEntryData(documentTree.getRoot()));
return new DocumentStructure(toEntryData(documentTree.getRoot()));
}
private DocumentTreeData.EntryData toEntryData(DocumentTree.Entry entry) {
private DocumentStructure.EntryData toEntryData(DocumentTree.Entry entry) {
Long[] atomicTextBlocks;
@@ -72,7 +76,7 @@ public class DocumentDataMapper {
default -> new HashMap<>();
};
return DocumentTreeData.EntryData.builder()
return DocumentStructure.EntryData.builder()
.treeId(toPrimitiveIntArray(entry.getTreeId()))
.children(entry.getChildren().stream().map(DocumentDataMapper::toEntryData).toList())
.type(entry.getType())
@@ -89,15 +93,15 @@ public class DocumentDataMapper {
}
private PageData toPageData(Page p) {
private DocumentPage toPageData(Page p) {
return PageData.builder().rotation(p.getRotation()).height(p.getHeight()).width(p.getWidth()).number(p.getNumber()).build();
return DocumentPage.builder().rotation(p.getRotation()).height(p.getHeight()).width(p.getWidth()).number(p.getNumber()).build();
}
private AtomicTextBlockData toAtomicTextBlockData(AtomicTextBlock atomicTextBlock) {
private DocumentTextData toAtomicTextBlockData(AtomicTextBlock atomicTextBlock) {
return AtomicTextBlockData.builder()
return DocumentTextData.builder()
.id(atomicTextBlock.getId())
.page(atomicTextBlock.getPage().getNumber().longValue())
.searchText(atomicTextBlock.getSearchText())
@@ -109,9 +113,9 @@ public class DocumentDataMapper {
}
private AtomicPositionBlockData toAtomicPositionBlockData(AtomicTextBlock atomicTextBlock) {
private DocumentPositionData toAtomicPositionBlockData(AtomicTextBlock atomicTextBlock) {
return AtomicPositionBlockData.builder()
return DocumentPositionData.builder()
.id(atomicTextBlock.getId())
.positions(toPrimitiveFloatMatrix(atomicTextBlock.getPositions()))
.stringIdxToPositionIdx(toPrimitiveIntArray(atomicTextBlock.getStringIdxToPositionIdx()))
@@ -1,4 +1,4 @@
package com.knecon.fforesight.service.layoutparser.processor.mapper;
package com.knecon.fforesight.service.layoutparser.processor.mapper.redaction;
import java.util.Arrays;
import java.util.HashSet;
@@ -7,11 +7,11 @@ import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import com.knecon.fforesight.service.layoutparser.internal.api.data.AtomicPositionBlockData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.AtomicTextBlockData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.DocumentData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.DocumentTreeData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.PageData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.DocumentPositionData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.DocumentTextData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.DocumentData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.DocumentStructure;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.DocumentPage;
import com.knecon.fforesight.service.layoutparser.processor.graph.DocumentTree;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.Document;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.Footer;
@@ -39,23 +39,23 @@ public class DocumentGraphMapper {
DocumentTree documentTree = new DocumentTree(document);
Context context = new Context(documentData, documentTree);
context.pages.addAll(Arrays.stream(documentData.getPages()).map(DocumentGraphMapper::buildPage).toList());
context.pages.addAll(Arrays.stream(documentData.getDocumentPages()).map(DocumentGraphMapper::buildPage).toList());
context.documentTree.getRoot().getChildren().addAll(buildEntries(documentData.getDocumentTreeData().getRoot().getChildren(), context));
context.documentTree.getRoot().getChildren().addAll(buildEntries(documentData.getDocumentStructure().getRoot().getChildren(), context));
document.setDocumentTree(context.documentTree);
document.setPages(new HashSet<>(context.pages));
document.setNumberOfPages(documentData.getPages().length);
document.setNumberOfPages(documentData.getDocumentPages().length);
document.setTextBlock(document.getTextBlock());
return document;
}
private List<DocumentTree.Entry> buildEntries(List<DocumentTreeData.EntryData> entries, Context context) {
private List<DocumentTree.Entry> buildEntries(List<DocumentStructure.EntryData> entries, Context context) {
List<DocumentTree.Entry> newEntries = new LinkedList<>();
for (DocumentTreeData.EntryData entryData : entries) {
for (DocumentStructure.EntryData entryData : entries) {
List<Page> pages = Arrays.stream(entryData.getPageNumbers()).map(pageNumber -> getPage(pageNumber, context)).toList();
@@ -154,14 +154,14 @@ public class DocumentGraphMapper {
private AtomicTextBlock getAtomicTextBlock(Context context, SemanticNode parent, Long atomicTextBlockId) {
return AtomicTextBlock.fromAtomicTextBlockData(context.atomicTextBlockData.get(Math.toIntExact(atomicTextBlockId)),
return AtomicTextBlock.fromAtomicTextBlockData(context.documentTextDataBlockData.get(Math.toIntExact(atomicTextBlockId)),
context.atomicPositionBlockData.get(Math.toIntExact(atomicTextBlockId)),
parent,
getPage(context.atomicTextBlockData.get(Math.toIntExact(atomicTextBlockId)).getPage(), context));
getPage(context.documentTextDataBlockData.get(Math.toIntExact(atomicTextBlockId)).getPage(), context));
}
private Page buildPage(PageData p) {
private Page buildPage(DocumentPage p) {
return Page.builder().rotation(p.getRotation()).height(p.getHeight()).width(p.getWidth()).number(p.getNumber()).mainBody(new LinkedList<>()).build();
}
@@ -180,16 +180,16 @@ public class DocumentGraphMapper {
private final DocumentTree documentTree;
private final List<Page> pages;
private final List<AtomicTextBlockData> atomicTextBlockData;
private final List<AtomicPositionBlockData> atomicPositionBlockData;
private final List<DocumentTextData> documentTextDataBlockData;
private final List<DocumentPositionData> atomicPositionBlockData;
Context(DocumentData documentData, DocumentTree documentTree) {
this.documentTree = documentTree;
this.pages = new LinkedList<>();
this.atomicTextBlockData = Arrays.stream(documentData.getAtomicTextBlocks()).toList();
this.atomicPositionBlockData = Arrays.stream(documentData.getAtomicPositionBlocks()).toList();
this.documentTextDataBlockData = Arrays.stream(documentData.getDocumentTextData()).toList();
this.atomicPositionBlockData = Arrays.stream(documentData.getDocumentPositions()).toList();
}
@@ -1,4 +1,4 @@
package com.knecon.fforesight.service.layoutparser.processor.mapper;
package com.knecon.fforesight.service.layoutparser.processor.mapper.redaction;
import java.awt.geom.Rectangle2D;
import java.util.Arrays;
@@ -10,7 +10,7 @@ import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.Image;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.ImageType;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.TableCell;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.Table;
import com.knecon.fforesight.service.layoutparser.processor.services.RectangleTransformations;
import com.knecon.fforesight.service.layoutparser.processor.utils.RectangleTransformations;
public class PropertiesMapper {
@@ -0,0 +1,132 @@
package com.knecon.fforesight.service.layoutparser.processor.mapper.taas;
import java.awt.geom.Rectangle2D;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;
import com.knecon.fforesight.service.layoutparser.internal.api.data.redaction.NodeType;
import com.knecon.fforesight.service.layoutparser.internal.api.data.taas.ParagraphData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.taas.Range;
import com.knecon.fforesight.service.layoutparser.internal.api.data.taas.ResearchDocumentData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.taas.RowData;
import com.knecon.fforesight.service.layoutparser.internal.api.data.taas.StructureObject;
import com.knecon.fforesight.service.layoutparser.internal.api.data.taas.TableData;
import com.knecon.fforesight.service.layoutparser.processor.graph.Boundary;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.Document;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.Page;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.SemanticNode;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.Table;
import com.knecon.fforesight.service.layoutparser.processor.graph.nodes.TableCell;
import com.knecon.fforesight.service.layoutparser.processor.graph.textblock.TextBlock;
public class TaasDocumentDataMapper {
public static ResearchDocumentData fromDocument(Document document) {
AtomicInteger structureObjectNumber = new AtomicInteger();
List<StructureObject> structureObjects = document.streamAllSubNodes()
.filter(node -> !node.getType().equals(NodeType.TABLE_CELL))
.filter(node -> !node.getType().equals(NodeType.SECTION))
.map(node -> {
if (node.getType().equals(NodeType.TABLE)) {
return TaasDocumentDataMapper.fromTableWithTableData((Table) node, structureObjectNumber.getAndIncrement());
} else {
return TaasDocumentDataMapper.fromSemanticNodeWithParagraphData(node, structureObjectNumber.getAndIncrement());
}
})
.toList();
return ResearchDocumentData.builder().structureObjects(structureObjects).build();
}
public static ParagraphData fromTextBlock(String classification, TextBlock textBlock) {
return ParagraphData.builder()
.boldTextBoundaries(textBlock.getBoldTextBoundaries().stream().map(b -> new Range(b.start(), b.end())).toList())
.italicTextBoundaries(textBlock.getItalicTextBoundaries().stream().map(b -> new Range(b.start(), b.end())).toList())
.text(textBlock.getSearchText())
.linebreaks(textBlock.getLineBreaks())
.classification(classification)
.orientation(textBlock.getOrientation())
.textDirection(textBlock.getTextDirection())
.build();
}
public static TableData fromTable(Table table) {
List<RowData> rowData = IntStream.range(0, table.getNumberOfRows())
.boxed()
.map(rowIdx -> table.streamRow(rowIdx).toList())
.map(TaasDocumentDataMapper::fromTableCells)
.toList();
return new TableData(rowData, table.getNumberOfCols(), table.getNumberOfRows());
}
public static RowData fromTableCells(List<TableCell> tableCells) {
if (tableCells.isEmpty()) {
throw new IllegalArgumentException("no table cells provided");
}
boolean header = tableCells.stream().allMatch(TableCell::isHeader);
Page firstPage = tableCells.get(0).getFirstPage();
Rectangle2D bBox = tableCells.stream().map(TableCell::getBBox).reduce((map1, map2) -> {
map1.forEach((page, rectangle) -> map2.merge(page, rectangle, (rect1, rect2) -> rect1.createUnion(rect2).getBounds2D()));
return map2;
}).orElseThrow().get(firstPage);
List<TextBlock> textBlocks = tableCells.stream().map(TableCell::getTextBlock).toList();
return new RowData(header, textBlocks.stream().map(textBlock -> TaasDocumentDataMapper.fromTextBlock("table_cell", textBlock)).toList(), toFloatArray(bBox));
}
private static Range toRange(Boundary boundary) {
return new Range(boundary.start(), boundary.end());
}
private static List<Range> toRange(List<Boundary> boundary) {
return boundary.stream().map(TaasDocumentDataMapper::toRange).toList();
}
public static StructureObject fromSemanticNodeWithParagraphData(SemanticNode semanticNode, Integer structureObjectNumber) {
Page page = semanticNode.getFirstPage();
Rectangle2D bBox = semanticNode.getBBox().get(page);
return StructureObject.builder()
.structureObjectNumber(structureObjectNumber)
.boundingBox(toFloatArray(bBox))
.stringOffset(semanticNode.getBoundary().start())
.page(page.getNumber())
.paragraph(TaasDocumentDataMapper.fromTextBlock(semanticNode.getType().toString().toLowerCase(Locale.ROOT), semanticNode.getTextBlock()))
.table(null)
.build();
}
public static StructureObject fromTableWithTableData(Table table, int structureObjectNumber) {
Page page = table.getFirstPage();
Rectangle2D bBox = table.getBBox().get(page);
return StructureObject.builder()
.structureObjectNumber(structureObjectNumber)
.boundingBox(toFloatArray(bBox))
.stringOffset(table.getBoundary().start())
.page(page.getNumber())
.paragraph(null)
.table(TaasDocumentDataMapper.fromTable(table))
.build();
}
private static float[] toFloatArray(Rectangle2D bBox) {
return new float[]{(float) bBox.getX(), (float) bBox.getY(), (float) bBox.getWidth(), (float) bBox.getHeight()};
}
}
@@ -1,8 +1,8 @@
package com.knecon.fforesight.service.layoutparser.processor.classification.model;
package com.knecon.fforesight.service.layoutparser.processor.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Rectangle;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.text.TextPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPageBlock;
import lombok.AllArgsConstructor;
import lombok.Data;
@@ -1,11 +1,11 @@
package com.knecon.fforesight.service.layoutparser.processor.classification.model;
package com.knecon.fforesight.service.layoutparser.processor.model;
import java.util.ArrayList;
import java.util.List;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.section.SectionGrid;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.text.StringFrequencyCounter;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.text.UnclassifiedText;
import com.knecon.fforesight.service.layoutparser.processor.model.text.StringFrequencyCounter;
import com.knecon.fforesight.service.layoutparser.processor.model.text.UnclassifiedText;
import lombok.Data;
import lombok.NoArgsConstructor;
@@ -1,8 +1,8 @@
package com.knecon.fforesight.service.layoutparser.processor.classification.model;
package com.knecon.fforesight.service.layoutparser.processor.model;
import java.util.List;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.text.TextPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPageBlock;
import lombok.AllArgsConstructor;
import lombok.Data;
@@ -1,8 +1,8 @@
package com.knecon.fforesight.service.layoutparser.processor.classification.model;
package com.knecon.fforesight.service.layoutparser.processor.model;
import java.util.List;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.text.TextPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPageBlock;
import lombok.AllArgsConstructor;
import lombok.Data;
@@ -1,11 +1,11 @@
package com.knecon.fforesight.service.layoutparser.processor.classification.model;
package com.knecon.fforesight.service.layoutparser.processor.model;
import java.util.ArrayList;
import java.util.List;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Rectangle;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.image.ClassifiedImage;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.text.StringFrequencyCounter;
import com.knecon.fforesight.service.layoutparser.processor.model.image.ClassifiedImage;
import com.knecon.fforesight.service.layoutparser.processor.model.text.StringFrequencyCounter;
import lombok.Data;
import lombok.NonNull;
@@ -1,10 +1,10 @@
package com.knecon.fforesight.service.layoutparser.processor.classification.model;
package com.knecon.fforesight.service.layoutparser.processor.model;
import java.util.ArrayList;
import java.util.List;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.image.ClassifiedImage;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.table.TablePageBlock;
import com.knecon.fforesight.service.layoutparser.processor.model.image.ClassifiedImage;
import com.knecon.fforesight.service.layoutparser.processor.model.table.TablePageBlock;
import lombok.Data;
import lombok.NoArgsConstructor;
@@ -1,4 +1,4 @@
package com.knecon.fforesight.service.layoutparser.processor.classification.model;
package com.knecon.fforesight.service.layoutparser.processor.model;
import java.util.ArrayList;
import java.util.Collections;
@@ -0,0 +1,27 @@
package com.knecon.fforesight.service.layoutparser.processor.model;
import java.awt.geom.Rectangle2D;
import java.util.LinkedList;
import java.util.List;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
@Getter
@AllArgsConstructor
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class GapInformation {
List<List<Rectangle2D>> xGaps;
List<List<Rectangle2D>> yGaps;
public GapInformation() {
xGaps = new LinkedList<>();
yGaps = new LinkedList<>();
}
}
@@ -0,0 +1,23 @@
package com.knecon.fforesight.service.layoutparser.processor.model;
import java.awt.geom.Rectangle2D;
import java.util.List;
import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPositionSequence;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
@Getter
@AllArgsConstructor
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class LineInformation {
List<Rectangle2D> lineBBox;
List<List<TextPositionSequence>> sequencesByLines;
List<List<Rectangle2D>> bBoxWithGapsByLines;
List<List<List<TextPositionSequence>>> sequencesWithGapsByLines;
}
@@ -0,0 +1,8 @@
package com.knecon.fforesight.service.layoutparser.processor.model;
public enum Orientation {
NONE,
LEFT,
RIGHT
}
@@ -1,4 +1,4 @@
package com.knecon.fforesight.service.layoutparser.processor.classification.model;
package com.knecon.fforesight.service.layoutparser.processor.model;
public enum PageBlockType {
H1,
@@ -0,0 +1,21 @@
package com.knecon.fforesight.service.layoutparser.processor.model;
import java.awt.geom.Rectangle2D;
import java.util.List;
import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPositionSequence;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
@AllArgsConstructor
public class PageContents {
List<TextPositionSequence> sortedTextPositionSequences;
Rectangle2D cropBox;
Rectangle2D mediaBox;
}
@@ -0,0 +1,17 @@
package com.knecon.fforesight.service.layoutparser.processor.model;
import java.awt.geom.Rectangle2D;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class PageInformation {
PageContents pageContents;
LineInformation lineInformation;
Rectangle2D mainBodyTextFrame;
GapInformation gapInformation;
}
@@ -0,0 +1,123 @@
package com.knecon.fforesight.service.layoutparser.processor.model;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.experimental.FieldDefaults;
@AllArgsConstructor
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class SectionIdentifier {
static Pattern numericalIdentifierPattern = Pattern.compile("^[\\s]?(\\d+)[\\s.,;]?(\\d+)?[\\s.,;]?(\\d+)?[\\s.,;]?(\\d+)?");
private enum Format {
EMPTY,
NUMERICAL,
DOCUMENT
}
Format format;
String identifierString;
List<Integer> identifiers;
boolean asChild;
public static SectionIdentifier fromSearchText(String headline) {
if (headline == null || headline.isEmpty() || headline.isBlank()) {
return SectionIdentifier.empty();
}
Matcher numericalIdentifierMatcher = numericalIdentifierPattern.matcher(headline);
if (numericalIdentifierMatcher.find()) {
return buildNumericalSectionIdentifier(headline, numericalIdentifierMatcher);
}
// more formats here
return SectionIdentifier.empty();
}
public static SectionIdentifier asChildOf(SectionIdentifier sectionIdentifier) {
return new SectionIdentifier(sectionIdentifier.format, sectionIdentifier.toString(), sectionIdentifier.identifiers, true);
}
public static SectionIdentifier document() {
return new SectionIdentifier(Format.DOCUMENT, "document", Collections.emptyList(), false);
}
public static SectionIdentifier empty() {
return new SectionIdentifier(Format.EMPTY, "empty", Collections.emptyList(), false);
}
private static SectionIdentifier buildNumericalSectionIdentifier(String headline, Matcher numericalIdentifierMatcher) {
String identifierString = headline.substring(numericalIdentifierMatcher.start(), numericalIdentifierMatcher.end());
List<Integer> identifiers = new LinkedList<>();
for (int i = 1; i <= 4; i++) {
String numericalIdentifier = numericalIdentifierMatcher.group(i);
if (numericalIdentifier == null || numericalIdentifier.equals("0") || numericalIdentifier.isEmpty() || numericalIdentifier.isBlank()) {
break;
}
identifiers.add(Integer.parseInt(numericalIdentifier.trim()));
}
return new SectionIdentifier(Format.NUMERICAL, identifierString, identifiers.stream().toList(), false);
}
/**
* Determines if the current section is the parent of the given section.
*
* @param sectionIdentifier The section identifier to compare against.
* @return true if the current section is the parent of the given section, false otherwise.
*/
public boolean isParentOf(SectionIdentifier sectionIdentifier) {
if (this.format.equals(Format.EMPTY)) {
return false;
}
if (this.format.equals(Format.DOCUMENT)) {
return true;
}
if (!this.format.equals(sectionIdentifier.format)) {
return false;
}
if (this.identifiers.size() >= sectionIdentifier.identifiers.size() && !(this.identifiers.size() == sectionIdentifier.identifiers.size() && sectionIdentifier.asChild)) {
return false;
}
for (int i = 0; i < this.identifiers.size(); i++) {
if (!this.identifiers.get(i).equals(sectionIdentifier.identifiers.get(i))) {
return false;
}
}
return true;
}
public boolean isChildOf(SectionIdentifier sectionIdentifier) {
if (this.format.equals(Format.DOCUMENT) || this.format.equals(Format.EMPTY)) {
return false;
}
return sectionIdentifier.isParentOf(this);
}
@Override
public String toString() {
return identifierString;
}
}
@@ -1,4 +1,4 @@
package com.knecon.fforesight.service.layoutparser.processor.classification.model.image;
package com.knecon.fforesight.service.layoutparser.processor.model.image;
import java.awt.geom.Rectangle2D;
@@ -1,13 +1,13 @@
package com.knecon.fforesight.service.layoutparser.processor.classification.model.table;
package com.knecon.fforesight.service.layoutparser.processor.model.table;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.text.TextPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.text.TextPositionSequence;
import com.knecon.fforesight.service.layoutparser.processor.classification.utils.TextNormalizationUtilities;
import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPositionSequence;
import com.knecon.fforesight.service.layoutparser.processor.utils.TextNormalizationUtilities;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -1,4 +1,4 @@
package com.knecon.fforesight.service.layoutparser.processor.classification.model.table;
package com.knecon.fforesight.service.layoutparser.processor.model.table;
import lombok.RequiredArgsConstructor;
import lombok.Value;
@@ -1,4 +1,4 @@
package com.knecon.fforesight.service.layoutparser.processor.classification.model.table;
package com.knecon.fforesight.service.layoutparser.processor.model.table;
import java.util.List;
@@ -1,4 +1,4 @@
package com.knecon.fforesight.service.layoutparser.processor.classification.model.table;
package com.knecon.fforesight.service.layoutparser.processor.model.table;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
@@ -1,4 +1,4 @@
package com.knecon.fforesight.service.layoutparser.processor.classification.model.table;
package com.knecon.fforesight.service.layoutparser.processor.model.table;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
@@ -11,8 +11,8 @@ import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import com.knecon.fforesight.service.layoutparser.processor.classification.utils.CohenSutherlandClipping;
import com.knecon.fforesight.service.layoutparser.processor.classification.utils.DoubleComparisons;
import com.knecon.fforesight.service.layoutparser.processor.utils.CohenSutherlandClipping;
import com.knecon.fforesight.service.layoutparser.processor.utils.DoubleComparisons;
import lombok.extern.slf4j.Slf4j;
@@ -28,6 +28,22 @@ public class Ruling extends Line2D.Float {
super(p1, p2);
}
public Ruling straightenVertical() {
double y1 = Math.min(getY1(), getY2());
double y2 = Math.max(getY1(), getY2());
double x = (getX1() + getX2()) / 2;
return new Ruling(new Point2D.Double(x, y1), new Point2D.Double(x, y2));
}
public Ruling straightenHorizontal() {
double x1 = Math.min(getX1(), getX2());
double x2 = Math.max(getX1(), getX2());
double y = (getY1() + getY2()) / 2;
return new Ruling(new Point2D.Double(x1, y), new Point2D.Double(x2, y));
}
public static List<Ruling> cropRulingsToArea(List<Ruling> rulings, Rectangle2D area) {
@@ -1,4 +1,4 @@
package com.knecon.fforesight.service.layoutparser.processor.classification.model.table;
package com.knecon.fforesight.service.layoutparser.processor.model.table;
import java.awt.geom.Point2D;
import java.util.ArrayList;
@@ -8,9 +8,9 @@ import java.util.List;
import java.util.Set;
import java.util.TreeMap;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.AbstractPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.PageBlockType;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.text.TextPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.model.AbstractPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.model.PageBlockType;
import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPageBlock;
import lombok.Getter;
import lombok.Setter;
@@ -1,4 +1,4 @@
package com.knecon.fforesight.service.layoutparser.processor.classification.model.text;
package com.knecon.fforesight.service.layoutparser.processor.model.text;
import org.apache.pdfbox.text.TextPosition;
import org.springframework.beans.BeanUtils;
@@ -1,8 +1,8 @@
package com.knecon.fforesight.service.layoutparser.processor.classification.model.text;
package com.knecon.fforesight.service.layoutparser.processor.model.text;
import java.util.ArrayList;
import java.util.List;
import com.knecon.fforesight.service.layoutparser.processor.classification.utils.TextNormalizationUtilities;
import com.knecon.fforesight.service.layoutparser.processor.utils.TextNormalizationUtilities;
import lombok.Getter;
@@ -1,4 +1,4 @@
package com.knecon.fforesight.service.layoutparser.processor.classification.model.text;
package com.knecon.fforesight.service.layoutparser.processor.model.text;
import lombok.AllArgsConstructor;
import lombok.Builder;
@@ -1,4 +1,4 @@
package com.knecon.fforesight.service.layoutparser.processor.classification.model.text;
package com.knecon.fforesight.service.layoutparser.processor.model.text;
import java.util.ArrayList;
import java.util.List;
@@ -1,4 +1,4 @@
package com.knecon.fforesight.service.layoutparser.processor.classification.model.text;
package com.knecon.fforesight.service.layoutparser.processor.model.text;
import java.util.HashMap;
import java.util.Map;
@@ -1,4 +1,4 @@
package com.knecon.fforesight.service.layoutparser.processor.classification.model.text;
package com.knecon.fforesight.service.layoutparser.processor.model.text;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
@@ -1,21 +1,28 @@
package com.knecon.fforesight.service.layoutparser.processor.classification.model.text;
package com.knecon.fforesight.service.layoutparser.processor.model.text;
import static java.util.stream.Collectors.toSet;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.AbstractPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.PageBlockType;
import com.knecon.fforesight.service.layoutparser.processor.classification.utils.TextNormalizationUtilities;
import com.knecon.fforesight.service.layoutparser.processor.model.AbstractPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.model.FloatFrequencyCounter;
import com.knecon.fforesight.service.layoutparser.processor.model.PageBlockType;
import com.knecon.fforesight.service.layoutparser.processor.utils.DoubleComparisons;
import com.knecon.fforesight.service.layoutparser.processor.utils.TextNormalizationUtilities;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@Builder
@Data
@NoArgsConstructor
public class TextPageBlock extends AbstractPageBlock {
@@ -67,6 +74,64 @@ public class TextPageBlock extends AbstractPageBlock {
return sequences.get(0).getPageWidth();
}
public static TextPageBlock merge(List<TextPageBlock> textBlocksToMerge) {
List<TextPositionSequence> sequences = textBlocksToMerge.stream().map(TextPageBlock::getSequences).flatMap(java.util.Collection::stream).toList();
sequences = new ArrayList<>(sequences);
return fromTextPositionSequences(sequences);
}
public static TextPageBlock fromTextPositionSequences(List<TextPositionSequence> wordBlockList) {
TextPageBlock textBlock = null;
FloatFrequencyCounter lineHeightFrequencyCounter = new FloatFrequencyCounter();
FloatFrequencyCounter fontSizeFrequencyCounter = new FloatFrequencyCounter();
FloatFrequencyCounter spaceFrequencyCounter = new FloatFrequencyCounter();
StringFrequencyCounter fontFrequencyCounter = new StringFrequencyCounter();
StringFrequencyCounter styleFrequencyCounter = new StringFrequencyCounter();
for (TextPositionSequence wordBlock : wordBlockList) {
lineHeightFrequencyCounter.add(wordBlock.getTextHeight());
fontSizeFrequencyCounter.add(wordBlock.getFontSize());
spaceFrequencyCounter.add(wordBlock.getSpaceWidth());
fontFrequencyCounter.add(wordBlock.getFont());
styleFrequencyCounter.add(wordBlock.getFontStyle());
if (textBlock == null) {
textBlock = new TextPageBlock(wordBlock.getMinXDirAdj(),
wordBlock.getMaxXDirAdj(),
wordBlock.getMinYDirAdj(),
wordBlock.getMaxYDirAdj(),
wordBlockList,
wordBlock.getRotation());
} else {
TextPageBlock spatialEntity = textBlock.union(wordBlock);
textBlock.resize(spatialEntity.getMinX(), spatialEntity.getMinY(), spatialEntity.getWidth(), spatialEntity.getHeight());
}
}
if (textBlock != null) {
textBlock.setMostPopularWordFont(fontFrequencyCounter.getMostPopular());
textBlock.setMostPopularWordStyle(styleFrequencyCounter.getMostPopular());
textBlock.setMostPopularWordFontSize(fontSizeFrequencyCounter.getMostPopular());
textBlock.setMostPopularWordHeight(lineHeightFrequencyCounter.getMostPopular());
textBlock.setMostPopularWordSpaceWidth(spaceFrequencyCounter.getMostPopular());
textBlock.setHighestFontSize(fontSizeFrequencyCounter.getHighest());
}
if (textBlock != null && textBlock.getSequences() != null && textBlock.getSequences()
.stream()
.map(t -> DoubleComparisons.round(t.getMinYDirAdj(), 3))
.collect(toSet())
.size() == 1) {
textBlock.getSequences().sort(Comparator.comparing(TextPositionSequence::getMinXDirAdj));
}
return textBlock;
}
/**
* Returns the minX value in pdf coordinate system.
@@ -1,15 +1,16 @@
package com.knecon.fforesight.service.layoutparser.processor.classification.model.text;
package com.knecon.fforesight.service.layoutparser.processor.model.text;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import org.apache.pdfbox.text.TextPosition;
import com.dslplatform.json.JsonAttribute;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
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;
@@ -25,7 +26,6 @@ import lombok.extern.slf4j.Slf4j;
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties({"empty"})
public class TextPositionSequence implements CharSequence {
public static final int HEIGHT_PADDING = 2;
@@ -36,6 +36,7 @@ public class TextPositionSequence implements CharSequence {
private int rotation;
private float pageHeight;
private float pageWidth;
private boolean isParagraphStart;
public TextPositionSequence(int page) {
@@ -44,7 +45,7 @@ public class TextPositionSequence implements CharSequence {
}
public TextPositionSequence(List<TextPosition> textPositions, int page) {
public TextPositionSequence(List<TextPosition> textPositions, int page, boolean isParagraphStart) {
this.textPositions = textPositions.stream().map(RedTextPosition::fromTextPosition).collect(Collectors.toList());
this.page = page;
@@ -52,6 +53,7 @@ public class TextPositionSequence implements CharSequence {
this.rotation = textPositions.get(0).getRotation();
this.pageHeight = textPositions.get(0).getPageHeight();
this.pageWidth = textPositions.get(0).getPageWidth();
this.isParagraphStart = isParagraphStart;
}
@@ -75,7 +77,7 @@ public class TextPositionSequence implements CharSequence {
RedTextPosition textPosition = textPositionAt(index);
String text = textPosition.getUnicode();
return caseInSensitive ? text.toLowerCase().charAt(0) : text.charAt(0);
return caseInSensitive ? text.toLowerCase(Locale.ROOT).charAt(0) : text.charAt(0);
}
@@ -141,6 +143,7 @@ public class TextPositionSequence implements CharSequence {
* @return the text direction adjusted minX value
*/
@JsonIgnore
@JsonAttribute(ignore = true)
public float getMinXDirAdj() {
return textPositions.get(0).getXDirAdj();
@@ -155,6 +158,7 @@ public class TextPositionSequence implements CharSequence {
* @return the text direction adjusted maxX value
*/
@JsonIgnore
@JsonAttribute(ignore = true)
public float getMaxXDirAdj() {
return textPositions.get(textPositions.size() - 1).getXDirAdj() + textPositions.get(textPositions.size() - 1).getWidthDirAdj() + HEIGHT_PADDING;
@@ -169,6 +173,7 @@ public class TextPositionSequence implements CharSequence {
* @return the text direction adjusted minY value. The upper border of the bounding box of the word.
*/
@JsonIgnore
@JsonAttribute(ignore = true)
public float getMinYDirAdj() {
return textPositions.get(0).getYDirAdj() - getTextHeight();
@@ -183,6 +188,7 @@ public class TextPositionSequence implements CharSequence {
* @return the text direction adjusted maxY value. The lower border of the bounding box of the word.
*/
@JsonIgnore
@JsonAttribute(ignore = true)
public float getMaxYDirAdj() {
return textPositions.get(0).getYDirAdj();
@@ -191,6 +197,7 @@ public class TextPositionSequence implements CharSequence {
@JsonIgnore
@JsonAttribute(ignore = true)
public float getTextHeight() {
return textPositions.get(0).getHeightDir() + HEIGHT_PADDING;
@@ -198,6 +205,7 @@ public class TextPositionSequence implements CharSequence {
@JsonIgnore
@JsonAttribute(ignore = true)
public float getHeight() {
return getMaxYDirAdj() - getMinYDirAdj();
@@ -205,6 +213,7 @@ public class TextPositionSequence implements CharSequence {
@JsonIgnore
@JsonAttribute(ignore = true)
public float getWidth() {
return getMaxXDirAdj() - getMinXDirAdj();
@@ -212,16 +221,18 @@ public class TextPositionSequence implements CharSequence {
@JsonIgnore
@JsonAttribute(ignore = true)
public String getFont() {
return textPositions.get(0).getFontName().toLowerCase().replaceAll(",bold", "").replaceAll(",italic", "");
return textPositions.get(0).getFontName().toLowerCase(Locale.ROOT).replaceAll(",bold", "").replaceAll(",italic", "");
}
@JsonIgnore
@JsonAttribute(ignore = true)
public String getFontStyle() {
String lowercaseFontName = textPositions.get(0).getFontName().toLowerCase();
String lowercaseFontName = textPositions.get(0).getFontName().toLowerCase(Locale.ROOT);
if (lowercaseFontName.contains("bold") && lowercaseFontName.contains("italic")) {
return "bold, italic";
@@ -237,6 +248,7 @@ public class TextPositionSequence implements CharSequence {
@JsonIgnore
@JsonAttribute(ignore = true)
public float getFontSize() {
return textPositions.get(0).getFontSizeInPt();
@@ -244,6 +256,7 @@ public class TextPositionSequence implements CharSequence {
@JsonIgnore
@JsonAttribute(ignore = true)
public float getSpaceWidth() {
return textPositions.get(0).getWidthOfSpace();
@@ -260,6 +273,7 @@ public class TextPositionSequence implements CharSequence {
* @return bounding box of the word in Pdf Coordinate System
*/
@JsonIgnore
@JsonAttribute(ignore = true)
@SneakyThrows
public Rectangle getRectangle() {
@@ -299,3 +313,4 @@ public class TextPositionSequence implements CharSequence {
}
}
@@ -1,4 +1,4 @@
package com.knecon.fforesight.service.layoutparser.processor.classification.model.text;
package com.knecon.fforesight.service.layoutparser.processor.model.text;
import java.util.List;
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.knecon.fforesight.service.layoutparser.processor.classification.parsing;
package com.knecon.fforesight.service.layoutparser.processor.parsing;
import java.io.IOException;
import java.io.InputStream;
@@ -76,7 +76,7 @@ import org.apache.pdfbox.util.Vector;
* THIS CODE IS DELIBERATELY INCORRECT, USE PDFStreamEngine INSTEAD.
*/
@SuppressWarnings({"PMD", "checkstyle:all"})
class LegacyPDFStreamEngine extends PDFStreamEngine {
public class LegacyPDFStreamEngine extends PDFStreamEngine {
private static final Log LOG = LogFactory.getLog(LegacyPDFStreamEngine.class);
@@ -126,7 +126,7 @@ class LegacyPDFStreamEngine extends PDFStreamEngine {
* This will initialize and process the contents of the stream.
*
* @param page the page to process
* @throws IOException if there is an error accessing the stream.
* @throws java.io.IOException if there is an error accessing the stream.
*/
@Override
public void processPage(PDPage page) throws IOException {
@@ -149,7 +149,7 @@ class LegacyPDFStreamEngine extends PDFStreamEngine {
* written by Ben Litchfield for PDFStreamEngine.
*/
@Override
protected void showGlyph(Matrix textRenderingMatrix, PDFont font, int code,Vector displacement) throws IOException {
protected void showGlyph(Matrix textRenderingMatrix, PDFont font, int code, Vector displacement) throws IOException {
//
// legacy calculations which were previously in PDFStreamEngine
//
@@ -165,7 +165,7 @@ class LegacyPDFStreamEngine extends PDFStreamEngine {
float displacementX = displacement.getX();
// the sorting algorithm is based on the width of the character. As the displacement
// for vertical characters doesn't provide any suitable value for it, we have to
// for vertical characters doesn't provide any suitable value for it, we have to
// calculate our own
if (font.isVertical()) {
displacementX = font.getWidth(code) / 1000;
@@ -382,3 +382,4 @@ class LegacyPDFStreamEngine extends PDFStreamEngine {
}
}
@@ -1,9 +1,9 @@
package com.knecon.fforesight.service.layoutparser.processor.classification.parsing;
package com.knecon.fforesight.service.layoutparser.processor.parsing;
import java.awt.color.CMMException;
import java.awt.geom.Point2D;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import org.apache.pdfbox.contentstream.operator.Operator;
@@ -32,14 +32,16 @@ import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSNumber;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
import org.apache.pdfbox.text.TextPosition;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.table.Ruling;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.text.RedTextPosition;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.text.TextPositionSequence;
import com.knecon.fforesight.service.layoutparser.processor.model.table.Ruling;
import com.knecon.fforesight.service.layoutparser.processor.model.text.RedTextPosition;
import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPositionSequence;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@Getter
@@ -195,8 +197,8 @@ public class PDFLinesTextStripper extends PDFTextStripper {
private void addVisibleRulings(List<Ruling> path, boolean stroke) throws IOException {
try {
if (stroke && !getGraphicsState().getStrokingColor().isPattern() && getGraphicsState().getStrokingColor()
.toRGB() == 0 || !stroke && !getGraphicsState().getNonStrokingColor().isPattern() && getGraphicsState().getNonStrokingColor().toRGB() == 0) {
if (stroke && !getGraphicsState().getStrokingColor().isPattern() && isBlack(getGraphicsState().getStrokingColor()) || //
!stroke && !getGraphicsState().getNonStrokingColor().isPattern() && isBlack(getGraphicsState().getNonStrokingColor())) {
rulings.addAll(path);
}
} catch (UnsupportedOperationException e) {
@@ -207,14 +209,27 @@ public class PDFLinesTextStripper extends PDFTextStripper {
}
@SneakyThrows
private boolean isBlack(PDColor color) {
try {
return color.toRGB() == 0;
} catch (CMMException e) {
// see https://github.com/haraldk/TwelveMonkeys/issues/124 or https://issues.apache.org/jira/browse/PDFBOX-3531
// This is a quick and dirt hack
// Happens for file 216.pdf
log.debug(e.getMessage());
return color.getComponents()[0] == 0 && color.getComponents()[1] == 0 && color.getComponents()[2] == 0 && color.getComponents()[1] == 1;
}
}
@Override
public void writeString(String text, List<TextPosition> textPositions) throws IOException {
public void writeString(String text, List<TextPosition> textPositions, boolean isParagraphStart) throws IOException {
int startIndex = 0;
RedTextPosition previous = null;
textPositions.sort(Comparator.comparing(TextPosition::getXDirAdj));
for (int i = 0; i <= textPositions.size() - 1; i++) {
if (!textPositionSequences.isEmpty()) {
@@ -250,7 +265,7 @@ public class PDFLinesTextStripper extends PDFTextStripper {
if (!(sublist.isEmpty() || sublist.size() == 1 && (sublist.get(0).getUnicode().equals(" ") || sublist.get(0).getUnicode().equals("\u00A0") || sublist.get(0)
.getUnicode()
.equals("\t")))) {
textPositionSequences.add(new TextPositionSequence(sublist, pageNumber));
textPositionSequences.add(new TextPositionSequence(sublist, pageNumber, i == textPositions.size() - 1 && isParagraphStart));
}
startIndex = i;
}
@@ -260,7 +275,7 @@ public class PDFLinesTextStripper extends PDFTextStripper {
if (!(sublist.isEmpty() || sublist.size() == 1 && (sublist.get(0).getUnicode().equals(" ") || sublist.get(0).getUnicode().equals("\u00A0") || sublist.get(0)
.getUnicode()
.equals("\t")))) {
textPositionSequences.add(new TextPositionSequence(sublist, pageNumber));
textPositionSequences.add(new TextPositionSequence(sublist, pageNumber, i == textPositions.size() - 1 && isParagraphStart));
}
startIndex = i;
}
@@ -276,11 +291,11 @@ public class PDFLinesTextStripper extends PDFTextStripper {
// Remove false sequence ends (whitespaces)
if (previous != null && sublist.get(0).getYDirAdj() == previous.getYDirAdj() && sublist.get(0)
.getXDirAdj() - (previous.getXDirAdj() + previous.getWidthDirAdj()) < 0.01) {
for (TextPosition textPosition : sublist) {
textPositionSequences.get(textPositionSequences.size() - 1).add(textPosition);
for (TextPosition t : sublist) {
textPositionSequences.get(textPositionSequences.size() - 1).add(t);
}
} else {
textPositionSequences.add(new TextPositionSequence(sublist, pageNumber));
textPositionSequences.add(new TextPositionSequence(sublist, pageNumber, i == textPositions.size() - 1 && isParagraphStart));
}
}
startIndex = i + 1;
@@ -303,7 +318,7 @@ public class PDFLinesTextStripper extends PDFTextStripper {
textPositionSequences.get(textPositionSequences.size() - 1).add(t);
}
} else {
textPositionSequences.add(new TextPositionSequence(sublist, pageNumber));
textPositionSequences.add(new TextPositionSequence(sublist, pageNumber, isParagraphStart));
}
}
super.writeString(text);
@@ -328,3 +343,4 @@ public class PDFLinesTextStripper extends PDFTextStripper {
}
}
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.knecon.fforesight.service.layoutparser.processor.classification.parsing;
package com.knecon.fforesight.service.layoutparser.processor.parsing;
import java.io.BufferedInputStream;
import java.io.IOException;
@@ -27,6 +27,7 @@ import java.text.Bidi;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
@@ -240,10 +241,10 @@ public class PDFTextStripper extends LegacyPDFStreamEngine {
document = doc;
output = outputStream;
if (getAddMoreFormatting()) {
paragraphEnd = lineSeparator;
paragraphEnd = "\n----ParagraphEnd----\n\n";
pageStart = lineSeparator;
articleStart = lineSeparator;
articleEnd = lineSeparator;
articleStart = "\n----ArticelStart----\n\n";
articleEnd = "\n----ArticelEnd----\n\n";
}
startDocument(document);
processPages(document.getPages());
@@ -594,9 +595,14 @@ public class PDFTextStripper extends LegacyPDFStreamEngine {
// but this caused a lot of regression test failures. So, I'm leaving it be for
// now
if (!overlap(positionY, positionHeight, maxYForLine, maxHeightForLine)) {
writeLine(normalize(line));
line.clear();
var normalized = normalize(line);
// normalized.stream().filter(l -> System.out.println(l.getText().contains("Plenarprotokoll 20/24")).findFirst().isPresent()
lastLineStartPosition = handleLineSeparation(current, lastPosition, lastLineStartPosition, maxHeightForLine);
writeLine(normalized, current.isParagraphStart);
line.clear();
expectedStartOfNextWordX = EXPECTED_START_OF_NEXT_WORD_X_RESET_VALUE;
maxYForLine = MAX_Y_FOR_LINE_RESET_VALUE;
maxHeightForLine = MAX_HEIGHT_FOR_LINE_RESET_VALUE;
@@ -630,7 +636,24 @@ public class PDFTextStripper extends LegacyPDFStreamEngine {
if (startOfPage && lastPosition == null) {
writeParagraphStart();// not sure this is correct for RTL?
}
line.add(new LineItem(position));
// Collections.sort(line, new Comparator<LineItem>() {
//
// @Override
// public int compare(LineItem str1, LineItem str2) {
// if(null == str1.getTextPosition()) {
// return 0;
// }
// else if(null == str2.getTextPosition()) {
// return 0;
// }
// return Float.compare(str1.getTextPosition().getX(), str2.getTextPosition().getX());
// }
// });
// line.sort(Comparator.comparing(a -> a.getTextPosition() != null && a.getTextPosition().getX()));
}
maxHeightForLine = Math.max(maxHeightForLine, positionHeight);
minYTopForLine = Math.min(minYTopForLine, positionY - positionHeight);
@@ -646,7 +669,7 @@ public class PDFTextStripper extends LegacyPDFStreamEngine {
}
// print the final line
if (line.size() > 0) {
writeLine(normalize(line));
writeLine(normalize(line), false);
writeParagraphEnd();
}
endArticle();
@@ -703,7 +726,7 @@ public class PDFTextStripper extends LegacyPDFStreamEngine {
* @param textPositions The TextPositions belonging to the text.
* @throws IOException If there is an error when writing the text.
*/
protected void writeString(String text, List<TextPosition> textPositions) throws IOException {
protected void writeString(String text, List<TextPosition> textPositions, boolean isParagraphEnd) throws IOException {
writeString(text);
}
@@ -998,7 +1021,7 @@ public class PDFTextStripper extends LegacyPDFStreamEngine {
/**
* By default the text stripper will attempt to remove text that overlapps each other. Word paints the same
* By default, the text stripper will attempt to remove text that overlapps each other. Word paints the same
* character several times in order to make it look bold. By setting this to false all text will be extracted, which
* means that certain sections will be duplicated, but better performance will be noticed.
*
@@ -1385,6 +1408,7 @@ public class PDFTextStripper extends LegacyPDFStreamEngine {
} else {
writeLineSeparator();
writeParagraphSeparator();
lastLineStartPosition.setEndParagraphWritten();
}
} else {
writeLineSeparator();
@@ -1428,6 +1452,10 @@ public class PDFTextStripper extends LegacyPDFStreamEngine {
float newXVal = multiplyFloat(getIndentThreshold(), position.getTextPosition().getWidthOfSpace());
float positionWidth = multiplyFloat(0.25f, position.getTextPosition().getWidth());
// if(xGap < 0){
// result = true;
// }
// else
if (yGap > newYVal) {
result = true;
} else if (xGap > newXVal) {
@@ -1636,12 +1664,13 @@ public class PDFTextStripper extends LegacyPDFStreamEngine {
* @param line a list with the words of the given line
* @throws IOException if something went wrong
*/
private void writeLine(List<WordWithTextPositions> line) throws IOException {
private void writeLine(List<WordWithTextPositions> line, boolean isParagraphEnd) throws IOException {
int numberOfStrings = line.size();
for (int i = 0; i < numberOfStrings; i++) {
WordWithTextPositions word = line.get(i);
writeString(word.getText(), word.getTextPositions());
word.getTextPositions().sort(Comparator.comparing(TextPosition::getX));
writeString(word.getText(), word.getTextPositions(), isParagraphEnd && i == numberOfStrings - 1);
if (i < numberOfStrings - 1) {
writeWordSeparator();
}
@@ -1963,6 +1992,8 @@ public class PDFTextStripper extends LegacyPDFStreamEngine {
private boolean isHangingIndent = false;
private boolean isArticleStart = false;
private boolean endParagraphWritten = false;
private TextPosition position = null;
@@ -2024,6 +2055,16 @@ public class PDFTextStripper extends LegacyPDFStreamEngine {
}
public boolean isEndParagraphWritten() {
return endParagraphWritten;
}
public void setEndParagraphWritten(){
endParagraphWritten = true;
}
/**
* Sets the isArticleStart() flag to true.
*/
@@ -2065,3 +2106,4 @@ public class PDFTextStripper extends LegacyPDFStreamEngine {
}
}
@@ -10,11 +10,10 @@ import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.knecon.fforesight.service.layoutparser.internal.api.queue.LayoutParsingFinishedEvent;
import com.knecon.fforesight.service.layoutparser.internal.api.queue.LayoutParsingRequest;
import com.knecon.fforesight.service.layoutparser.processor.LayoutParsingService;
import com.knecon.fforesight.service.layoutparser.processor.LayoutParsingPipeline;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
@@ -25,7 +24,7 @@ import lombok.extern.slf4j.Slf4j;
@RequiredArgsConstructor
public class MessageHandler {
private final LayoutParsingService layoutParsingService;
private final LayoutParsingPipeline layoutParsingPipeline;
private final ObjectMapper objectMapper;
private final RabbitTemplate rabbitTemplate;
@@ -42,7 +41,7 @@ public class MessageHandler {
throw new AmqpRejectAndDontRequeueException(String.format("Error during last layout parsing of request with identifier: %s, do not retry.",
layoutParsingRequest.identifier()));
}
LayoutParsingFinishedEvent layoutParsingFinishedEvent = layoutParsingService.parseLayoutAndSaveFilesToStorage(layoutParsingRequest);
LayoutParsingFinishedEvent layoutParsingFinishedEvent = layoutParsingPipeline.parseLayoutAndSaveFilesToStorage(layoutParsingRequest);
sendLayoutParsingFinishedEvent(layoutParsingFinishedEvent);
log.info("Layout parsing finished {} in {} ms", layoutParsingRequest.identifier(), layoutParsingFinishedEvent.duration());
}
@@ -50,11 +49,7 @@ public class MessageHandler {
public void sendLayoutParsingFinishedEvent(LayoutParsingFinishedEvent layoutParsingFinishedEvent) {
try {
rabbitTemplate.convertAndSend(LAYOUT_PARSING_FINISHED_EVENT_QUEUE, objectMapper.writeValueAsString(layoutParsingFinishedEvent));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
rabbitTemplate.convertAndSend(LAYOUT_PARSING_FINISHED_EVENT_QUEUE, layoutParsingFinishedEvent);
}
}
@@ -1,4 +1,4 @@
package com.knecon.fforesight.service.layoutparser.processor.classification.service;
package com.knecon.fforesight.service.layoutparser.processor.services;
import java.util.List;
@@ -6,18 +6,19 @@ 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.knecon.fforesight.service.layoutparser.processor.classification.model.AbstractPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.ClassificationPage;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.FloatFrequencyCounter;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.table.Cell;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.table.TablePageBlock;
import com.knecon.fforesight.service.layoutparser.processor.classification.model.text.TextPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.classification.utils.PositionUtils;
import com.knecon.fforesight.service.layoutparser.internal.api.queue.LayoutParsingType;
import com.knecon.fforesight.service.layoutparser.processor.model.AbstractPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.model.ClassificationPage;
import com.knecon.fforesight.service.layoutparser.processor.model.FloatFrequencyCounter;
import com.knecon.fforesight.service.layoutparser.processor.model.table.Cell;
import com.knecon.fforesight.service.layoutparser.processor.model.table.TablePageBlock;
import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPageBlock;
import com.knecon.fforesight.service.layoutparser.processor.utils.PositionUtils;
@Service
public class BodyTextFrameService {
private static final float APPROXIMATE_HEADER_LINE_COUNT = 2.9f;
/**
@@ -68,7 +69,14 @@ public class BodyTextFrameService {
* @param landscape Calculate for landscape or portrait
* @return Rectangle of the text frame
*/
public Rectangle calculateBodyTextFrame(List<ClassificationPage> pages, FloatFrequencyCounter documentFontSizeCounter, boolean landscape) {
public Rectangle calculateBodyTextFrame(List<ClassificationPage> pages, FloatFrequencyCounter documentFontSizeCounter, boolean landscape, LayoutParsingType layoutParsingType) {
float approximateHeaderLineCount;
if (layoutParsingType.equals(LayoutParsingType.TAAS)) {
approximateHeaderLineCount = 3.3f;
} else {
approximateHeaderLineCount = 2.9f;
}
BodyTextFrameExpansionsRectangle expansionsRectangle = new BodyTextFrameExpansionsRectangle();
@@ -87,7 +95,8 @@ public class BodyTextFrameService {
}
float approxLineCount = PositionUtils.getApproxLineCount(textBlock);
if (approxLineCount < APPROXIMATE_HEADER_LINE_COUNT) {
if (layoutParsingType.equals(LayoutParsingType.DOCUMINE) && approxLineCount < approximateHeaderLineCount && textBlock.getMaxY() >= page.getPageHeight() - (page.getPageHeight() / 10)
|| !layoutParsingType.equals(LayoutParsingType.DOCUMINE) && approxLineCount < approximateHeaderLineCount){
continue;
}

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