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.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);

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;
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

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 lombok.Data;
@Data
public abstract class BBoxObject {
public abstract class BoundingBox {
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.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.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;

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;

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.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());

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 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;

View File

@ -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>> {

View File

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

View File

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

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.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;
}

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.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 {

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.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());

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 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 {

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.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;

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 {