Skip to content

Commit 9faa51c

Browse files
authored
Merge pull request #8 from byWulf/feature/piece-recognizer
Add piece recognizer service
2 parents 10ed89e + 63fa9a1 commit 9faa51c

File tree

7 files changed

+124
-15
lines changed

7 files changed

+124
-15
lines changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,18 @@ Each `Placement` has the following information:
100100
* `$placement->getPiece()`: the piece described in this placement
101101
* `$placement->getX()` / `$placement->getY()`: the position of the piece in the pattern of the group. One unit means one piece row/column.
102102
* `$placement->getTopSideIndex()`: specifies the side array index of the piece, that points to the top of the group (so the piece must be rotated, that this side is at the top).
103+
104+
### PieceRecognizer
105+
If you want to find a newly scanned piece within a list of already scanned pieces, you can use the `PieceRecognizer` service.
106+
107+
```php
108+
use Bywulf\Jigsawlutioner\Service\PieceRecognizer;
109+
110+
// $existingPieces = [...];
111+
// $newlyScannedPiece = new Piece(...);
112+
113+
$recognizer = new PieceRecognizer();
114+
$piece = $recognizer->findExistingPiece($newlyScannedPiece, $existingPieces);
115+
```
116+
117+
The returned `$piece` is the cloned `$newlyScannedPiece` with the correctly set index and its sides reordered so the first side of the newly scanned pieces matches the first side of the existing piece.

src/Service/PieceRecognizer.php

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Bywulf\Jigsawlutioner\Service;
6+
7+
use Bywulf\Jigsawlutioner\Dto\Piece;
8+
use Bywulf\Jigsawlutioner\Dto\Side;
9+
use Bywulf\Jigsawlutioner\Exception\SideClassifierException;
10+
11+
class PieceRecognizer
12+
{
13+
/**
14+
* Returns a new piece cloned by the original piece with its sides rotated to match the sides of the existing piece.
15+
*
16+
* @param Piece[] $existingPieces
17+
*/
18+
public function findExistingPiece(Piece $piece, array $existingPieces): ?Piece
19+
{
20+
$bestExistingPiece = null;
21+
$bestProbability = 0;
22+
$bestSideOffset = 0;
23+
24+
foreach ($existingPieces as $existingPiece) {
25+
for ($sideOffset = 0; $sideOffset < 4; ++$sideOffset) {
26+
$probability = $this->getMatchingProbability($piece, $existingPiece, $sideOffset);
27+
28+
if ($probability > $bestProbability) {
29+
$bestProbability = $probability;
30+
$bestExistingPiece = $existingPiece;
31+
$bestSideOffset = $sideOffset;
32+
}
33+
}
34+
}
35+
36+
if ($bestExistingPiece === null) {
37+
return null;
38+
}
39+
40+
$sides = $piece->getSides();
41+
for ($i = 0; $i < $bestSideOffset; ++$i) {
42+
/** @var Side $side */
43+
$side = array_pop($sides);
44+
array_splice($sides, 0, 0, [$side]);
45+
}
46+
47+
return new Piece(
48+
$bestExistingPiece->getIndex(),
49+
$piece->getBorderPoints(),
50+
$sides,
51+
$piece->getImageWidth(),
52+
$piece->getImageHeight(),
53+
);
54+
}
55+
56+
private function getMatchingProbability(Piece $piece, Piece $comparePiece, int $sideOffset): float
57+
{
58+
$probabilitySum = 0;
59+
60+
for ($sideIndex = 0; $sideIndex < 4; ++$sideIndex) {
61+
$side = $piece->getSide($sideIndex);
62+
$existingSide = $comparePiece->getSide($sideIndex + $sideOffset);
63+
64+
if ($side->getDirection() !== $existingSide->getDirection()) {
65+
continue;
66+
}
67+
68+
$probabilities = [];
69+
foreach ($side->getClassifiers() as $classifier) {
70+
try {
71+
$existingClassifier = $existingSide->getClassifier($classifier::class);
72+
} catch (SideClassifierException) {
73+
continue 2;
74+
}
75+
76+
$probabilities[] = $classifier->compareSameSide($existingClassifier);
77+
}
78+
79+
$probabilitySum += array_sum($probabilities) / count($probabilities);
80+
}
81+
82+
return $probabilitySum;
83+
}
84+
}

