This commit is contained in:
Dominique Eifländer 2024-02-16 14:50:31 +01:00
parent 9e5778d4b2
commit e14d953b04
20 changed files with 181 additions and 208 deletions

View File

@ -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.DocuMineClassificationService;
import com.knecon.fforesight.service.layoutparser.processor.services.classification.RedactManagerClassificationService; 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.classification.TaasClassificationService;
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.DocstrumSegmenter; import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.DocstrumSegmentationService;
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.HierarchicalReadingOrderResolver;
import com.knecon.fforesight.service.layoutparser.processor.services.factory.DocumentGraphFactory; 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.DocumentDataMapper;
import com.knecon.fforesight.service.layoutparser.processor.services.mapper.TaasDocumentDataMapper; import com.knecon.fforesight.service.layoutparser.processor.services.mapper.TaasDocumentDataMapper;
@ -90,8 +89,7 @@ public class LayoutParsingPipeline {
RedactManagerBlockificationService redactManagerBlockificationService; RedactManagerBlockificationService redactManagerBlockificationService;
LayoutGridService layoutGridService; LayoutGridService layoutGridService;
ObservationRegistry observationRegistry; ObservationRegistry observationRegistry;
DocstrumSegmenter docstrumSegmenter; DocstrumSegmentationService docstrumSegmentationService;
HierarchicalReadingOrderResolver hierarchicalReadingOrderResolver;
public LayoutParsingFinishedEvent parseLayoutAndSaveFilesToStorage(LayoutParsingRequest layoutParsingRequest) throws IOException { public LayoutParsingFinishedEvent parseLayoutAndSaveFilesToStorage(LayoutParsingRequest layoutParsingRequest) throws IOException {
@ -251,8 +249,7 @@ public class LayoutParsingPipeline {
// Docstrum // Docstrum
AtomicInteger num = new AtomicInteger(pageNumber); AtomicInteger num = new AtomicInteger(pageNumber);
var zones = docstrumSegmenter.segmentPage(stripper.getTextPositionSequences()); var zones = docstrumSegmentationService.segmentPage(stripper.getTextPositionSequences());
zones = hierarchicalReadingOrderResolver.resolve(zones);
List<AbstractPageBlock> pageBlocks = new ArrayList<>(); List<AbstractPageBlock> pageBlocks = new ArrayList<>();
AtomicInteger numOnPage = new AtomicInteger(1); AtomicInteger numOnPage = new AtomicInteger(1);

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -1,6 +1,4 @@
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum; package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model;
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.docstrum.Neighbor;
/** /**
* Filter class for neighbor objects that checks if the angle of the * Filter class for neighbor objects that checks if the angle of the

View File

@ -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 java.awt.geom.Rectangle2D;
import lombok.Data; import lombok.Data;
@Data @Data
public abstract class BBoxObject { public abstract class BoundingBox {
private Rectangle2D bBox; private Rectangle2D bBox;

View File

@ -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.ArrayList;
import java.util.List; import java.util.List;

View File

@ -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.awt.geom.Rectangle2D;
import java.util.ArrayList; import java.util.ArrayList;
@ -6,12 +6,11 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import com.knecon.fforesight.service.layoutparser.processor.model.text.TextPositionSequence; 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; import lombok.Data;
@Data @Data
public class CharacterLine extends BBoxObject { public class Line extends BoundingBox {
private static final double WORD_DISTANCE_MULTIPLIER = 0.2; 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<>(); private final List<TextPositionSequence> words = new ArrayList<>();
public CharacterLine(List<Character> characters, double wordSpacing) { public Line(List<Character> characters, double wordSpacing) {
this.characters = characters; 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()); double diff = Math.abs(getAngle() - j.getAngle());
if (diff <= Math.PI / 2) { 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]; double[] xs = new double[4];
xs[0] = x0; 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 ym = (y0 + y1) / 2;
double yn = (other.y0 + other.y1) / 2; double yn = (other.y0 + other.y1) / 2;

View File

@ -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; import lombok.Getter;

View File

@ -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.awt.geom.Rectangle2D;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.BBoxObject;
import lombok.Data; import lombok.Data;
@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; this.lines = lines;
buildBox(); buildBox();
} }
@ -29,7 +27,7 @@ public class CharacterZone extends BBoxObject {
double maxX = Double.NEGATIVE_INFINITY; double maxX = Double.NEGATIVE_INFINITY;
double maxY = Double.NEGATIVE_INFINITY; double maxY = Double.NEGATIVE_INFINITY;
for (CharacterLine line : lines) { for (Line line : lines) {
minX = Math.min(minX, line.getX()); minX = Math.min(minX, line.getX());
minY = Math.min(minY, line.getY()); minY = Math.min(minY, line.getY());

View File

@ -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);
}
}

View File

@ -2,15 +2,15 @@ package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.r
import java.awt.geom.Rectangle2D; 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 BoundingBox leftChild;
private BBoxObject rightChild; private BoundingBox rightChild;
public BBoxZoneGroup(BBoxObject child1, BBoxObject child2) { public BoundingBoxZoneGroup(BoundingBox child1, BoundingBox child2) {
this.leftChild = child1; this.leftChild = child1;
this.rightChild = child2; this.rightChild = child2;
@ -27,33 +27,33 @@ public class BBoxZoneGroup extends BBoxObject {
} }
public BBoxObject getLeftChild() { public BoundingBox getLeftChild() {
return leftChild; return leftChild;
} }
public BBoxObject getRightChild() { public BoundingBox getRightChild() {
return rightChild; return rightChild;
} }
public BBoxZoneGroup setLeftChild(BBoxObject obj) { public BoundingBoxZoneGroup setLeftChild(BoundingBox obj) {
this.leftChild = obj; this.leftChild = obj;
return this; return this;
} }
public BBoxZoneGroup setRightChild(BBoxObject obj) { public BoundingBoxZoneGroup setRightChild(BoundingBox obj) {
this.rightChild = obj; this.rightChild = obj;
return this; 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 x1 >= x0;
assert y1 >= y0; assert y1 >= y0;

View File

@ -1,6 +1,6 @@
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.readingorder; 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>> { public class DistElem<E> implements Comparable<DistElem<E>> {

View File

@ -1,13 +1,13 @@
package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.readingorder; package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.readingorder;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; 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.BoundingBox;
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.BoundingBox; import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.Zone;
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.docstrum.CharacterZone;
/** /**
* A set-like data structure for objects placed on a plane. Can efficiently find objects in a certain rectangular area. * 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 * 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 * 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 * 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 * grid square. Single object can be stored under several keys (depending on its physical size). Grid squares are
* lazy-initialized. * lazy-initialized.
*/ */
private final Map<GridXY, List<BBoxObject>> grid; private final Map<GridXY, List<BoundingBox>> grid;
/** /**
* Representation of XY coordinates * Representation of XY coordinates
@ -76,18 +76,18 @@ public class DocumentPlane {
} }
public List<BBoxObject> getObjects() { public List<BoundingBox> getObjects() {
return objs; return objs;
} }
public DocumentPlane(List<CharacterZone> objectList, int gridSize) { public DocumentPlane(List<Zone> objectList, int gridSize) {
this.grid = new HashMap<GridXY, List<BBoxObject>>(); this.grid = new HashMap<GridXY, List<BoundingBox>>();
this.objs = new ArrayList<BBoxObject>(); this.objs = new ArrayList<BoundingBox>();
this.gridSize = gridSize; this.gridSize = gridSize;
for (CharacterZone obj : objectList) { for (Zone obj : objectList) {
add(obj); add(obj);
} }
} }
@ -100,15 +100,15 @@ public class DocumentPlane {
* @param obj2 object * @param obj2 object
* @return object list * @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 x0 = Math.min(obj1.getX(), obj2.getX());
double y0 = Math.min(obj1.getY(), obj2.getY()); double y0 = Math.min(obj1.getY(), obj2.getY());
double x1 = Math.max(obj1.getX() + obj1.getWidth(), obj2.getX() + obj2.getWidth()); double x1 = Math.max(obj1.getX() + obj1.getWidth(), obj2.getX() + obj2.getWidth());
double y1 = Math.max(obj1.getY() + obj1.getHeight(), obj2.getY() + obj2.getHeight()); double y1 = Math.max(obj1.getY() + obj1.getHeight(), obj2.getY() + obj2.getHeight());
assert x1 >= x0 && y1 >= y0; assert x1 >= x0 && y1 >= y0;
BoundingBox searchBounds = new BoundingBox(x0, y0, x1 - x0, y1 - y0); Rectangle2D searchBounds = new Rectangle2D.Double(x0, y0, x1 - x0, y1 - y0);
List<BBoxObject> objsBetween = find(searchBounds); List<BoundingBox> objsBetween = find(searchBounds);
/* /*
* the rectangle area must contain at least obj1 and obj2 * the rectangle area must contain at least obj1 and obj2
*/ */
@ -125,9 +125,9 @@ public class DocumentPlane {
* @param obj2 object * @param obj2 object
* @return true if anything is placed between, false otherwise * @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()); return !(lObjs.isEmpty());
} }
@ -138,7 +138,7 @@ public class DocumentPlane {
* @param obj object * @param obj object
* @return document plane * @return document plane
*/ */
public DocumentPlane add(BBoxObject obj) { public DocumentPlane add(BoundingBox obj) {
int objsBefore = this.objs.size(); int objsBefore = this.objs.size();
/* /*
@ -151,7 +151,7 @@ public class DocumentPlane {
/* /*
* add the non-existing key * add the non-existing key
*/ */
grid.put(xy, new ArrayList<BBoxObject>()); grid.put(xy, new ArrayList<BoundingBox>());
grid.get(xy).add(obj); grid.get(xy).add(obj);
assert grid.get(xy).size() == 1; assert grid.get(xy).size() == 1;
} else { } else {
@ -172,7 +172,7 @@ public class DocumentPlane {
} }
public DocumentPlane remove(BBoxObject obj) { public DocumentPlane remove(BoundingBox obj) {
/* /*
* iterate over grid squares * iterate over grid squares
*/ */
@ -196,10 +196,10 @@ public class DocumentPlane {
* @param searchBounds is a search rectangle * @param searchBounds is a search rectangle
* @return list of objects in!side 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<BoundingBox> done = new ArrayList<BoundingBox>(); //contains already considered objects (wrt. optimization)
List<BBoxObject> ret = new ArrayList<BBoxObject>(); List<BoundingBox> ret = new ArrayList<BoundingBox>();
double x0 = searchBounds.getX(); double x0 = searchBounds.getX();
double y0 = searchBounds.getY(); double y0 = searchBounds.getY();
double y1 = searchBounds.getY() + searchBounds.getHeight(); double y1 = searchBounds.getY() + searchBounds.getHeight();
@ -213,7 +213,7 @@ public class DocumentPlane {
if (!grid.containsKey(xy)) { if (!grid.containsKey(xy)) {
continue; continue;
} }
for (BBoxObject obj : grid.get(xy)) { for (BoundingBox obj : grid.get(xy)) {
if (done.contains(obj)) /* if (done.contains(obj)) /*
* omit if already checked * omit if already checked
*/ { */ {
@ -244,9 +244,9 @@ public class DocumentPlane {
*/ */
protected int elementsInGrid() { protected int elementsInGrid() {
List<BBoxObject> objs_ = new ArrayList<BBoxObject>(); List<BoundingBox> objs_ = new ArrayList<BoundingBox>();
for (GridXY coord : grid.keySet()) { for (GridXY coord : grid.keySet()) {
for (BBoxObject obj : grid.get(coord)) { for (BoundingBox obj : grid.get(coord)) {
if (!objs_.contains(obj)) { if (!objs_.contains(obj)) {
objs_.add(obj); objs_.add(obj);
} }

View File

@ -3,25 +3,25 @@ package com.knecon.fforesight.service.layoutparser.processor.services.docstrum.r
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; 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 class TreeToListConverter {
public List<CharacterZone> convertToList(BBoxZoneGroup obj) { public List<Zone> convertToList(BoundingBoxZoneGroup obj) {
List<CharacterZone> ret = new ArrayList<>(); List<Zone> ret = new ArrayList<>();
if (obj.getLeftChild() instanceof CharacterZone) { if (obj.getLeftChild() instanceof Zone) {
CharacterZone zone = (CharacterZone) obj.getLeftChild(); Zone zone = (Zone) obj.getLeftChild();
ret.add(zone); ret.add(zone);
} else { // obj.getLeftChild() instanceof BxZoneGroup } else { // obj.getLeftChild() instanceof BxZoneGroup
ret.addAll(convertToList((BBoxZoneGroup) obj.getLeftChild())); ret.addAll(convertToList((BoundingBoxZoneGroup) obj.getLeftChild()));
} }
if (obj.getRightChild() instanceof CharacterZone) { if (obj.getRightChild() instanceof Zone) {
CharacterZone zone = (CharacterZone) obj.getRightChild(); Zone zone = (Zone) obj.getRightChild();
ret.add(zone); ret.add(zone);
} else { // obj.getRightChild() instanceof BxZoneGroup } else { // obj.getRightChild() instanceof BxZoneGroup
ret.addAll(convertToList((BBoxZoneGroup) obj.getRightChild())); ret.addAll(convertToList((BoundingBoxZoneGroup) obj.getRightChild()));
} }
return ret; return ret;
} }

View File

@ -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.ArrayList;
import java.util.Comparator; import java.util.Comparator;
@ -6,9 +6,10 @@ import java.util.List;
import org.springframework.stereotype.Service; 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.DisjointSets;
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.docstrum.Character; import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.Line;
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.docstrum.CharacterLine;
@Service @Service
public class LineBuilderService { public class LineBuilderService {
@ -18,7 +19,7 @@ public class LineBuilderService {
private static final double ANGLE_TOLERANCE = Math.PI / 6; 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 maxHorizontalDistance = characterSpacing * CHARACTER_SPACING_DISTANCE_MULTIPLIER;
double maxVerticalDistance = lineSpacing * MAX_VERTICAL_CHARACTER_DISTANCE; 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 -> { sets.forEach(group -> {
List<Character> lineComponents = new ArrayList<>(group); List<Character> lineComponents = new ArrayList<>(group);
lineComponents.sort(Comparator.comparingDouble(Character::getX)); lineComponents.sort(Comparator.comparingDouble(Character::getX));
lines.add(new CharacterLine(lineComponents, characterSpacing)); lines.add(new Line(lineComponents, characterSpacing));
}); });
return lines; return lines;
} }

View File

@ -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.ArrayList;
import java.util.Comparator; import java.util.Comparator;
@ -6,8 +6,8 @@ import java.util.List;
import org.springframework.stereotype.Service; 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.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 @Service
public class NearestNeighbourService { public class NearestNeighbourService {

View File

@ -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.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -8,42 +8,42 @@ import java.util.List;
import org.springframework.stereotype.Service; 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.BoundingBox;
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.docstrum.CharacterZone; import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.Zone;
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.utils.DoubleUtils; import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.readingorder.BoundingBoxZoneGroup;
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.readingorder.BBoxZoneGroup;
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.readingorder.DistElem; 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.DocumentPlane;
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.readingorder.TreeToListConverter; import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.readingorder.TreeToListConverter;
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.utils.DoubleUtils;
@Service @Service
public class HierarchicalReadingOrderResolver { public class ReadingOrderService {
static final int GRIDSIZE = 50; static final int GRIDSIZE = 50;
static final double EPS = 0.01; static final double EPS = 0.01;
static final int MAX_ZONES = 1000; 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 @Override
public int compare(BBoxObject o1, BBoxObject o2) { public int compare(BoundingBox o1, BoundingBox o2) {
return DoubleUtils.compareDouble(o1.getY(), o2.getY(), EPS); 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 @Override
public int compare(BBoxObject o1, BBoxObject o2) { public int compare(BoundingBox o1, BoundingBox o2) {
return DoubleUtils.compareDouble(o1.getX(), o2.getX(), EPS); 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 @Override
public int compare(BBoxObject o1, BBoxObject o2) { public int compare(BoundingBox o1, BoundingBox o2) {
int yCompare = Y_ASCENDING_ORDER.compare(o1, o2); int yCompare = Y_ASCENDING_ORDER.compare(o1, o2);
return yCompare == 0 ? X_ASCENDING_ORDER.compare(o1, o2) : yCompare; 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) { if (zones.size() > MAX_ZONES) {
orderedZones = new ArrayList<>(zones); orderedZones = new ArrayList<>(zones);
Collections.sort(orderedZones, YX_ASCENDING_ORDER); 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()) { if (unorderedZones.isEmpty()) {
return new ArrayList<>(); return new ArrayList<>();
} else if (unorderedZones.size() == 1) { } else if (unorderedZones.size() == 1) {
List<CharacterZone> ret = new ArrayList<>(1); List<Zone> ret = new ArrayList<>(1);
ret.add(unorderedZones.get(0)); ret.add(unorderedZones.get(0));
return ret; return ret;
} else { } else {
BBoxZoneGroup bxZonesTree = groupZonesHierarchically(unorderedZones); BoundingBoxZoneGroup bxZonesTree = groupZonesHierarchically(unorderedZones);
sortGroupedZones(bxZonesTree); sortGroupedZones(bxZonesTree);
TreeToListConverter treeConverter = new TreeToListConverter(); TreeToListConverter treeConverter = new TreeToListConverter();
List<CharacterZone> orderedZones = treeConverter.convertToList(bxZonesTree); List<Zone> orderedZones = treeConverter.convertToList(bxZonesTree);
assert unorderedZones.size() == orderedZones.size(); assert unorderedZones.size() == orderedZones.size();
return orderedZones; return orderedZones;
} }
@ -90,50 +90,50 @@ public class HierarchicalReadingOrderResolver {
* @param zones is a list of unordered zones * @param zones is a list of unordered zones
* @return root of the zones clustered in a tree * @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 * 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 idx1 = 0; idx1 < zones.size(); ++idx1) {
for (int idx2 = idx1 + 1; idx2 < zones.size(); ++idx2) { for (int idx2 = idx1 + 1; idx2 < zones.size(); ++idx2) {
CharacterZone zone1 = zones.get(idx1); Zone zone1 = zones.get(idx1);
CharacterZone zone2 = zones.get(idx2); Zone zone2 = zones.get(idx2);
dists.add(new DistElem<BBoxObject>(false, distance(zone1, zone2), zone1, zone2)); dists.add(new DistElem<BoundingBox>(false, distance(zone1, zone2), zone1, zone2));
} }
} }
Collections.sort(dists); Collections.sort(dists);
DocumentPlane plane = new DocumentPlane(zones, GRIDSIZE); DocumentPlane plane = new DocumentPlane(zones, GRIDSIZE);
while (!dists.isEmpty()) { while (!dists.isEmpty()) {
DistElem<BBoxObject> distElem = dists.get(0); DistElem<BoundingBox> distElem = dists.get(0);
dists.remove(0); dists.remove(0);
if (!distElem.isC() && plane.anyObjectsBetween(distElem.getObj1(), distElem.getObj2())) { 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; continue;
} }
BBoxZoneGroup newGroup = new BBoxZoneGroup(distElem.getObj1(), distElem.getObj2()); BoundingBoxZoneGroup newGroup = new BoundingBoxZoneGroup(distElem.getObj1(), distElem.getObj2());
plane.remove(distElem.getObj1()).remove(distElem.getObj2()); plane.remove(distElem.getObj1()).remove(distElem.getObj2());
dists = removeDistElementsContainingObject(dists, distElem.getObj1()); dists = removeDistElementsContainingObject(dists, distElem.getObj1());
dists = removeDistElementsContainingObject(dists, distElem.getObj2()); dists = removeDistElementsContainingObject(dists, distElem.getObj2());
for (BBoxObject other : plane.getObjects()) { for (BoundingBox other : plane.getObjects()) {
dists.add(new DistElem<BBoxObject>(false, distance(other, newGroup), newGroup, other)); dists.add(new DistElem<BoundingBox>(false, distance(other, newGroup), newGroup, other));
} }
Collections.sort(dists); Collections.sort(dists);
plane.add(newGroup); plane.add(newGroup);
} }
assert plane.getObjects().size() == 1 : "There should be one object left at the plane after grouping"; 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 * 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>>(); List<DistElem<BoundingBox>> ret = new ArrayList<DistElem<BoundingBox>>();
for (DistElem<BBoxObject> distElem : list) { for (DistElem<BoundingBox> distElem : list) {
if (distElem.getObj1() != obj && distElem.getObj2() != obj) { if (distElem.getObj1() != obj && distElem.getObj2() != obj) {
ret.add(distElem); ret.add(distElem);
} }
@ -149,28 +149,28 @@ public class HierarchicalReadingOrderResolver {
* *
* @param group * @param group
*/ */
private void sortGroupedZones(BBoxZoneGroup group) { private void sortGroupedZones(BoundingBoxZoneGroup group) {
BBoxObject leftChild = group.getLeftChild(); BoundingBox leftChild = group.getLeftChild();
BBoxObject rightChild = group.getRightChild(); BoundingBox rightChild = group.getRightChild();
if (shouldBeSwapped(leftChild, rightChild)) { if (shouldBeSwapped(leftChild, rightChild)) {
// swap // swap
group.setLeftChild(rightChild); group.setLeftChild(rightChild);
group.setRightChild(leftChild); 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; double cx, cy, cw, ch, ox, oy, ow, oh;
cx = first.getBBox().getX(); cx = first.getBBox().getX();
@ -216,7 +216,7 @@ public class HierarchicalReadingOrderResolver {
* *
* @return distance value based on objects' coordinates and physical size on a plane * @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 x0 = Math.min(obj1.getX(), obj2.getX());
double y0 = Math.min(obj1.getY(), obj2.getY()); double y0 = Math.min(obj1.getY(), obj2.getY());

View File

@ -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 java.util.List;
import org.springframework.stereotype.Service; 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.Histogram;
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.docstrum.Character; import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.Neighbor;
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.docstrum.Neighbor;
@Service @Service
public class SpacingService { public class SpacingService {

View File

@ -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.ArrayList;
import java.util.List; import java.util.List;
@ -6,8 +6,8 @@ import java.util.List;
import org.springframework.stereotype.Service; 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.DisjointSets;
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.docstrum.CharacterLine; import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.Line;
import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.refactor.docstrum.CharacterZone; import com.knecon.fforesight.service.layoutparser.processor.services.docstrum.model.Zone;
@Service @Service
public class ZoneBuilderService { public class ZoneBuilderService {
@ -28,14 +28,14 @@ public class ZoneBuilderService {
public static final int MAX_ZONES = 300; 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 minHorizontalDistance = characterSpacing * MIN_HORIZONTAL_DISTANCE_MULTIPLIER;
double maxVerticalDistance = lineSpacing * MAX_VERTICAL_DISTANCE_MULTIPLIER; double maxVerticalDistance = lineSpacing * MAX_VERTICAL_DISTANCE_MULTIPLIER;
double minHorizontalMergeDistance = characterSpacing * MIN_HORIZONTAL_MERGE_DISTANCE_MULTIPLIER; double minHorizontalMergeDistance = characterSpacing * MIN_HORIZONTAL_MERGE_DISTANCE_MULTIPLIER;
double maxVerticalMergeDistance = lineSpacing * MAX_VERTICAL_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); double meanHeight = calculateMeanHeight(lines);
@ -61,28 +61,28 @@ public class ZoneBuilderService {
} }
})); }));
List<CharacterZone> zones = new ArrayList<>(); List<Zone> zones = new ArrayList<>();
sets.forEach(group -> { sets.forEach(group -> {
zones.add(new CharacterZone(new ArrayList<>(group))); zones.add(new Zone(new ArrayList<>(group)));
}); });
if (zones.size() > MAX_ZONES) { if (zones.size() > MAX_ZONES) {
List<CharacterLine> oneZoneLines = new ArrayList<>(); List<Line> oneZoneLines = new ArrayList<>();
for (CharacterZone zone : zones) { for (Zone zone : zones) {
oneZoneLines.addAll(zone.getLines()); oneZoneLines.addAll(zone.getLines());
} }
return List.of(new CharacterZone(oneZoneLines)); return List.of(new Zone(oneZoneLines));
} }
return zones; return zones;
} }
private double calculateMeanHeight(List<CharacterLine> lines) { private double calculateMeanHeight(List<Line> lines) {
double meanHeight = 0.0; double meanHeight = 0.0;
double weights = 0.0; double weights = 0.0;
for (CharacterLine line : lines) { for (Line line : lines) {
double weight = line.getLength(); double weight = line.getLength();
meanHeight += line.getHeight() * weight; meanHeight += line.getHeight() * weight;
weights += weight; weights += weight;

View File

@ -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 { public class DoubleUtils {