use of com.joliciel.talismane.machineLearning.Decision in project jochre by urieli.
the class RecursiveShapeSplitterTest method testSplitShapeNoSplitMoreLikely.
@SuppressWarnings("unchecked")
@Test
public void testSplitShapeNoSplitMoreLikely() throws Exception {
System.setProperty("config.file", "src/test/resources/test.conf");
ConfigFactory.invalidateCaches();
Config config = ConfigFactory.load();
JochreSession jochreSession = new JochreSession(config);
BufferedImage originalImage = new BufferedImage(256, 256, BufferedImage.TYPE_INT_RGB);
final JochreImage jochreImage = new JochreImage(originalImage, jochreSession);
final Shape shape = new Shape(jochreImage, 0, 0, 63, 15, jochreSession);
shape.setBaseLine(12);
shape.setMeanLine(4);
final Shape shape1 = new Shape(jochreImage, 0, 0, 31, 15, jochreSession);
shape1.setBaseLine(12);
shape1.setMeanLine(4);
final Shape shape2 = new Shape(jochreImage, 32, 0, 63, 15, jochreSession);
shape2.setBaseLine(12);
shape2.setMeanLine(4);
final SplitCandidateFinder splitCandidateFinder = mock(SplitCandidateFinder.class);
final DecisionMaker decisionMaker = mock(DecisionMaker.class);
Split split = new Split(shape, jochreSession);
split.setPosition(31);
List<Split> splits = new ArrayList<>();
splits.add(split);
when(splitCandidateFinder.findSplitCandidates(shape)).thenReturn(splits);
Decision yesDecision = new Decision(SplitOutcome.DO_SPLIT.name(), 0.4);
Decision noDecision = new Decision(SplitOutcome.DO_NOT_SPLIT.name(), 0.6);
List<Decision> decisions = new ArrayList<>();
decisions.add(yesDecision);
decisions.add(noDecision);
when(decisionMaker.decide(anyList())).thenReturn(decisions);
Split split1 = new Split(shape1, jochreSession);
split1.setPosition(15);
List<Split> splits1 = new ArrayList<>();
splits1.add(split1);
when(splitCandidateFinder.findSplitCandidates(shape1)).thenReturn(splits1);
Split split2 = new Split(shape2, jochreSession);
split2.setPosition(15);
List<Split> splits2 = new ArrayList<>();
splits2.add(split2);
when(splitCandidateFinder.findSplitCandidates(shape2)).thenReturn(splits2);
Set<SplitFeature<?>> splitFeatures = new TreeSet<>();
RecursiveShapeSplitter splitter = new RecursiveShapeSplitter(splitCandidateFinder, splitFeatures, decisionMaker, jochreSession);
splitter.setBeamWidth(10);
splitter.setMaxDepth(2);
splitter.setMinWidthRatio(1.0);
List<ShapeSequence> shapeSequences = splitter.split(shape);
assertEquals(5, shapeSequences.size());
int i = 0;
double prob = 1.0;
double twoThirds = 0.4 / 0.6;
LOG.debug("twoThirds: " + twoThirds);
for (ShapeSequence shapeSequence : shapeSequences) {
LOG.debug("sequence " + i + " decisions:");
for (Decision decision : shapeSequence.getDecisions()) LOG.debug("" + decision.getProbability());
if (i == 0) {
prob = 1.0;
assertEquals(prob, shapeSequence.getScore(), 0.0001);
assertEquals(1, shapeSequence.size());
} else if (i == 1) {
prob = 1.0 * twoThirds;
assertEquals(prob, shapeSequence.getScore(), 0.0001);
assertEquals(2, shapeSequence.size());
} else if (i == 2) {
prob = 1.0 * twoThirds * twoThirds;
assertEquals(prob, shapeSequence.getScore(), 0.0001);
assertEquals(3, shapeSequence.size());
} else if (i == 3) {
prob = 1.0 * twoThirds * twoThirds;
assertEquals(prob, shapeSequence.getScore(), 0.0001);
assertEquals(3, shapeSequence.size());
} else if (i == 4) {
prob = 1.0 * twoThirds * twoThirds * twoThirds;
assertEquals(prob, shapeSequence.getScore(), 0.0001);
assertEquals(4, shapeSequence.size());
}
i++;
}
}
use of com.joliciel.talismane.machineLearning.Decision in project jochre by urieli.
the class RecursiveShapeSplitterTest method testSplitShapeSplitMoreLikely.
/**
* If a split is always more likely (e.g. 60% likelihood), ensure the shape
* sequences are ordered correctly.
*/
@SuppressWarnings("unchecked")
@Test
public void testSplitShapeSplitMoreLikely() throws Exception {
System.setProperty("config.file", "src/test/resources/test.conf");
ConfigFactory.invalidateCaches();
Config config = ConfigFactory.load();
JochreSession jochreSession = new JochreSession(config);
BufferedImage originalImage = new BufferedImage(256, 256, BufferedImage.TYPE_INT_RGB);
final JochreImage jochreImage = new JochreImage(originalImage, jochreSession);
final Shape shape = new Shape(jochreImage, 0, 0, 63, 15, jochreSession);
shape.setBaseLine(12);
shape.setMeanLine(4);
final Shape shape1 = new Shape(jochreImage, 0, 0, 31, 15, jochreSession);
shape1.setBaseLine(12);
shape1.setMeanLine(4);
final Shape shape2 = new Shape(jochreImage, 32, 0, 63, 15, jochreSession);
shape2.setBaseLine(12);
shape2.setMeanLine(4);
final SplitCandidateFinder splitCandidateFinder = mock(SplitCandidateFinder.class);
final DecisionMaker decisionMaker = mock(DecisionMaker.class);
Split split = new Split(shape, jochreSession);
split.setPosition(31);
List<Split> splits = new ArrayList<>();
splits.add(split);
when(splitCandidateFinder.findSplitCandidates(shape)).thenReturn(splits);
Decision yesDecision = new Decision(SplitOutcome.DO_SPLIT.name(), 0.6);
Decision noDecision = new Decision(SplitOutcome.DO_NOT_SPLIT.name(), 0.4);
List<Decision> decisions = new ArrayList<>();
decisions.add(yesDecision);
decisions.add(noDecision);
when(decisionMaker.decide(anyList())).thenReturn(decisions);
Split split1 = new Split(shape1, jochreSession);
split1.setPosition(15);
List<Split> splits1 = new ArrayList<>();
splits1.add(split1);
when(splitCandidateFinder.findSplitCandidates(shape1)).thenReturn(splits1);
Split split2 = new Split(shape2, jochreSession);
split2.setPosition(15);
List<Split> splits2 = new ArrayList<>();
splits2.add(split2);
when(splitCandidateFinder.findSplitCandidates(shape2)).thenReturn(splits2);
Set<SplitFeature<?>> splitFeatures = new TreeSet<>();
RecursiveShapeSplitter splitter = new RecursiveShapeSplitter(splitCandidateFinder, splitFeatures, decisionMaker, jochreSession);
splitter.setBeamWidth(10);
splitter.setMaxDepth(2);
splitter.setMinWidthRatio(1.0);
List<ShapeSequence> shapeSequences = splitter.split(shape);
assertEquals(5, shapeSequences.size());
int i = 0;
for (ShapeSequence shapeSequence : shapeSequences) {
LOG.debug("sequence " + i + " shapes:");
for (ShapeInSequence shapeInSequence : shapeSequence) {
Shape oneShape = shapeInSequence.getShape();
LOG.debug("Shape: " + oneShape.getLeft() + "," + oneShape.getRight());
}
LOG.debug("" + shapeSequence.getScore());
i++;
}
i = 0;
double prob = 1.0;
double twoThirds = 0.4 / 0.6;
LOG.debug("twoThirds: " + twoThirds);
for (ShapeSequence shapeSequence : shapeSequences) {
LOG.debug("sequence " + i + " decisions:");
for (Decision decision : shapeSequence.getDecisions()) LOG.debug("" + decision.getProbability());
if (i == 0) {
prob = 1.0;
assertEquals(prob, shapeSequence.getScore(), 0.0001);
assertEquals(4, shapeSequence.size());
} else if (i == 1) {
prob = 1.0 * twoThirds;
assertEquals(prob, shapeSequence.getScore(), 0.0001);
assertEquals(3, shapeSequence.size());
} else if (i == 2) {
prob = 1.0 * twoThirds;
assertEquals(prob, shapeSequence.getScore(), 0.0001);
assertEquals(3, shapeSequence.size());
} else if (i == 3) {
prob = 1.0 * twoThirds * twoThirds;
assertEquals(prob, shapeSequence.getScore(), 0.0001);
assertEquals(2, shapeSequence.size());
} else if (i == 4) {
prob = 1.0 * twoThirds * twoThirds * twoThirds;
assertEquals(prob, shapeSequence.getScore(), 0.0001);
assertEquals(1, shapeSequence.size());
}
i++;
}
}
use of com.joliciel.talismane.machineLearning.Decision in project jochre by urieli.
the class LetterGuesser method guessLetter.
/**
* Analyses this shape, using the context provided for features that are not
* intrinsic. Updates shape.getWeightedOutcomes to include all outcomes
* above a certain threshold of probability.
*
* @return the best outcome for this shape.
*/
public String guessLetter(ShapeInSequence shapeInSequence, LetterSequence history) {
Shape shape = shapeInSequence.getShape();
if (LOG.isTraceEnabled())
LOG.trace("guessLetter, shape: " + shape);
List<FeatureResult<?>> featureResults = new ArrayList<FeatureResult<?>>();
// analyse features
for (LetterFeature<?> feature : features) {
LetterGuesserContext context = new LetterGuesserContext(shapeInSequence, history);
RuntimeEnvironment env = new RuntimeEnvironment();
FeatureResult<?> featureResult = feature.check(context, env);
if (featureResult != null) {
featureResults.add(featureResult);
if (LOG.isTraceEnabled()) {
LOG.trace(featureResult.toString());
}
}
}
List<Decision> letterGuesses = decisionMaker.decide(featureResults);
// store outcomes
String bestOutcome = null;
shape.getLetterGuesses().clear();
for (Decision letterGuess : letterGuesses) {
if (letterGuess.getProbability() >= MIN_PROB_TO_STORE) {
shape.getLetterGuesses().add(letterGuess);
}
}
bestOutcome = shape.getLetterGuesses().iterator().next().getOutcome();
if (LOG.isTraceEnabled()) {
LOG.trace("Shape: " + shape);
LOG.trace("Letter: " + shape.getLetter());
LOG.trace("Best outcome: " + bestOutcome);
}
return bestOutcome;
}
use of com.joliciel.talismane.machineLearning.Decision in project jochre by urieli.
the class DeterministicBoundaryDetector method findBoundaries.
@Override
public List<ShapeSequence> findBoundaries(GroupOfShapes group) {
// find the possible shape sequences that make up this group
ShapeSequence bestSequence = new ShapeSequence();
for (Shape shape : group.getShapes()) {
// check if shape is wide enough to bother with
double widthRatio = (double) shape.getWidth() / (double) shape.getXHeight();
double heightRatio = (double) shape.getHeight() / (double) shape.getXHeight();
// Splitting/merging shapes as required
ShapeSequence bestSplitSequence = null;
if (this.shapeSplitter != null && widthRatio >= minWidthRatioForSplit && heightRatio >= minHeightRatioForSplit) {
List<ShapeSequence> splitSequences = shapeSplitter.split(shape);
double bestProb = 0;
for (ShapeSequence splitSequence : splitSequences) {
if (splitSequence.getScore() > bestProb) {
bestSplitSequence = splitSequence;
bestProb = splitSequence.getScore();
}
}
if (bestProb < minProbabilityForDecision) {
// create a sequence containing only this shape
ShapeSequence singleShapeSequence = new ShapeSequence();
singleShapeSequence.addShape(shape);
bestSplitSequence = singleShapeSequence;
}
} else {
// create a sequence containing only this shape
ShapeSequence singleShapeSequence = new ShapeSequence();
singleShapeSequence.addShape(shape);
bestSplitSequence = singleShapeSequence;
}
ShapeInSequence previousShapeInSequence = null;
Shape previousShape = null;
if (bestSequence.size() > 0) {
previousShapeInSequence = bestSequence.get(bestSequence.size() - 1);
previousShape = previousShapeInSequence.getShape();
}
ShapeInSequence firstShapeInSequence = bestSplitSequence.get(0);
Shape firstShape = firstShapeInSequence.getShape();
double mergeProb = 0;
if (this.shapeMerger != null && previousShape != null) {
ShapePair mergeCandidate = new ShapePair(previousShape, shape);
double mergeCandidateWidthRatio = 0;
double mergeCandidateDistanceRatio = 0;
mergeCandidateWidthRatio = (double) mergeCandidate.getWidth() / (double) mergeCandidate.getXHeight();
mergeCandidateDistanceRatio = (double) mergeCandidate.getInnerDistance() / (double) mergeCandidate.getXHeight();
if (mergeCandidateWidthRatio <= maxWidthRatioForMerge && mergeCandidateDistanceRatio <= maxDistanceRatioForMerge) {
mergeProb = shapeMerger.checkMerge(previousShape, firstShape);
}
}
if (mergeProb > minProbabilityForDecision) {
Shape mergedShape = shapeMerger.merge(previousShape, firstShape);
bestSequence.remove(bestSequence.size() - 1);
List<Shape> originalShapesForMerge = new ArrayList<Shape>();
originalShapesForMerge.addAll(previousShapeInSequence.getOriginalShapes());
originalShapesForMerge.addAll(firstShapeInSequence.getOriginalShapes());
bestSequence.addShape(mergedShape, originalShapesForMerge);
boolean isFirstShape = true;
for (ShapeInSequence splitShape : bestSplitSequence) {
if (!isFirstShape)
bestSequence.add(splitShape);
isFirstShape = false;
}
Decision mergeDecision = new Decision(MergeOutcome.DO_MERGE.name(), mergeProb);
bestSequence.addDecision(mergeDecision);
for (Decision splitDecision : bestSplitSequence.getDecisions()) bestSequence.addDecision(splitDecision);
} else {
if (mergeProb > 0) {
Decision mergeDecision = new Decision(MergeOutcome.DO_NOT_MERGE.name(), 1 - mergeProb);
bestSequence.addDecision(mergeDecision);
}
for (Decision splitDecision : bestSplitSequence.getDecisions()) bestSequence.addDecision(splitDecision);
for (ShapeInSequence splitShape : bestSplitSequence) {
bestSequence.add(splitShape);
}
}
}
// next shape in group
List<ShapeSequence> result = new ArrayList<ShapeSequence>();
result.add(bestSequence);
return result;
}
use of com.joliciel.talismane.machineLearning.Decision in project jochre by urieli.
the class RecursiveShapeSplitter method split.
List<ShapeSequence> split(Shape shape, int depth, Shape originalShape, boolean leftToRight) {
String padding = "-";
for (int i = 0; i < depth; i++) padding += "-";
padding += " ";
if (LOG.isTraceEnabled()) {
LOG.trace(padding + "Splitting shape: " + shape.getLeft() + " , " + shape.getRight());
LOG.trace(padding + "depth: " + depth);
}
List<ShapeSequence> shapeSequences = new ArrayList<ShapeSequence>();
// check if shape is wide enough to bother with
double widthRatio = (double) shape.getWidth() / (double) shape.getXHeight();
if (LOG.isTraceEnabled())
LOG.trace(padding + "widthRatio: " + widthRatio);
if (widthRatio < minWidthRatio || depth >= maxDepth) {
if (LOG.isTraceEnabled())
LOG.trace(padding + "too narrow or too deep");
ShapeSequence shapeSequence = new ShapeSequence();
shapeSequence.addShape(shape, originalShape);
shapeSequences.add(shapeSequence);
} else {
List<Split> splitCandidates = this.splitCandidateFinder.findSplitCandidates(shape);
TreeSet<ShapeSequence> myShapeSequences = new TreeSet<ShapeSequence>();
TreeSet<WeightedOutcome<Split>> weightedSplits = new TreeSet<WeightedOutcome<Split>>();
for (Split splitCandidate : splitCandidates) {
double splitProb = this.shouldSplit(splitCandidate);
WeightedOutcome<Split> weightedSplit = new WeightedOutcome<Split>(splitCandidate, splitProb);
weightedSplits.add(weightedSplit);
}
double maxSplitProb = 0.0;
if (weightedSplits.size() > 0)
maxSplitProb = weightedSplits.first().getWeight();
double noSplitProb = 1 - maxSplitProb;
if (noSplitProb > maxSplitProb)
maxSplitProb = noSplitProb;
Split noSplit = new Split(shape, jochreSession);
noSplit.setPosition(-1);
WeightedOutcome<Split> weightedNoSplit = new WeightedOutcome<Split>(noSplit, noSplitProb);
weightedSplits.add(weightedNoSplit);
boolean topCandidate = true;
double topCandidateWeight = 1.0;
for (WeightedOutcome<Split> weightedSplit : weightedSplits) {
Split splitCandidate = weightedSplit.getOutcome();
double splitProb = weightedSplit.getWeight();
if (LOG.isTraceEnabled())
LOG.trace(padding + "splitCandidate: left=" + splitCandidate.getShape().getLeft() + ", pos=" + splitCandidate.getPosition() + ", initial prob: " + splitProb);
if (LOG.isTraceEnabled()) {
if (topCandidate) {
LOG.trace(padding + "topCandidate");
}
}
if (splitCandidate.getPosition() < 0) {
// This is the no-split candidate
if (topCandidate)
topCandidateWeight = 1.0;
ShapeSequence shapeSequence = new ShapeSequence();
shapeSequence.addShape(shape, originalShape);
double prob = (splitProb / maxSplitProb) * topCandidateWeight;
if (LOG.isTraceEnabled())
LOG.trace(padding + "noSplit prob=(" + splitProb + " / " + maxSplitProb + ") * " + topCandidateWeight + " = " + prob);
Decision decision = new Decision(SplitOutcome.DO_NOT_SPLIT.name(), prob);
shapeSequence.addDecision(decision);
myShapeSequences.add(shapeSequence);
} else {
// a proper split
Shape leftShape = shape.getJochreImage().getShape(shape.getLeft(), shape.getTop(), shape.getLeft() + splitCandidate.getPosition(), shape.getBottom());
Shape rightShape = shape.getJochreImage().getShape(shape.getLeft() + splitCandidate.getPosition() + 1, shape.getTop(), shape.getRight(), shape.getBottom());
// for each split recursively try to split it again up to depth of m
// Note: m=2 is probably enough, since we're not expecting more than 4
// letters per shape (3 splits)
List<ShapeSequence> leftShapeSequences = this.split(leftShape, depth + 1, originalShape, leftToRight);
List<ShapeSequence> rightShapeSequences = this.split(rightShape, depth + 1, originalShape, leftToRight);
if (topCandidate) {
// find the no-split sequence in each sub-sequence
ShapeSequence noSplitLeft = null;
for (ShapeSequence leftShapeSequence : leftShapeSequences) {
if (leftShapeSequence.size() == 1) {
noSplitLeft = leftShapeSequence;
break;
}
}
ShapeSequence noSplitRight = null;
for (ShapeSequence rightShapeSequence : rightShapeSequences) {
if (rightShapeSequence.size() == 1) {
noSplitRight = rightShapeSequence;
break;
}
}
// we should be guaranteed to find a noSplitLeft and noSplitRight
// since a no-split candidate is always returned
topCandidateWeight = noSplitLeft.getScore() * noSplitRight.getScore();
if (LOG.isTraceEnabled())
LOG.trace(padding + "topCandidateWeight=" + noSplitLeft.getScore() + " *" + noSplitRight.getScore() + " = " + topCandidateWeight);
}
for (ShapeSequence leftShapeSequence : leftShapeSequences) {
for (ShapeSequence rightShapeSequence : rightShapeSequences) {
ShapeSequence newSequence = null;
if (leftToRight)
newSequence = new ShapeSequence(leftShapeSequence, rightShapeSequence);
else
newSequence = new ShapeSequence(rightShapeSequence, leftShapeSequence);
if (LOG.isTraceEnabled()) {
StringBuilder sb = new StringBuilder();
for (ShapeInSequence splitShape : newSequence) {
sb.append("(" + splitShape.getShape().getLeft() + "," + splitShape.getShape().getRight() + ") ");
}
LOG.trace(padding + sb.toString());
}
double totalProb = 1.0;
for (Decision decision : newSequence.getDecisions()) {
totalProb = totalProb * decision.getProbability();
}
newSequence.getDecisions().clear();
double prob = 0.0;
if (topCandidate) {
prob = totalProb * (splitProb / maxSplitProb);
if (LOG.isTraceEnabled())
LOG.trace(padding + "prob=" + totalProb + " * (" + splitProb + " / " + maxSplitProb + ") = " + prob);
} else {
prob = totalProb * (splitProb / maxSplitProb) * topCandidateWeight;
if (LOG.isTraceEnabled())
LOG.trace(padding + "prob=" + totalProb + " * (" + splitProb + " / " + maxSplitProb + ") * " + topCandidateWeight + " = " + prob);
}
Decision decision = new Decision(SplitOutcome.DO_SPLIT.name(), prob);
newSequence.addDecision(decision);
myShapeSequences.add(newSequence);
}
}
}
topCandidate = false;
}
int i = 0;
for (ShapeSequence shapeSequence : myShapeSequences) {
// probability
if (shapeSequence.size() == 1 || i < beamWidth) {
shapeSequences.add(shapeSequence);
}
i++;
}
}
return shapeSequences;
}
Aggregations