src/SideClassifier/BigWidthClassifier.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,15 @@ public function getPredictionData(SideClassifierInterface $comparisonClassifier)
6868
*/
6969
public function compareSameSide(SideClassifierInterface $classifier): float
7070
{
71-
$insideClassifier = $this->direction === DirectionClassifier::NOP_INSIDE ? $this : $classifier;
72-
$outsideClassifier = $this->direction === DirectionClassifier::NOP_OUTSIDE ? $this : $classifier;
71+
$xDiff = abs($this->getCenterPoint()->getX() - $classifier->getCenterPoint()->getX()); // range 0 - 150
72+
$yDiff = abs($this->getCenterPoint()->getY() - $classifier->getCenterPoint()->getY()); // range 0 - 110
73+
$widthDiff = abs($this->getWidth() - $classifier->getWidth()); // range 0 - 100
7374

74-
$xDiff = $insideClassifier->getCenterPoint()->getX() - $outsideClassifier->getCenterPoint()->getX();
75-
$yDiff = $insideClassifier->getCenterPoint()->getY() - $outsideClassifier->getCenterPoint()->getY();
76-
$widthDiff = $insideClassifier->getWidth() - $outsideClassifier->getWidth();
75+
$xRating = $xDiff > 50 ? 0 : 1 - ($xDiff / 50);
76+
$yRating = $yDiff > 35 ? 0 : 1 - ($yDiff / 35);
77+
$widthRating = $widthDiff > 30 ? 0 : 1 - ($widthDiff / 30);
7778

78-
return 1 - ((min(1, abs($xDiff) / 10) + min(1, abs($yDiff) / 10) + min(1, abs($widthDiff) / 10)) / 3);
79+
return ($xRating + $yRating + $widthRating) / 3;
7980
}
8081

8182
public function getWidth(): float

src/SideClassifier/CornerDistanceClassifier.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public function getPredictionData(SideClassifierInterface $comparisonClassifier)
3636
*/
3737
public function compareSameSide(SideClassifierInterface $classifier): float
3838
{
39-
return max(0, 1 - (abs($this->width - $classifier->getWidth()) / 20));
39+
return max(0, 1 - (abs($this->width - $classifier->getWidth()) / 45)); // range 0 - 140
4040
}
4141

4242
public function getWidth(): float

src/SideClassifier/DepthClassifier.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public function getPredictionData(SideClassifierInterface $comparisonClassifier)
4545
*/
4646
public function compareSameSide(SideClassifierInterface $classifier): float
4747
{
48-
return max(0, 1 - (abs($this->depth - $classifier->getDepth()) / 12));
48+
return max(0, 1 - (abs($this->depth - $classifier->getDepth()) / 25)); // range 0 - 70
4949
}
5050

5151
public function getDepth(): float

src/SideClassifier/LineDistanceClassifier.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,15 @@ public function getPredictionData(SideClassifierInterface $comparisonClassifier)
140140
*/
141141
public function compareSameSide(SideClassifierInterface $classifier): float
142142
{
143-
return 0;
143+
$minDiff = abs($this->getMinLineDistance() - $classifier->getMinLineDistance()); // range 0 - 100
144+
$maxDiff = abs($this->getMaxLineDistance() - $classifier->getMaxLineDistance()); // range 0 - 30
145+
$averageDiff = abs($this->getAverageLineDistance() - $classifier->getAverageLineDistance()); // range 0 - 50
146+
147+
$minRating = $minDiff > 30 ? 0 : 1 - ($minDiff / 30);
148+
$maxRating = $maxDiff > 10 ? 0 : 1 - ($maxDiff / 10);
149+
$averageRating = $averageDiff > 15 ? 0 : 1 - ($averageDiff / 15);
150+
151+
return ($minRating + $maxRating + $averageRating) / 3;
144152
}
145153

146154
public function getDirection(): int

src/SideClassifier/SmallWidthClassifier.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,15 @@ public function getPredictionData(SideClassifierInterface $comparisonClassifier)
8080
*/
8181
public function compareSameSide(SideClassifierInterface $classifier): float
8282
{
83-
$insideClassifier = $this->direction === DirectionClassifier::NOP_INSIDE ? $this : $classifier;
84-
$outsideClassifier = $this->direction === DirectionClassifier::NOP_OUTSIDE ? $this : $classifier;
83+
$xDiff = abs($this->getCenterPoint()->getX() - $classifier->getCenterPoint()->getX()); // range 0 - 140
84+
$yDiff = abs($this->getCenterPoint()->getY() - $classifier->getCenterPoint()->getY()); // range 0 - 120
85+
$widthDiff = abs($this->getWidth() - $classifier->getWidth()); // range 0 - 140
8586

86-
$xDiff = $insideClassifier->getCenterPoint()->getX() - $outsideClassifier->getCenterPoint()->getX();
87-
$yDiff = $insideClassifier->getCenterPoint()->getY() - $outsideClassifier->getCenterPoint()->getY();
88-
$widthDiff = $insideClassifier->getWidth() - $outsideClassifier->getWidth();
87+
$xRating = $xDiff > 45 ? 0 : 1 - ($xDiff / 45);
88+
$yRating = $yDiff > 40 ? 0 : 1 - ($yDiff / 40);
89+
$widthRating = $widthDiff > 45 ? 0 : 1 - ($widthDiff / 45);
8990

90-
return 1 - ((min(1, abs($xDiff) / 10) + min(1, abs($yDiff) / 10) + min(1, abs($widthDiff) / 10)) / 3);
91+
return ($xRating + $yRating + $widthRating) / 3;
9192
}
9293

9394
public function getWidth(): float

0 commit comments

Comments
 (0)