RED-7074: Design Subsection section tree structure algorithm
* first draft: further implementations
This commit is contained in:
parent
e9d1bdc94f
commit
77ee8dd5bd
@ -282,13 +282,6 @@ public class LayoutParsingPipeline {
|
|||||||
case CLARIFYND, CLARIFYND_PARAGRAPH_DEBUG -> docstrumBlockificationService.blockify(stripper.getTextPositionSequences(), emptyTableCells, false);
|
case CLARIFYND, CLARIFYND_PARAGRAPH_DEBUG -> docstrumBlockificationService.blockify(stripper.getTextPositionSequences(), emptyTableCells, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
List<OutlineObject> outlineObjects = classificationDocument.getOutlineObjectTree().getOutlineObjectsPerPage()
|
|
||||||
.get(pageNumber - 1);
|
|
||||||
if (outlineObjects != null) {
|
|
||||||
classificationPage.setOutlineObjects(outlineObjects);
|
|
||||||
blockificationPostprocessingService.sanitizeOutlineBlocksWithKdTree(classificationPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
classificationPage.setCleanRulings(cleanRulings);
|
classificationPage.setCleanRulings(cleanRulings);
|
||||||
classificationPage.setRotation(rotation);
|
classificationPage.setRotation(rotation);
|
||||||
classificationPage.setLandscape(isLandscape);
|
classificationPage.setLandscape(isLandscape);
|
||||||
@ -296,6 +289,13 @@ public class LayoutParsingPipeline {
|
|||||||
classificationPage.setPageWidth(cropbox.getWidth());
|
classificationPage.setPageWidth(cropbox.getWidth());
|
||||||
classificationPage.setPageHeight(cropbox.getHeight());
|
classificationPage.setPageHeight(cropbox.getHeight());
|
||||||
|
|
||||||
|
List<OutlineObject> outlineObjects = classificationDocument.getOutlineObjectTree().getOutlineObjectsPerPage()
|
||||||
|
.get(pageNumber - 1);
|
||||||
|
if (outlineObjects != null) {
|
||||||
|
classificationPage.setOutlineObjects(outlineObjects);
|
||||||
|
blockificationPostprocessingService.sanitizeOutlineBlocks(classificationPage);
|
||||||
|
}
|
||||||
|
|
||||||
// MarkedContent needs to be converted at this point, otherwise it leads to GC Problems in Pdfbox.
|
// MarkedContent needs to be converted at this point, otherwise it leads to GC Problems in Pdfbox.
|
||||||
classificationPage.setMarkedContentBboxPerType(convertMarkedContents(stripper.getMarkedContents()));
|
classificationPage.setMarkedContentBboxPerType(convertMarkedContents(stripper.getMarkedContents()));
|
||||||
|
|
||||||
|
|||||||
@ -52,9 +52,10 @@ public class OutlineValidationService {
|
|||||||
|
|
||||||
private void addItemAtCorrectPosition(TableOfContents toc, TableOfContentItem tocItem, TableOfContentItem lastHeadlineFromOutlines) {
|
private void addItemAtCorrectPosition(TableOfContents toc, TableOfContentItem tocItem, TableOfContentItem lastHeadlineFromOutlines) {
|
||||||
|
|
||||||
if(!tocItem.getChildren().isEmpty()) {
|
//if (lastHeadlineFromOutlines == null || tocItem.g)
|
||||||
|
//if(!tocItem.getChildren().isEmpty()) {
|
||||||
}
|
//
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
public TableOfContents createToC(List<TextPageBlock> headlines) {
|
public TableOfContents createToC(List<TextPageBlock> headlines) {
|
||||||
|
|||||||
@ -65,14 +65,14 @@ public class TextPageBlock extends AbstractPageBlock {
|
|||||||
|
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private float getPageHeight() {
|
public float getPageHeight() {
|
||||||
|
|
||||||
return sequences.get(0).getPageHeight();
|
return sequences.get(0).getPageHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private float getPageWidth() {
|
public float getPageWidth() {
|
||||||
|
|
||||||
return sequences.get(0).getPageWidth();
|
return sequences.get(0).getPageWidth();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,10 @@ import static com.knecon.fforesight.service.layoutparser.processor.services.bloc
|
|||||||
|
|
||||||
import java.awt.geom.Rectangle2D;
|
import java.awt.geom.Rectangle2D;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@ -36,39 +39,9 @@ public class BlockificationPostprocessingService {
|
|||||||
.collect(RectangleTransformations.collectBBox());
|
.collect(RectangleTransformations.collectBBox());
|
||||||
|
|
||||||
|
|
||||||
public void sanitizeOutlineBlocksWithKdTree(ClassificationPage classificationPage) {
|
public void sanitizeOutlineBlocks(ClassificationPage classificationPage) {
|
||||||
|
|
||||||
List<OutlineObject> outlineObjects = classificationPage.getOutlineObjects();
|
List<OutlineObject> outlineObjects = classificationPage.getOutlineObjects();
|
||||||
if (classificationPage.getTextBlocks().isEmpty() || outlineObjects.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
KDTree<TextPageBlock> kdTree = createKdTree(classificationPage);
|
|
||||||
|
|
||||||
for (OutlineObject outlineObject : outlineObjects) {
|
|
||||||
|
|
||||||
KDIterator<TextPageBlock> successorIterator = kdTree.query(new double[]{ //
|
|
||||||
0, //
|
|
||||||
outlineObject.getPoint().getY() - BLOCK_TO_OUTLINE_DISTANCE_THRESHOLD //
|
|
||||||
}, //
|
|
||||||
new double[]{Double.MAX_VALUE, Double.MAX_VALUE});
|
|
||||||
|
|
||||||
boolean matchedExactly = false;
|
|
||||||
|
|
||||||
OutlineProcessionContext context = new OutlineProcessionContext(outlineObject);
|
|
||||||
while (successorIterator.hasNext() && !matchedExactly) {
|
|
||||||
TextPageBlock pageBlock = successorIterator.next().value();
|
|
||||||
matchedExactly = processOutlineForTextBlock(pageBlock, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!matchedExactly) {
|
|
||||||
selectMatch(classificationPage, kdTree, context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static KDTree<TextPageBlock> createKdTree(ClassificationPage classificationPage) {
|
|
||||||
|
|
||||||
List<TextPageBlock> textBlocks = classificationPage.getTextBlocks()
|
List<TextPageBlock> textBlocks = classificationPage.getTextBlocks()
|
||||||
.stream()
|
.stream()
|
||||||
@ -78,97 +51,149 @@ public class BlockificationPostprocessingService {
|
|||||||
.map(block -> (TextPageBlock) block)
|
.map(block -> (TextPageBlock) block)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
KDTree<TextPageBlock> kdTree = KDTree.create(2);
|
if (textBlocks.isEmpty() || outlineObjects.isEmpty()) {
|
||||||
textBlocks.forEach(block -> {
|
return;
|
||||||
var boundingBox = blockToBoundingBox.apply(block);
|
}
|
||||||
kdTree.insert(new double[]{boundingBox.getMinX(), boundingBox.getMaxY()}, block);
|
|
||||||
});
|
float pageHeight = classificationPage.getPageHeight();
|
||||||
return kdTree;
|
|
||||||
|
for (OutlineObject outlineObject : outlineObjects) {
|
||||||
|
|
||||||
|
OutlineProcessionContext context = new OutlineProcessionContext(outlineObject);
|
||||||
|
|
||||||
|
ListIterator<TextPageBlock> iterator = textBlocks.listIterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
TextPageBlock pageBlock = iterator.next();
|
||||||
|
if (pageHeight - outlineObject.getPoint().getY() - BLOCK_TO_OUTLINE_DISTANCE_THRESHOLD <= pageBlock.getMaxY()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (iterator.hasPrevious()) {
|
||||||
|
iterator.previous();
|
||||||
|
}
|
||||||
|
boolean earlyStop = false;
|
||||||
|
while (iterator.hasNext() && !earlyStop) {
|
||||||
|
TextPageBlock pageBlock = iterator.next();
|
||||||
|
earlyStop = processOutlineForTextBlock(pageBlock, context);
|
||||||
|
}
|
||||||
|
selectMatch(classificationPage, context);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void selectMatch(ClassificationPage classificationPage, KDTree<TextPageBlock> kdTree, OutlineProcessionContext context) {
|
private void selectMatch(ClassificationPage classificationPage, OutlineProcessionContext context) {
|
||||||
|
|
||||||
OutlineObject outlineObject = context.outlineObject;
|
OutlineObject outlineObject = context.outlineObject;
|
||||||
|
TextPageBlock directMatch = context.directMatch;
|
||||||
List<TextPageBlock> mergeCandidates = context.mergeCandidates;
|
List<TextPageBlock> mergeCandidates = context.mergeCandidates;
|
||||||
TextPageBlock splitCandidate = context.splitCandidate;
|
TextPageBlock splitCandidate = context.splitCandidate;
|
||||||
PageBlockType headlineType = PageBlockType.getHeadlineType(outlineObject.getTreeDepth());
|
PageBlockType headlineType = PageBlockType.getHeadlineType(outlineObject.getTreeDepth());
|
||||||
|
|
||||||
|
double distanceToDirectMatch = directMatch != null ? calculateDistance(outlineObject, directMatch) : Double.MAX_VALUE;
|
||||||
|
double distanceToSplitCandidate = splitCandidate != null ? calculateDistance(outlineObject, splitCandidate) : Double.MAX_VALUE;
|
||||||
|
|
||||||
|
double distanceToBestMergeCandidates = Double.MAX_VALUE;
|
||||||
|
List<TextPageBlock> bestMergeCandidateCombination = new ArrayList<>();
|
||||||
if (!mergeCandidates.isEmpty()) {
|
if (!mergeCandidates.isEmpty()) {
|
||||||
|
|
||||||
List<TextPageBlock> allMergeCandidates = new ArrayList<>(mergeCandidates);
|
// with this code adjacent blocks to the first and last merge candidate get added, this could be useful for some edge cases:
|
||||||
addNeighborsOfCandidate(kdTree, mergeCandidates.get(0), allMergeCandidates);
|
//List<TextPageBlock> allMergeCandidates = new ArrayList<>(mergeCandidates);
|
||||||
if (mergeCandidates.size() > 1) {
|
//addNeighborsOfCandidate(kdTree, mergeCandidates.get(0), allMergeCandidates);
|
||||||
addNeighborsOfCandidate(kdTree, mergeCandidates.get(mergeCandidates.size() - 1), allMergeCandidates);
|
//if (mergeCandidates.size() > 1) {
|
||||||
}
|
// addNeighborsOfCandidate(kdTree, mergeCandidates.get(mergeCandidates.size() - 1), allMergeCandidates);
|
||||||
allMergeCandidates = allMergeCandidates.stream()
|
//}
|
||||||
.distinct()
|
//allMergeCandidates = allMergeCandidates.stream()
|
||||||
.toList();
|
// .distinct()
|
||||||
|
// .toList();
|
||||||
|
|
||||||
|
List<List<TextPageBlock>> combinations = findCombinations(outlineObject.getTitle(), mergeCandidates);
|
||||||
|
|
||||||
List<List<TextPageBlock>> combinations = findCombinations(outlineObject.getTitle(), allMergeCandidates);
|
|
||||||
double maxDistance = Double.MAX_VALUE;
|
|
||||||
List<TextPageBlock> bestCombination = new ArrayList<>();
|
|
||||||
for (List<TextPageBlock> combination : combinations) {
|
for (List<TextPageBlock> combination : combinations) {
|
||||||
double averageDistance = combination.stream()
|
double averageDistance = combination.stream()
|
||||||
.map(block -> calculateDistance(outlineObject, block))
|
.map(block -> calculateDistance(outlineObject, block))
|
||||||
.mapToDouble(Double::doubleValue).average()
|
.mapToDouble(Double::doubleValue).average()
|
||||||
.orElse(Double.MAX_VALUE);
|
.orElse(Double.MAX_VALUE);
|
||||||
if (maxDistance > averageDistance) {
|
if (distanceToBestMergeCandidates > averageDistance) {
|
||||||
maxDistance = averageDistance;
|
distanceToBestMergeCandidates = averageDistance;
|
||||||
bestCombination = combination;
|
bestMergeCandidateCombination = combination;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var merged = mergeBlocks(classificationPage, bestCombination);
|
}
|
||||||
|
|
||||||
|
double minDistance = Math.min(distanceToDirectMatch, Math.min(distanceToSplitCandidate, distanceToBestMergeCandidates));
|
||||||
|
|
||||||
|
if(minDistance == Double.MAX_VALUE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (minDistance == distanceToDirectMatch) {
|
||||||
|
directMatch.setClassification(headlineType);
|
||||||
|
} else if (minDistance == distanceToSplitCandidate) {
|
||||||
|
List<TextPageBlock> others = splitBlock(classificationPage, splitCandidate, outlineObject.getTitle());
|
||||||
|
splitCandidate.setClassification(headlineType);
|
||||||
|
others.forEach(other -> other.setClassification(headlineType));
|
||||||
|
} else {
|
||||||
|
var merged = mergeBlocks(classificationPage, bestMergeCandidateCombination);
|
||||||
merged.setClassification(headlineType);
|
merged.setClassification(headlineType);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (splitCandidate != null) {
|
|
||||||
TextPageBlock other = splitBlock(classificationPage, splitCandidate, outlineObject.getTitle());
|
private List<TextPageBlock> splitBlock(ClassificationPage classificationPage, TextPageBlock blockToSplit, String text) {
|
||||||
splitCandidate.setClassification(headlineType);
|
|
||||||
other.setClassification(headlineType);
|
List<TextPageBlock> otherBlocks = new ArrayList<>();
|
||||||
|
int blockToSplitIdx = classificationPage.getTextBlocks().indexOf(blockToSplit);
|
||||||
|
WordSequenceResult wordSequenceResult = findWordSequence(blockToSplit.getSequences(), text);
|
||||||
|
List<TextPositionSequence> postSequence = blockToSplit.getSequences();
|
||||||
|
postSequence.removeAll(wordSequenceResult.inSequence);
|
||||||
|
postSequence.removeAll(wordSequenceResult.preSequence);
|
||||||
|
|
||||||
|
blockToSplit.setSequences(wordSequenceResult.inSequence);
|
||||||
|
|
||||||
|
if (!wordSequenceResult.preSequence.isEmpty()) {
|
||||||
|
TextPageBlock block = buildTextBlock(wordSequenceResult.preSequence, 0);
|
||||||
|
classificationPage.getTextBlocks().add(blockToSplitIdx, block);
|
||||||
|
otherBlocks.add(block);
|
||||||
|
blockToSplitIdx++;
|
||||||
}
|
}
|
||||||
|
if (!postSequence.isEmpty()) {
|
||||||
|
TextPageBlock block = buildTextBlock(postSequence, 0);
|
||||||
|
classificationPage.getTextBlocks().add(blockToSplitIdx + 1, block);
|
||||||
|
otherBlocks.add(block);
|
||||||
|
}
|
||||||
|
return otherBlocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private TextPageBlock splitBlock(ClassificationPage classificationPage, TextPageBlock blockToSplit, String text) {
|
private static WordSequenceResult findWordSequence(List<TextPositionSequence> textPositionSequences, String text) {
|
||||||
|
|
||||||
List<TextPositionSequence> wordSequence = findWordSequence(blockToSplit.getSequences(), text);
|
String target = sanitizeString(text);
|
||||||
List<TextPositionSequence> remaining = blockToSplit.getSequences();
|
|
||||||
remaining.removeAll(wordSequence);
|
|
||||||
|
|
||||||
blockToSplit.setSequences(wordSequence);
|
|
||||||
|
|
||||||
TextPageBlock other = buildTextBlock(remaining, 0);
|
|
||||||
classificationPage.getTextBlocks().add(other);
|
|
||||||
return other;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static List<TextPositionSequence> findWordSequence(List<TextPositionSequence> textPositionSequences, String text) {
|
|
||||||
|
|
||||||
String target = text.replaceAll("\\s", "");
|
|
||||||
List<TextPositionSequence> inSequence = new ArrayList<>();
|
List<TextPositionSequence> inSequence = new ArrayList<>();
|
||||||
|
List<TextPositionSequence> preSequence = new ArrayList<>();
|
||||||
StringBuilder currentSequence = new StringBuilder();
|
StringBuilder currentSequence = new StringBuilder();
|
||||||
|
|
||||||
for (TextPositionSequence sequence : textPositionSequences) {
|
for (TextPositionSequence sequence : textPositionSequences) {
|
||||||
|
|
||||||
if (currentSequence.toString().equals(target)) {
|
currentSequence.append(sanitizeString(sequence.toString()));
|
||||||
return inSequence;
|
|
||||||
}
|
|
||||||
currentSequence.append(sequence.toString());
|
|
||||||
inSequence.add(sequence);
|
inSequence.add(sequence);
|
||||||
|
|
||||||
if (currentSequence.length() > target.length()) {
|
if (currentSequence.length() > target.length()) {
|
||||||
TextPositionSequence removed = inSequence.remove(0);
|
TextPositionSequence removed = inSequence.remove(0);
|
||||||
currentSequence.delete(0, removed.toString().length());
|
currentSequence.delete(0, removed.toString().length());
|
||||||
|
preSequence.add(removed);
|
||||||
|
|
||||||
while (currentSequence.length() > target.length()) {
|
while (currentSequence.length() > target.length()) {
|
||||||
removed = inSequence.remove(0);
|
removed = inSequence.remove(0);
|
||||||
currentSequence.delete(0, removed.toString().length());
|
currentSequence.delete(0, removed.toString().length());
|
||||||
|
preSequence.add(removed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentSequence.toString().equals(target)) {
|
||||||
|
return new WordSequenceResult(inSequence, preSequence);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return new ArrayList<>();
|
return new WordSequenceResult(new ArrayList<>(), new ArrayList<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -209,7 +234,7 @@ public class BlockificationPostprocessingService {
|
|||||||
|
|
||||||
private static void findCombinations(String title, List<TextPageBlock> blocks, List<TextPageBlock> current, List<List<TextPageBlock>> combinations) {
|
private static void findCombinations(String title, List<TextPageBlock> blocks, List<TextPageBlock> current, List<List<TextPageBlock>> combinations) {
|
||||||
|
|
||||||
String target = title.replaceAll("\\s", "");
|
String target = sanitizeString(title);
|
||||||
if (target.isEmpty()) {
|
if (target.isEmpty()) {
|
||||||
combinations.add(new ArrayList<>(current));
|
combinations.add(new ArrayList<>(current));
|
||||||
return;
|
return;
|
||||||
@ -219,10 +244,10 @@ public class BlockificationPostprocessingService {
|
|||||||
.filter(block -> !current.contains(block))
|
.filter(block -> !current.contains(block))
|
||||||
.toList();
|
.toList();
|
||||||
for (TextPageBlock block : remaining) {
|
for (TextPageBlock block : remaining) {
|
||||||
String prefix = block.getText().replaceAll("\\s", "");
|
String prefix = sanitizeString(block.getText());
|
||||||
if (target.startsWith(prefix)) {
|
if (target.startsWith(prefix)) {
|
||||||
current.add(block);
|
current.add(block);
|
||||||
findCombinations(target.substring(prefix.length()), blocks, current, combinations);
|
findCombinations(target.substring(prefix.length()), blocks.subList(blocks.indexOf(block) + 1, blocks.size()), current, combinations);
|
||||||
current.remove(current.size() - 1);
|
current.remove(current.size() - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -232,7 +257,7 @@ public class BlockificationPostprocessingService {
|
|||||||
private double calculateDistance(OutlineObject outlineObject, TextPageBlock pageBlock) {
|
private double calculateDistance(OutlineObject outlineObject, TextPageBlock pageBlock) {
|
||||||
|
|
||||||
double deltaX = outlineObject.getPoint().getX() - pageBlock.getMinX();
|
double deltaX = outlineObject.getPoint().getX() - pageBlock.getMinX();
|
||||||
double deltaY = outlineObject.getPoint().getY() - pageBlock.getMinY();
|
double deltaY = pageBlock.getPageHeight() - outlineObject.getPoint().getY() - pageBlock.getMinY();
|
||||||
return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,8 +280,8 @@ public class BlockificationPostprocessingService {
|
|||||||
private boolean processOutlineForTextBlock(TextPageBlock pageBlock, OutlineProcessionContext context) {
|
private boolean processOutlineForTextBlock(TextPageBlock pageBlock, OutlineProcessionContext context) {
|
||||||
|
|
||||||
OutlineObject outlineObject = context.getOutlineObject();
|
OutlineObject outlineObject = context.getOutlineObject();
|
||||||
String blockText = pageBlock.getText();
|
String blockText = sanitizeString(pageBlock.getText());
|
||||||
String outlineTitle = outlineObject.getTitle();
|
String outlineTitle = sanitizeString(outlineObject.getTitle());
|
||||||
|
|
||||||
boolean blockTextContainsOutlineTitle = blockText.contains(outlineTitle);
|
boolean blockTextContainsOutlineTitle = blockText.contains(outlineTitle);
|
||||||
boolean outlineTitleContainsBlockText = outlineTitle.contains(blockText);
|
boolean outlineTitleContainsBlockText = outlineTitle.contains(blockText);
|
||||||
@ -265,8 +290,8 @@ public class BlockificationPostprocessingService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (blockText.equals(outlineTitle)) {
|
if (blockText.equals(outlineTitle) && context.directMatch == null) {
|
||||||
pageBlock.setClassification(PageBlockType.getHeadlineType(outlineObject.getTreeDepth()));
|
context.directMatch = pageBlock;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,17 +299,27 @@ public class BlockificationPostprocessingService {
|
|||||||
context.mergeCandidates.add(pageBlock);
|
context.mergeCandidates.add(pageBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (blockTextContainsOutlineTitle && context.splitCandidate != null) {
|
if (blockTextContainsOutlineTitle && context.splitCandidate == null) {
|
||||||
context.splitCandidate = pageBlock;
|
context.splitCandidate = pageBlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static String sanitizeString(String text) {
|
||||||
|
|
||||||
|
return text.replaceAll("\\s", "").toLowerCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private record WordSequenceResult(List<TextPositionSequence> inSequence, List<TextPositionSequence> preSequence) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
private static class OutlineProcessionContext {
|
private static class OutlineProcessionContext {
|
||||||
|
|
||||||
|
private TextPageBlock directMatch;
|
||||||
private OutlineObject outlineObject;
|
private OutlineObject outlineObject;
|
||||||
private List<TextPageBlock> mergeCandidates;
|
private List<TextPageBlock> mergeCandidates;
|
||||||
private TextPageBlock splitCandidate;
|
private TextPageBlock splitCandidate;
|
||||||
@ -293,10 +328,65 @@ public class BlockificationPostprocessingService {
|
|||||||
public OutlineProcessionContext(OutlineObject outlineObject) {
|
public OutlineProcessionContext(OutlineObject outlineObject) {
|
||||||
|
|
||||||
this.outlineObject = outlineObject;
|
this.outlineObject = outlineObject;
|
||||||
|
this.directMatch = null;
|
||||||
this.mergeCandidates = new ArrayList<>();
|
this.mergeCandidates = new ArrayList<>();
|
||||||
this.splitCandidate = null;
|
this.splitCandidate = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public void sanitizeOutlineBlocksWithKdTree(ClassificationPage classificationPage) {
|
||||||
|
|
||||||
|
List<OutlineObject> outlineObjects = classificationPage.getOutlineObjects();
|
||||||
|
if (classificationPage.getTextBlocks().isEmpty() || outlineObjects.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
KDTree<TextPageBlock> kdTree = createKdTree(classificationPage);
|
||||||
|
float pageHeight = classificationPage.getPageHeight();
|
||||||
|
|
||||||
|
for (OutlineObject outlineObject : outlineObjects) {
|
||||||
|
|
||||||
|
// kd tree contains yx coordinates
|
||||||
|
KDIterator<TextPageBlock> successorIterator = kdTree.query(new double[]{ //
|
||||||
|
pageHeight - outlineObject.getPoint().getY() - BLOCK_TO_OUTLINE_DISTANCE_THRESHOLD, 0, //
|
||||||
|
//
|
||||||
|
}, //
|
||||||
|
new double[]{Double.MAX_VALUE, Double.MAX_VALUE});
|
||||||
|
|
||||||
|
OutlineProcessionContext context = new OutlineProcessionContext(outlineObject);
|
||||||
|
|
||||||
|
boolean earlyStop = false;
|
||||||
|
while (successorIterator.hasNext() && !earlyStop) {
|
||||||
|
TextPageBlock pageBlock = successorIterator.next().value();
|
||||||
|
earlyStop = processOutlineForTextBlock(pageBlock, context);
|
||||||
|
processOutlineForTextBlock(pageBlock, context);
|
||||||
|
}
|
||||||
|
selectMatch(classificationPage, context);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
private static KDTree<TextPageBlock> createKdTree(ClassificationPage classificationPage) {
|
||||||
|
|
||||||
|
List<TextPageBlock> textBlocks = classificationPage.getTextBlocks()
|
||||||
|
.stream()
|
||||||
|
.filter(block -> block instanceof TextPageBlock)
|
||||||
|
.toList()
|
||||||
|
.stream()
|
||||||
|
.map(block -> (TextPageBlock) block)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
KDTree<TextPageBlock> kdTree = KDTree.create(2);
|
||||||
|
// insert y first then x, use pdf max y so that the page height is subtracted so that the order is inverted
|
||||||
|
textBlocks.forEach(block -> {
|
||||||
|
//var boundingBox = blockToBoundingBox.apply(block);
|
||||||
|
kdTree.insert(new double[]{block.getMinY(), block.getMinX()}, block);
|
||||||
|
});
|
||||||
|
return kdTree;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import com.knecon.fforesight.service.layoutparser.processor.model.PageBlockType;
|
|||||||
import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPageBlock;
|
import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPageBlock;
|
||||||
import com.knecon.fforesight.service.layoutparser.processor.utils.PositionUtils;
|
import com.knecon.fforesight.service.layoutparser.processor.utils.PositionUtils;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@ -42,15 +43,17 @@ public class RedactManagerClassificationService {
|
|||||||
.map(tb -> (TextPageBlock) tb))
|
.map(tb -> (TextPageBlock) tb))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
|
||||||
|
HeadLineClassificationContext headLineClassificationContext = new HeadLineClassificationContext();
|
||||||
for (ClassificationPage page : document.getPages()) {
|
for (ClassificationPage page : document.getPages()) {
|
||||||
classifyPage(page, document, headlineFontSizes);
|
classifyPage(page, document, headlineFontSizes, headLineClassificationContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<TextPageBlock> allHeadlines = document.getPages()
|
List<TextPageBlock> allHeadlines = document.getPages()
|
||||||
.stream()
|
.stream()
|
||||||
.flatMap(classificationPage -> classificationPage.getTextBlocks()
|
.flatMap(classificationPage -> classificationPage.getTextBlocks()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(tb -> tb instanceof TextPageBlock && tb.getClassification() != null && tb.getClassification().isHeadline())
|
.filter(tb -> tb instanceof TextPageBlock && tb.getClassification() != null && tb.getClassification().isHeadline())
|
||||||
.map(tb -> (TextPageBlock) tb))
|
.map(tb -> (TextPageBlock) tb))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
@ -65,21 +68,26 @@ public class RedactManagerClassificationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void classifyPage(ClassificationPage page, ClassificationDocument document, List<Float> headlineFontSizes) {
|
private void classifyPage(ClassificationPage page, ClassificationDocument document, List<Float> headlineFontSizes, HeadLineClassificationContext headLineClassificationContext) {
|
||||||
|
|
||||||
for (AbstractPageBlock textBlock : page.getTextBlocks()) {
|
for (AbstractPageBlock textBlock : page.getTextBlocks()) {
|
||||||
if (textBlock instanceof TextPageBlock) {
|
if (textBlock instanceof TextPageBlock) {
|
||||||
classifyBlock((TextPageBlock) textBlock, page, document, headlineFontSizes);
|
classifyBlock((TextPageBlock) textBlock, page, document, headlineFontSizes, headLineClassificationContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void classifyBlock(TextPageBlock textBlock, ClassificationPage page, ClassificationDocument document, List<Float> headlineFontSizes) {
|
private void classifyBlock(TextPageBlock textBlock,
|
||||||
|
ClassificationPage page,
|
||||||
|
ClassificationDocument document,
|
||||||
|
List<Float> headlineFontSizes,
|
||||||
|
HeadLineClassificationContext headLineClassificationContext) {
|
||||||
|
|
||||||
var bodyTextFrame = page.getBodyTextFrame();
|
var bodyTextFrame = page.getBodyTextFrame();
|
||||||
|
|
||||||
if (textBlock.getClassification() != null && textBlock.getClassification().isHeadline()) {
|
if (textBlock.getClassification() != null && textBlock.getClassification().isHeadline()) {
|
||||||
|
headLineClassificationContext.setLastHeadlineFromOutline(textBlock);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (document.getFontSizeCounter().getMostPopular() == null) {
|
if (document.getFontSizeCounter().getMostPopular() == null) {
|
||||||
@ -113,7 +121,8 @@ public class RedactManagerClassificationService {
|
|||||||
|
|
||||||
for (int i = 1; i <= headlineFontSizes.size(); i++) {
|
for (int i = 1; i <= headlineFontSizes.size(); i++) {
|
||||||
if (textBlock.getMostPopularWordFontSize() == headlineFontSizes.get(i - 1)) {
|
if (textBlock.getMostPopularWordFontSize() == headlineFontSizes.get(i - 1)) {
|
||||||
textBlock.setClassification(PageBlockType.getHeadlineType(i));
|
PageBlockType headlineType = PageBlockType.getHeadlineType(i);
|
||||||
|
classifyHeadline(textBlock, headLineClassificationContext, headlineType);
|
||||||
document.setHeadlines(true);
|
document.setHeadlines(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -125,7 +134,8 @@ public class RedactManagerClassificationService {
|
|||||||
&& textBlock.getSequences()
|
&& textBlock.getSequences()
|
||||||
.get(0).getTextPositions()
|
.get(0).getTextPositions()
|
||||||
.get(0).getFontSizeInPt() >= textBlock.getMostPopularWordFontSize()) {
|
.get(0).getFontSizeInPt() >= textBlock.getMostPopularWordFontSize()) {
|
||||||
textBlock.setClassification(PageBlockType.getHeadlineType(headlineFontSizes.size() + 1));
|
PageBlockType headlineType = PageBlockType.getHeadlineType(headlineFontSizes.size() + 1);
|
||||||
|
classifyHeadline(textBlock, headLineClassificationContext, headlineType);
|
||||||
document.setHeadlines(true);
|
document.setHeadlines(true);
|
||||||
} else if (PositionUtils.isWithinBodyTextFrame(bodyTextFrame, textBlock)
|
} else if (PositionUtils.isWithinBodyTextFrame(bodyTextFrame, textBlock)
|
||||||
&& textBlock.getMostPopularWordFontSize() == document.getFontSizeCounter().getMostPopular()
|
&& textBlock.getMostPopularWordFontSize() == document.getFontSizeCounter().getMostPopular()
|
||||||
@ -150,4 +160,66 @@ public class RedactManagerClassificationService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void classifyHeadline(TextPageBlock textBlock, HeadLineClassificationContext headLineClassificationContext, PageBlockType headlineType) {
|
||||||
|
|
||||||
|
TextPageBlock lastHeadline = headLineClassificationContext.getLastHeadline();
|
||||||
|
TextPageBlock lastHeadlineFromOutline = headLineClassificationContext.getLastHeadlineFromOutline();
|
||||||
|
PageBlockType originalClassifiedBlockType = headLineClassificationContext.getOriginalClassifiedBlockType();
|
||||||
|
|
||||||
|
if (lastHeadline != null) {
|
||||||
|
|
||||||
|
if (lastHeadline.equals(lastHeadlineFromOutline)) {
|
||||||
|
|
||||||
|
headlineType = getNextType(lastHeadline.getClassification());
|
||||||
|
|
||||||
|
} else if (originalClassifiedBlockType != null && lastHeadline.getClassification() != originalClassifiedBlockType) {
|
||||||
|
|
||||||
|
PageBlockType lastHeadlineType = lastHeadline.getClassification();
|
||||||
|
int difference = getHeadlineNumber(originalClassifiedBlockType) - getHeadlineNumber(lastHeadlineType);
|
||||||
|
headlineType = PageBlockType.getHeadlineType(getHeadlineNumber(headlineType) + difference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
headLineClassificationContext.setOriginalClassifiedBlockType(headlineType);
|
||||||
|
textBlock.setClassification(headlineType);
|
||||||
|
headLineClassificationContext.setLastHeadline(textBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static PageBlockType getNextType(PageBlockType pageBlockType) {
|
||||||
|
|
||||||
|
return PageBlockType.getHeadlineType(getHeadlineNumber(pageBlockType) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static int getHeadlineNumber(PageBlockType pageBlockType) {
|
||||||
|
|
||||||
|
return switch (pageBlockType) {
|
||||||
|
case H1 -> 1;
|
||||||
|
case H2 -> 2;
|
||||||
|
case H3 -> 3;
|
||||||
|
case H4 -> 4;
|
||||||
|
case H5 -> 5;
|
||||||
|
default -> 6;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Data
|
||||||
|
static class HeadLineClassificationContext {
|
||||||
|
|
||||||
|
TextPageBlock lastHeadline;
|
||||||
|
PageBlockType originalClassifiedBlockType;
|
||||||
|
TextPageBlock lastHeadlineFromOutline;
|
||||||
|
|
||||||
|
|
||||||
|
public void setLastHeadlineFromOutline(TextPageBlock lastHeadlineFromOutline) {
|
||||||
|
|
||||||
|
this.lastHeadlineFromOutline = lastHeadlineFromOutline;
|
||||||
|
this.setLastHeadline(lastHeadlineFromOutline);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,7 +32,8 @@ public class ViewerDocumentTest extends BuildDocumentTest {
|
|||||||
//String fileName = "files/new/abschlussarbeiten-template-institut-fur-informatik-padagogische-hochschule-karlsruhe.pdf";
|
//String fileName = "files/new/abschlussarbeiten-template-institut-fur-informatik-padagogische-hochschule-karlsruhe.pdf";
|
||||||
//String fileName = "files/new/kaust-official-thesis-template.pdf";
|
//String fileName = "files/new/kaust-official-thesis-template.pdf";
|
||||||
//String fileName = "files/new/$100m Offers.pdf";
|
//String fileName = "files/new/$100m Offers.pdf";
|
||||||
String fileName = "files/new/18-Curacron_ToxicidadeOcularInVitro.pdf";
|
//String fileName = "files/new/18-Curacron_ToxicidadeOcularInVitro.pdf";
|
||||||
|
String fileName = "files/new/UTT-Books-53.pdf";
|
||||||
//String fileName = "files/new/mistitled_outlines_example.pdf";
|
//String fileName = "files/new/mistitled_outlines_example.pdf";
|
||||||
//String fileName = "files/bdr/Plenarprotokoll 1 (keine Druchsache!) (1) 1.pdf";
|
//String fileName = "files/bdr/Plenarprotokoll 1 (keine Druchsache!) (1) 1.pdf";
|
||||||
String tmpFileName = "/tmp/" + Path.of(fileName).getFileName() + "_VIEWER.pdf";
|
String tmpFileName = "/tmp/" + Path.of(fileName).getFileName() + "_VIEWER.pdf";
|
||||||
|
|||||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user