More
This commit is contained in:
parent
9e5778d4b2
commit
e14d953b04
@ -49,8 +49,7 @@ import com.knecon.fforesight.service.layoutparser.processor.services.blockificat
|
||||
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 com.knecon.fforesight.service.layoutparser.processor.services.docstrum.DocstrumSegmenter;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.HierarchicalReadingOrderResolver;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.DocstrumSegmentationService;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.factory.DocumentGraphFactory;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.mapper.DocumentDataMapper;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.mapper.TaasDocumentDataMapper;
|
||||
@ -90,8 +89,7 @@ public class LayoutParsingPipeline {
|
||||
RedactManagerBlockificationService redactManagerBlockificationService;
|
||||
LayoutGridService layoutGridService;
|
||||
ObservationRegistry observationRegistry;
|
||||
DocstrumSegmenter docstrumSegmenter;
|
||||
HierarchicalReadingOrderResolver hierarchicalReadingOrderResolver;
|
||||
DocstrumSegmentationService docstrumSegmentationService;
|
||||
|
||||
|
||||
public LayoutParsingFinishedEvent parseLayoutAndSaveFilesToStorage(LayoutParsingRequest layoutParsingRequest) throws IOException {
|
||||
@ -251,8 +249,7 @@ public class LayoutParsingPipeline {
|
||||
|
||||
// Docstrum
|
||||
AtomicInteger num = new AtomicInteger(pageNumber);
|
||||
var zones = docstrumSegmenter.segmentPage(stripper.getTextPositionSequences());
|
||||
zones = hierarchicalReadingOrderResolver.resolve(zones);
|
||||
var zones = docstrumSegmentationService.segmentPage(stripper.getTextPositionSequences());
|
||||
|
||||
List<AbstractPageBlock> pageBlocks = new ArrayList<>();
|
||||
AtomicInteger numOnPage = new AtomicInteger(1);
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPositionSequence;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.Character;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.Zone;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.service.LineBuilderService;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.service.NearestNeighbourService;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.service.ReadingOrderService;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.service.SpacingService;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.service.ZoneBuilderService;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DocstrumSegmentationService {
|
||||
|
||||
private final NearestNeighbourService nearestNeighbourService;
|
||||
private final SpacingService spacingService;
|
||||
private final LineBuilderService lineBuilderService;
|
||||
private final ZoneBuilderService zoneBuilderService;
|
||||
private final ReadingOrderService readingOrderService;
|
||||
|
||||
|
||||
public List<Zone> segmentPage(List<TextPositionSequence> textPositions) {
|
||||
|
||||
var positions = textPositions.stream().map(TextPositionSequence::getTextPositions).flatMap(List::stream).toList();
|
||||
|
||||
var components = positions.stream().map(Character::new).collect(Collectors.toList());
|
||||
|
||||
nearestNeighbourService.findNearestNeighbors(components);
|
||||
|
||||
var characterSpacing = spacingService.computeCharacterSpacing(components);
|
||||
var lineSpacing = spacingService.computeLineSpacing(components);
|
||||
|
||||
var lines = lineBuilderService.buildLines(components, characterSpacing, lineSpacing);
|
||||
|
||||
var zones = zoneBuilderService.buildZones(lines, characterSpacing, lineSpacing);
|
||||
|
||||
return readingOrderService.resolve(zones);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPositionSequence;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.docstrum.Character;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.docstrum.CharacterLine;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.docstrum.CharacterZone;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DocstrumSegmenter {
|
||||
|
||||
private final NearestNeighbourService nearestNeighbourService;
|
||||
private final SpacingService spacingService;
|
||||
private final LineBuilderService lineBuilderService;
|
||||
private final ZoneBuilderService zoneBuilderService;
|
||||
|
||||
|
||||
public List<CharacterZone> segmentPage(List<TextPositionSequence> textPositions) {
|
||||
|
||||
var positions = textPositions.stream().map(TextPositionSequence::getTextPositions).flatMap(List::stream).toList();
|
||||
|
||||
var components = positions.stream().map(Character::new).collect(Collectors.toList());
|
||||
|
||||
nearestNeighbourService.findNearestNeighbors(components);
|
||||
|
||||
double characterSpacing = spacingService.computeCharacterSpacing(components);
|
||||
double lineSpacing = spacingService.computeLineSpacing(components);
|
||||
|
||||
List<CharacterLine> lines = lineBuilderService.buildLines(components, characterSpacing, lineSpacing);
|
||||
|
||||
return zoneBuilderService.buildZones(lines, characterSpacing, lineSpacing);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,6 +1,4 @@
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum;
|
||||
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.docstrum.Neighbor;
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model;
|
||||
|
||||
/**
|
||||
* Filter class for neighbor objects that checks if the angle of the
|
||||
@ -1,11 +1,11 @@
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor;
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model;
|
||||
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public abstract class BBoxObject {
|
||||
public abstract class BoundingBox {
|
||||
|
||||
private Rectangle2D bBox;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.docstrum;
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -1,4 +1,4 @@
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.docstrum;
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model;
|
||||
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.ArrayList;
|
||||
@ -6,12 +6,11 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPositionSequence;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.BBoxObject;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class CharacterLine extends BBoxObject {
|
||||
public class Line extends BoundingBox {
|
||||
|
||||
private static final double WORD_DISTANCE_MULTIPLIER = 0.2;
|
||||
|
||||
@ -27,7 +26,7 @@ public class CharacterLine extends BBoxObject {
|
||||
private final List<TextPositionSequence> words = new ArrayList<>();
|
||||
|
||||
|
||||
public CharacterLine(List<Character> characters, double wordSpacing) {
|
||||
public Line(List<Character> characters, double wordSpacing) {
|
||||
|
||||
this.characters = characters;
|
||||
|
||||
@ -86,7 +85,7 @@ public class CharacterLine extends BBoxObject {
|
||||
}
|
||||
|
||||
|
||||
public double angularDifference(CharacterLine j) {
|
||||
public double angularDifference(Line j) {
|
||||
|
||||
double diff = Math.abs(getAngle() - j.getAngle());
|
||||
if (diff <= Math.PI / 2) {
|
||||
@ -97,7 +96,7 @@ public class CharacterLine extends BBoxObject {
|
||||
}
|
||||
|
||||
|
||||
public double horizontalDistance(CharacterLine other) {
|
||||
public double horizontalDistance(Line other) {
|
||||
|
||||
double[] xs = new double[4];
|
||||
xs[0] = x0;
|
||||
@ -110,7 +109,7 @@ public class CharacterLine extends BBoxObject {
|
||||
}
|
||||
|
||||
|
||||
public double verticalDistance(CharacterLine other) {
|
||||
public double verticalDistance(Line other) {
|
||||
|
||||
double ym = (y0 + y1) / 2;
|
||||
double yn = (other.y0 + other.y1) / 2;
|
||||
@ -1,4 +1,4 @@
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.docstrum;
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@ -1,22 +1,20 @@
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.docstrum;
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model;
|
||||
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.BBoxObject;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class CharacterZone extends BBoxObject {
|
||||
public class Zone extends BoundingBox {
|
||||
|
||||
private List<CharacterLine> lines;
|
||||
private List<Line> lines;
|
||||
|
||||
|
||||
public CharacterZone(List<CharacterLine> lines) {
|
||||
public Zone(List<Line> lines) {
|
||||
|
||||
lines.sort(Comparator.comparingDouble(CharacterLine::getY));
|
||||
lines.sort(Comparator.comparingDouble(Line::getY));
|
||||
this.lines = lines;
|
||||
buildBox();
|
||||
}
|
||||
@ -29,7 +27,7 @@ public class CharacterZone extends BBoxObject {
|
||||
double maxX = Double.NEGATIVE_INFINITY;
|
||||
double maxY = Double.NEGATIVE_INFINITY;
|
||||
|
||||
for (CharacterLine line : lines) {
|
||||
for (Line line : lines) {
|
||||
|
||||
minX = Math.min(minX, line.getX());
|
||||
minY = Math.min(minY, line.getY());
|
||||
@ -1,27 +0,0 @@
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public final class BoundingBox {
|
||||
|
||||
private final double x;
|
||||
private final double y;
|
||||
private final double width;
|
||||
private final double height;
|
||||
|
||||
|
||||
public boolean contains(BoundingBox contained, double tolerance) {
|
||||
|
||||
return x <= contained.getX() + tolerance && y <= contained.getY() + tolerance && x + width >= contained.getX() + contained.getWidth() - tolerance && y + height >= contained.getY() + contained.getHeight() - tolerance;
|
||||
}
|
||||
|
||||
|
||||
public double getArea() {
|
||||
|
||||
return (height * width);
|
||||
}
|
||||
|
||||
}
|
||||
@ -2,15 +2,15 @@ package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.r
|
||||
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.BBoxObject;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.BoundingBox;
|
||||
|
||||
public class BBoxZoneGroup extends BBoxObject {
|
||||
public class BoundingBoxZoneGroup extends BoundingBox {
|
||||
|
||||
private BBoxObject leftChild;
|
||||
private BBoxObject rightChild;
|
||||
private BoundingBox leftChild;
|
||||
private BoundingBox rightChild;
|
||||
|
||||
|
||||
public BBoxZoneGroup(BBoxObject child1, BBoxObject child2) {
|
||||
public BoundingBoxZoneGroup(BoundingBox child1, BoundingBox child2) {
|
||||
|
||||
this.leftChild = child1;
|
||||
this.rightChild = child2;
|
||||
@ -27,33 +27,33 @@ public class BBoxZoneGroup extends BBoxObject {
|
||||
}
|
||||
|
||||
|
||||
public BBoxObject getLeftChild() {
|
||||
public BoundingBox getLeftChild() {
|
||||
|
||||
return leftChild;
|
||||
}
|
||||
|
||||
|
||||
public BBoxObject getRightChild() {
|
||||
public BoundingBox getRightChild() {
|
||||
|
||||
return rightChild;
|
||||
}
|
||||
|
||||
|
||||
public BBoxZoneGroup setLeftChild(BBoxObject obj) {
|
||||
public BoundingBoxZoneGroup setLeftChild(BoundingBox obj) {
|
||||
|
||||
this.leftChild = obj;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public BBoxZoneGroup setRightChild(BBoxObject obj) {
|
||||
public BoundingBoxZoneGroup setRightChild(BoundingBox obj) {
|
||||
|
||||
this.rightChild = obj;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public BBoxZoneGroup setBounds(double x0, double y0, double x1, double y1) {
|
||||
public BoundingBoxZoneGroup setBounds(double x0, double y0, double x1, double y1) {
|
||||
|
||||
assert x1 >= x0;
|
||||
assert y1 >= y0;
|
||||
@ -1,6 +1,6 @@
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.readingorder;
|
||||
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.utils.DoubleUtils;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.utils.DoubleUtils;
|
||||
|
||||
public class DistElem<E> implements Comparable<DistElem<E>> {
|
||||
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.readingorder;
|
||||
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.BBoxObject;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.BoundingBox;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.docstrum.CharacterZone;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.BoundingBox;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.Zone;
|
||||
|
||||
/**
|
||||
* A set-like data structure for objects placed on a plane. Can efficiently find objects in a certain rectangular area.
|
||||
@ -20,7 +20,7 @@ public class DocumentPlane {
|
||||
/**
|
||||
* List of objects on the plane. Stored in a random order
|
||||
*/
|
||||
private final List<BBoxObject> objs;
|
||||
private final List<BoundingBox> objs;
|
||||
/**
|
||||
* Size of a grid square. If gridSize=50, then the plane is divided into squares of size 50. Each square contains
|
||||
* objects placed in a 50x50 area
|
||||
@ -31,7 +31,7 @@ public class DocumentPlane {
|
||||
* grid square. Single object can be stored under several keys (depending on its physical size). Grid squares are
|
||||
* lazy-initialized.
|
||||
*/
|
||||
private final Map<GridXY, List<BBoxObject>> grid;
|
||||
private final Map<GridXY, List<BoundingBox>> grid;
|
||||
|
||||
/**
|
||||
* Representation of XY coordinates
|
||||
@ -76,18 +76,18 @@ public class DocumentPlane {
|
||||
}
|
||||
|
||||
|
||||
public List<BBoxObject> getObjects() {
|
||||
public List<BoundingBox> getObjects() {
|
||||
|
||||
return objs;
|
||||
}
|
||||
|
||||
|
||||
public DocumentPlane(List<CharacterZone> objectList, int gridSize) {
|
||||
public DocumentPlane(List<Zone> objectList, int gridSize) {
|
||||
|
||||
this.grid = new HashMap<GridXY, List<BBoxObject>>();
|
||||
this.objs = new ArrayList<BBoxObject>();
|
||||
this.grid = new HashMap<GridXY, List<BoundingBox>>();
|
||||
this.objs = new ArrayList<BoundingBox>();
|
||||
this.gridSize = gridSize;
|
||||
for (CharacterZone obj : objectList) {
|
||||
for (Zone obj : objectList) {
|
||||
add(obj);
|
||||
}
|
||||
}
|
||||
@ -100,15 +100,15 @@ public class DocumentPlane {
|
||||
* @param obj2 object
|
||||
* @return object list
|
||||
*/
|
||||
public List<BBoxObject> findObjectsBetween(BBoxObject obj1, BBoxObject obj2) {
|
||||
public List<BoundingBox> findObjectsBetween(BoundingBox obj1, BoundingBox obj2) {
|
||||
|
||||
double x0 = Math.min(obj1.getX(), obj2.getX());
|
||||
double y0 = Math.min(obj1.getY(), obj2.getY());
|
||||
double x1 = Math.max(obj1.getX() + obj1.getWidth(), obj2.getX() + obj2.getWidth());
|
||||
double y1 = Math.max(obj1.getY() + obj1.getHeight(), obj2.getY() + obj2.getHeight());
|
||||
assert x1 >= x0 && y1 >= y0;
|
||||
BoundingBox searchBounds = new BoundingBox(x0, y0, x1 - x0, y1 - y0);
|
||||
List<BBoxObject> objsBetween = find(searchBounds);
|
||||
Rectangle2D searchBounds = new Rectangle2D.Double(x0, y0, x1 - x0, y1 - y0);
|
||||
List<BoundingBox> objsBetween = find(searchBounds);
|
||||
/*
|
||||
* the rectangle area must contain at least obj1 and obj2
|
||||
*/
|
||||
@ -125,9 +125,9 @@ public class DocumentPlane {
|
||||
* @param obj2 object
|
||||
* @return true if anything is placed between, false otherwise
|
||||
*/
|
||||
public boolean anyObjectsBetween(BBoxObject obj1, BBoxObject obj2) {
|
||||
public boolean anyObjectsBetween(BoundingBox obj1, BoundingBox obj2) {
|
||||
|
||||
List<BBoxObject> lObjs = findObjectsBetween(obj1, obj2);
|
||||
List<BoundingBox> lObjs = findObjectsBetween(obj1, obj2);
|
||||
return !(lObjs.isEmpty());
|
||||
}
|
||||
|
||||
@ -138,7 +138,7 @@ public class DocumentPlane {
|
||||
* @param obj object
|
||||
* @return document plane
|
||||
*/
|
||||
public DocumentPlane add(BBoxObject obj) {
|
||||
public DocumentPlane add(BoundingBox obj) {
|
||||
|
||||
int objsBefore = this.objs.size();
|
||||
/*
|
||||
@ -151,7 +151,7 @@ public class DocumentPlane {
|
||||
/*
|
||||
* add the non-existing key
|
||||
*/
|
||||
grid.put(xy, new ArrayList<BBoxObject>());
|
||||
grid.put(xy, new ArrayList<BoundingBox>());
|
||||
grid.get(xy).add(obj);
|
||||
assert grid.get(xy).size() == 1;
|
||||
} else {
|
||||
@ -172,7 +172,7 @@ public class DocumentPlane {
|
||||
}
|
||||
|
||||
|
||||
public DocumentPlane remove(BBoxObject obj) {
|
||||
public DocumentPlane remove(BoundingBox obj) {
|
||||
/*
|
||||
* iterate over grid squares
|
||||
*/
|
||||
@ -196,10 +196,10 @@ public class DocumentPlane {
|
||||
* @param searchBounds is a search rectangle
|
||||
* @return list of objects in!side search rectangle
|
||||
*/
|
||||
public List<BBoxObject> find(BoundingBox searchBounds) {
|
||||
public List<BoundingBox> find(Rectangle2D searchBounds) {
|
||||
|
||||
List<BBoxObject> done = new ArrayList<BBoxObject>(); //contains already considered objects (wrt. optimization)
|
||||
List<BBoxObject> ret = new ArrayList<BBoxObject>();
|
||||
List<BoundingBox> done = new ArrayList<BoundingBox>(); //contains already considered objects (wrt. optimization)
|
||||
List<BoundingBox> ret = new ArrayList<BoundingBox>();
|
||||
double x0 = searchBounds.getX();
|
||||
double y0 = searchBounds.getY();
|
||||
double y1 = searchBounds.getY() + searchBounds.getHeight();
|
||||
@ -213,7 +213,7 @@ public class DocumentPlane {
|
||||
if (!grid.containsKey(xy)) {
|
||||
continue;
|
||||
}
|
||||
for (BBoxObject obj : grid.get(xy)) {
|
||||
for (BoundingBox obj : grid.get(xy)) {
|
||||
if (done.contains(obj)) /*
|
||||
* omit if already checked
|
||||
*/ {
|
||||
@ -244,9 +244,9 @@ public class DocumentPlane {
|
||||
*/
|
||||
protected int elementsInGrid() {
|
||||
|
||||
List<BBoxObject> objs_ = new ArrayList<BBoxObject>();
|
||||
List<BoundingBox> objs_ = new ArrayList<BoundingBox>();
|
||||
for (GridXY coord : grid.keySet()) {
|
||||
for (BBoxObject obj : grid.get(coord)) {
|
||||
for (BoundingBox obj : grid.get(coord)) {
|
||||
if (!objs_.contains(obj)) {
|
||||
objs_.add(obj);
|
||||
}
|
||||
|
||||
@ -3,25 +3,25 @@ package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.r
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.docstrum.CharacterZone;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.Zone;
|
||||
|
||||
public class TreeToListConverter {
|
||||
|
||||
public List<CharacterZone> convertToList(BBoxZoneGroup obj) {
|
||||
public List<Zone> convertToList(BoundingBoxZoneGroup obj) {
|
||||
|
||||
List<CharacterZone> ret = new ArrayList<>();
|
||||
if (obj.getLeftChild() instanceof CharacterZone) {
|
||||
CharacterZone zone = (CharacterZone) obj.getLeftChild();
|
||||
List<Zone> ret = new ArrayList<>();
|
||||
if (obj.getLeftChild() instanceof Zone) {
|
||||
Zone zone = (Zone) obj.getLeftChild();
|
||||
ret.add(zone);
|
||||
} else { // obj.getLeftChild() instanceof BxZoneGroup
|
||||
ret.addAll(convertToList((BBoxZoneGroup) obj.getLeftChild()));
|
||||
ret.addAll(convertToList((BoundingBoxZoneGroup) obj.getLeftChild()));
|
||||
}
|
||||
|
||||
if (obj.getRightChild() instanceof CharacterZone) {
|
||||
CharacterZone zone = (CharacterZone) obj.getRightChild();
|
||||
if (obj.getRightChild() instanceof Zone) {
|
||||
Zone zone = (Zone) obj.getRightChild();
|
||||
ret.add(zone);
|
||||
} else { // obj.getRightChild() instanceof BxZoneGroup
|
||||
ret.addAll(convertToList((BBoxZoneGroup) obj.getRightChild()));
|
||||
ret.addAll(convertToList((BoundingBoxZoneGroup) obj.getRightChild()));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum;
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
@ -6,9 +6,10 @@ import java.util.List;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.AngleFilter;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.Character;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.DisjointSets;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.docstrum.Character;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.docstrum.CharacterLine;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.Line;
|
||||
|
||||
@Service
|
||||
public class LineBuilderService {
|
||||
@ -18,7 +19,7 @@ public class LineBuilderService {
|
||||
private static final double ANGLE_TOLERANCE = Math.PI / 6;
|
||||
|
||||
|
||||
public List<CharacterLine> buildLines(List<Character> characters, double characterSpacing, double lineSpacing) {
|
||||
public List<Line> buildLines(List<Character> characters, double characterSpacing, double lineSpacing) {
|
||||
|
||||
double maxHorizontalDistance = characterSpacing * CHARACTER_SPACING_DISTANCE_MULTIPLIER;
|
||||
double maxVerticalDistance = lineSpacing * MAX_VERTICAL_CHARACTER_DISTANCE;
|
||||
@ -36,13 +37,13 @@ public class LineBuilderService {
|
||||
});
|
||||
});
|
||||
|
||||
List<CharacterLine> lines = new ArrayList<>();
|
||||
List<Line> lines = new ArrayList<>();
|
||||
sets.forEach(group -> {
|
||||
List<Character> lineComponents = new ArrayList<>(group);
|
||||
lineComponents.sort(Comparator.comparingDouble(Character::getX));
|
||||
lines.add(new CharacterLine(lineComponents, characterSpacing));
|
||||
lines.add(new Line(lineComponents, characterSpacing));
|
||||
});
|
||||
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum;
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
@ -6,8 +6,8 @@ import java.util.List;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.docstrum.Character;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.docstrum.Neighbor;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.Character;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.Neighbor;
|
||||
|
||||
@Service
|
||||
public class NearestNeighbourService {
|
||||
@ -1,4 +1,4 @@
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum;
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@ -8,42 +8,42 @@ import java.util.List;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.BBoxObject;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.docstrum.CharacterZone;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.utils.DoubleUtils;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.readingorder.BBoxZoneGroup;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.BoundingBox;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.Zone;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.readingorder.BoundingBoxZoneGroup;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.readingorder.DistElem;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.readingorder.DocumentPlane;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.readingorder.TreeToListConverter;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.utils.DoubleUtils;
|
||||
|
||||
@Service
|
||||
public class HierarchicalReadingOrderResolver {
|
||||
public class ReadingOrderService {
|
||||
|
||||
static final int GRIDSIZE = 50;
|
||||
static final double EPS = 0.01;
|
||||
static final int MAX_ZONES = 1000;
|
||||
static final Comparator<BBoxObject> Y_ASCENDING_ORDER = new Comparator<BBoxObject>() {
|
||||
static final Comparator<BoundingBox> Y_ASCENDING_ORDER = new Comparator<BoundingBox>() {
|
||||
|
||||
@Override
|
||||
public int compare(BBoxObject o1, BBoxObject o2) {
|
||||
public int compare(BoundingBox o1, BoundingBox o2) {
|
||||
|
||||
return DoubleUtils.compareDouble(o1.getY(), o2.getY(), EPS);
|
||||
}
|
||||
};
|
||||
|
||||
static final Comparator<BBoxObject> X_ASCENDING_ORDER = new Comparator<BBoxObject>() {
|
||||
static final Comparator<BoundingBox> X_ASCENDING_ORDER = new Comparator<BoundingBox>() {
|
||||
|
||||
@Override
|
||||
public int compare(BBoxObject o1, BBoxObject o2) {
|
||||
public int compare(BoundingBox o1, BoundingBox o2) {
|
||||
|
||||
return DoubleUtils.compareDouble(o1.getX(), o2.getX(), EPS);
|
||||
}
|
||||
};
|
||||
|
||||
static final Comparator<BBoxObject> YX_ASCENDING_ORDER = new Comparator<BBoxObject>() {
|
||||
static final Comparator<BoundingBox> YX_ASCENDING_ORDER = new Comparator<BoundingBox>() {
|
||||
|
||||
@Override
|
||||
public int compare(BBoxObject o1, BBoxObject o2) {
|
||||
public int compare(BoundingBox o1, BoundingBox o2) {
|
||||
|
||||
int yCompare = Y_ASCENDING_ORDER.compare(o1, o2);
|
||||
return yCompare == 0 ? X_ASCENDING_ORDER.compare(o1, o2) : yCompare;
|
||||
@ -51,9 +51,9 @@ public class HierarchicalReadingOrderResolver {
|
||||
};
|
||||
|
||||
|
||||
public List<CharacterZone> resolve(List<CharacterZone> zones) {
|
||||
public List<Zone> resolve(List<Zone> zones) {
|
||||
|
||||
List<CharacterZone> orderedZones;
|
||||
List<Zone> orderedZones;
|
||||
if (zones.size() > MAX_ZONES) {
|
||||
orderedZones = new ArrayList<>(zones);
|
||||
Collections.sort(orderedZones, YX_ASCENDING_ORDER);
|
||||
@ -64,19 +64,19 @@ public class HierarchicalReadingOrderResolver {
|
||||
}
|
||||
|
||||
|
||||
private List<CharacterZone> reorderZones(List<CharacterZone> unorderedZones) {
|
||||
private List<Zone> reorderZones(List<Zone> unorderedZones) {
|
||||
|
||||
if (unorderedZones.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
} else if (unorderedZones.size() == 1) {
|
||||
List<CharacterZone> ret = new ArrayList<>(1);
|
||||
List<Zone> ret = new ArrayList<>(1);
|
||||
ret.add(unorderedZones.get(0));
|
||||
return ret;
|
||||
} else {
|
||||
BBoxZoneGroup bxZonesTree = groupZonesHierarchically(unorderedZones);
|
||||
BoundingBoxZoneGroup bxZonesTree = groupZonesHierarchically(unorderedZones);
|
||||
sortGroupedZones(bxZonesTree);
|
||||
TreeToListConverter treeConverter = new TreeToListConverter();
|
||||
List<CharacterZone> orderedZones = treeConverter.convertToList(bxZonesTree);
|
||||
List<Zone> orderedZones = treeConverter.convertToList(bxZonesTree);
|
||||
assert unorderedZones.size() == orderedZones.size();
|
||||
return orderedZones;
|
||||
}
|
||||
@ -90,50 +90,50 @@ public class HierarchicalReadingOrderResolver {
|
||||
* @param zones is a list of unordered zones
|
||||
* @return root of the zones clustered in a tree
|
||||
*/
|
||||
private BBoxZoneGroup groupZonesHierarchically(List<CharacterZone> zones) {
|
||||
private BoundingBoxZoneGroup groupZonesHierarchically(List<Zone> zones) {
|
||||
/*
|
||||
* Distance tuples are stored sorted by ascending distance value
|
||||
*/
|
||||
List<DistElem<BBoxObject>> dists = new ArrayList<DistElem<BBoxObject>>(zones.size() * zones.size() / 2);
|
||||
List<DistElem<BoundingBox>> dists = new ArrayList<DistElem<BoundingBox>>(zones.size() * zones.size() / 2);
|
||||
for (int idx1 = 0; idx1 < zones.size(); ++idx1) {
|
||||
for (int idx2 = idx1 + 1; idx2 < zones.size(); ++idx2) {
|
||||
CharacterZone zone1 = zones.get(idx1);
|
||||
CharacterZone zone2 = zones.get(idx2);
|
||||
dists.add(new DistElem<BBoxObject>(false, distance(zone1, zone2), zone1, zone2));
|
||||
Zone zone1 = zones.get(idx1);
|
||||
Zone zone2 = zones.get(idx2);
|
||||
dists.add(new DistElem<BoundingBox>(false, distance(zone1, zone2), zone1, zone2));
|
||||
}
|
||||
}
|
||||
Collections.sort(dists);
|
||||
DocumentPlane plane = new DocumentPlane(zones, GRIDSIZE);
|
||||
while (!dists.isEmpty()) {
|
||||
DistElem<BBoxObject> distElem = dists.get(0);
|
||||
DistElem<BoundingBox> distElem = dists.get(0);
|
||||
dists.remove(0);
|
||||
if (!distElem.isC() && plane.anyObjectsBetween(distElem.getObj1(), distElem.getObj2())) {
|
||||
dists.add(new DistElem<BBoxObject>(true, distElem.getDist(), distElem.getObj1(), distElem.getObj2()));
|
||||
dists.add(new DistElem<BoundingBox>(true, distElem.getDist(), distElem.getObj1(), distElem.getObj2()));
|
||||
continue;
|
||||
}
|
||||
BBoxZoneGroup newGroup = new BBoxZoneGroup(distElem.getObj1(), distElem.getObj2());
|
||||
BoundingBoxZoneGroup newGroup = new BoundingBoxZoneGroup(distElem.getObj1(), distElem.getObj2());
|
||||
plane.remove(distElem.getObj1()).remove(distElem.getObj2());
|
||||
dists = removeDistElementsContainingObject(dists, distElem.getObj1());
|
||||
dists = removeDistElementsContainingObject(dists, distElem.getObj2());
|
||||
for (BBoxObject other : plane.getObjects()) {
|
||||
dists.add(new DistElem<BBoxObject>(false, distance(other, newGroup), newGroup, other));
|
||||
for (BoundingBox other : plane.getObjects()) {
|
||||
dists.add(new DistElem<BoundingBox>(false, distance(other, newGroup), newGroup, other));
|
||||
}
|
||||
Collections.sort(dists);
|
||||
plane.add(newGroup);
|
||||
}
|
||||
|
||||
assert plane.getObjects().size() == 1 : "There should be one object left at the plane after grouping";
|
||||
return (BBoxZoneGroup) plane.getObjects().get(0);
|
||||
return (BoundingBoxZoneGroup) plane.getObjects().get(0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes all distance tuples containing obj
|
||||
*/
|
||||
private List<DistElem<BBoxObject>> removeDistElementsContainingObject(Collection<DistElem<BBoxObject>> list, BBoxObject obj) {
|
||||
private List<DistElem<BoundingBox>> removeDistElementsContainingObject(Collection<DistElem<BoundingBox>> list, BoundingBox obj) {
|
||||
|
||||
List<DistElem<BBoxObject>> ret = new ArrayList<DistElem<BBoxObject>>();
|
||||
for (DistElem<BBoxObject> distElem : list) {
|
||||
List<DistElem<BoundingBox>> ret = new ArrayList<DistElem<BoundingBox>>();
|
||||
for (DistElem<BoundingBox> distElem : list) {
|
||||
if (distElem.getObj1() != obj && distElem.getObj2() != obj) {
|
||||
ret.add(distElem);
|
||||
}
|
||||
@ -149,28 +149,28 @@ public class HierarchicalReadingOrderResolver {
|
||||
*
|
||||
* @param group
|
||||
*/
|
||||
private void sortGroupedZones(BBoxZoneGroup group) {
|
||||
private void sortGroupedZones(BoundingBoxZoneGroup group) {
|
||||
|
||||
BBoxObject leftChild = group.getLeftChild();
|
||||
BBoxObject rightChild = group.getRightChild();
|
||||
BoundingBox leftChild = group.getLeftChild();
|
||||
BoundingBox rightChild = group.getRightChild();
|
||||
if (shouldBeSwapped(leftChild, rightChild)) {
|
||||
// swap
|
||||
group.setLeftChild(rightChild);
|
||||
group.setRightChild(leftChild);
|
||||
}
|
||||
|
||||
if (leftChild instanceof BBoxZoneGroup) // if the child is a tree node, then recurse
|
||||
if (leftChild instanceof BoundingBoxZoneGroup) // if the child is a tree node, then recurse
|
||||
{
|
||||
sortGroupedZones((BBoxZoneGroup) leftChild);
|
||||
sortGroupedZones((BoundingBoxZoneGroup) leftChild);
|
||||
}
|
||||
if (rightChild instanceof BBoxZoneGroup) // as above - recurse
|
||||
if (rightChild instanceof BoundingBoxZoneGroup) // as above - recurse
|
||||
{
|
||||
sortGroupedZones((BBoxZoneGroup) rightChild);
|
||||
sortGroupedZones((BoundingBoxZoneGroup) rightChild);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean shouldBeSwapped(BBoxObject first, BBoxObject second) {
|
||||
private boolean shouldBeSwapped(BoundingBox first, BoundingBox second) {
|
||||
|
||||
double cx, cy, cw, ch, ox, oy, ow, oh;
|
||||
cx = first.getBBox().getX();
|
||||
@ -216,7 +216,7 @@ public class HierarchicalReadingOrderResolver {
|
||||
*
|
||||
* @return distance value based on objects' coordinates and physical size on a plane
|
||||
*/
|
||||
private double distance(BBoxObject obj1, BBoxObject obj2) {
|
||||
private double distance(BoundingBox obj1, BoundingBox obj2) {
|
||||
|
||||
double x0 = Math.min(obj1.getX(), obj2.getX());
|
||||
double y0 = Math.min(obj1.getY(), obj2.getY());
|
||||
@ -1,12 +1,13 @@
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum;
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.AngleFilter;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.Character;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.Histogram;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.docstrum.Character;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.docstrum.Neighbor;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.Neighbor;
|
||||
|
||||
@Service
|
||||
public class SpacingService {
|
||||
@ -1,4 +1,4 @@
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum;
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -6,8 +6,8 @@ import java.util.List;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.DisjointSets;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.docstrum.CharacterLine;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.docstrum.CharacterZone;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.Line;
|
||||
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.Zone;
|
||||
|
||||
@Service
|
||||
public class ZoneBuilderService {
|
||||
@ -28,14 +28,14 @@ public class ZoneBuilderService {
|
||||
public static final int MAX_ZONES = 300;
|
||||
|
||||
|
||||
public List<CharacterZone> buildZones(List<CharacterLine> lines, double characterSpacing, double lineSpacing) {
|
||||
public List<Zone> buildZones(List<Line> lines, double characterSpacing, double lineSpacing) {
|
||||
|
||||
double minHorizontalDistance = characterSpacing * MIN_HORIZONTAL_DISTANCE_MULTIPLIER;
|
||||
double maxVerticalDistance = lineSpacing * MAX_VERTICAL_DISTANCE_MULTIPLIER;
|
||||
double minHorizontalMergeDistance = characterSpacing * MIN_HORIZONTAL_MERGE_DISTANCE_MULTIPLIER;
|
||||
double maxVerticalMergeDistance = lineSpacing * MAX_VERTICAL_MERGE_DISTANCE_MULTIPLIER;
|
||||
|
||||
DisjointSets<CharacterLine> sets = new DisjointSets<>(lines);
|
||||
DisjointSets<Line> sets = new DisjointSets<>(lines);
|
||||
|
||||
double meanHeight = calculateMeanHeight(lines);
|
||||
|
||||
@ -61,28 +61,28 @@ public class ZoneBuilderService {
|
||||
}
|
||||
}));
|
||||
|
||||
List<CharacterZone> zones = new ArrayList<>();
|
||||
List<Zone> zones = new ArrayList<>();
|
||||
sets.forEach(group -> {
|
||||
zones.add(new CharacterZone(new ArrayList<>(group)));
|
||||
zones.add(new Zone(new ArrayList<>(group)));
|
||||
});
|
||||
|
||||
if (zones.size() > MAX_ZONES) {
|
||||
List<CharacterLine> oneZoneLines = new ArrayList<>();
|
||||
for (CharacterZone zone : zones) {
|
||||
List<Line> oneZoneLines = new ArrayList<>();
|
||||
for (Zone zone : zones) {
|
||||
oneZoneLines.addAll(zone.getLines());
|
||||
}
|
||||
return List.of(new CharacterZone(oneZoneLines));
|
||||
return List.of(new Zone(oneZoneLines));
|
||||
}
|
||||
|
||||
return zones;
|
||||
}
|
||||
|
||||
|
||||
private double calculateMeanHeight(List<CharacterLine> lines) {
|
||||
private double calculateMeanHeight(List<Line> lines) {
|
||||
|
||||
double meanHeight = 0.0;
|
||||
double weights = 0.0;
|
||||
for (CharacterLine line : lines) {
|
||||
for (Line line : lines) {
|
||||
double weight = line.getLength();
|
||||
meanHeight += line.getHeight() * weight;
|
||||
weights += weight;
|
||||
@ -1,4 +1,4 @@
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.utils;
|
||||
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.utils;
|
||||
|
||||
public class DoubleUtils {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user