-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
RomAlignerTilting implementing a rotation matrix. #120
- Loading branch information
1 parent
2dc78c6
commit e1c81fb
Showing
5 changed files
with
224 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
#include "romalignertilting.h" | ||
#include "maskromtool.h" | ||
|
||
|
||
/* All comparisons and sorts are performed on tilted bit positions. | ||
* This tilt is based upon the angle of the top row in the previous | ||
* alignment, and that angle is not saved between runs of the program. | ||
* | ||
* Aside from the QTransform mapping, this is the same as RomAlignerReliable. | ||
*/ | ||
static QTransform transform; | ||
static bool leftOf(RomBitItem * left, RomBitItem * right){ | ||
auto l=transform.map(left->pos()); | ||
auto r=transform.map(right->pos()); | ||
return l.x() < r.x(); | ||
} | ||
static bool above(RomBitItem * top, RomBitItem * bottom){ | ||
auto t=transform.map(top->pos()); | ||
auto b=transform.map(bottom->pos()); | ||
return t.y() < b.y(); | ||
} | ||
|
||
RomAlignerTilting::RomAlignerTilting() { | ||
name="RomAlignerTilting"; | ||
transform=QTransform(); | ||
} | ||
|
||
qreal RomAlignerTilting::angle(){ | ||
RomBitItem *first, *last; | ||
|
||
//Fail out with zero degrees. | ||
if(rowstarts.empty()) | ||
return 0.0; | ||
|
||
first=rowstarts[0]; | ||
last=first; | ||
while(last->nexttoright) | ||
last=last->nexttoright; | ||
|
||
QPointF left=first->pos(), right=last->pos(); | ||
QLineF line(left, right); | ||
|
||
lastangle=line.angle(); | ||
transform=QTransform().rotate(lastangle); | ||
angleset=true; | ||
|
||
/* How well did the transform work? | ||
auto l=transform.map(left), r=transform.map(right); | ||
auto newangle=QLineF(l,r).angle(); | ||
qDebug()<<"Post transform, angle is "<<newangle; //Very small, we hope. | ||
*/ | ||
|
||
return lastangle; | ||
} | ||
|
||
|
||
RomBitItem* RomAlignerTilting::markBitTable(MaskRomTool* mrt){ | ||
QList<RomBitItem *> bits=mrt->bits; | ||
|
||
//The very first time we align this, we continuously update the transform. | ||
bool firstrun=!angleset; | ||
|
||
//We recalculate the transformation whenever this grows by 16. | ||
int bitcount=0; | ||
|
||
//First we remove all the old bit marks and pointers. | ||
foreach (RomBitItem* bit, bits){ | ||
//Clear the markup details. | ||
bit->marked=false; //Not used by this aligner. | ||
bit->nextrow=0; | ||
bit->nexttoright=0; | ||
bit->lastinrow=0; //Speeds up linked list usage. | ||
bit->row=-1; | ||
bit->col=-1; | ||
} | ||
rowstarts.clear(); | ||
leftsorted.clear(); | ||
|
||
// We'll need presorted collections by X and Y. | ||
for(RomBitItem *bit: bits){ | ||
leftsorted<<bit; | ||
} | ||
//We read each row from the left. Presorting doesn't help determinism. | ||
std::sort(leftsorted.begin(), leftsorted.end(), leftOf); | ||
|
||
//Each bit is either its own row or the next in an existing row. | ||
for(RomBitItem *bit: leftsorted){ | ||
RomBitItem* nearest=nearestBit(bit); | ||
if(!nearest){ //First bit is its own row. | ||
rowstarts<<bit; | ||
}else{ | ||
auto nearestpos=transform.map(nearest->pos()); | ||
auto bitpos=transform.map(bit->pos()); | ||
qreal dx=qFabs(nearestpos.x()-bitpos.x()); | ||
qreal dy=qFabs(nearestpos.y()-bitpos.y()); | ||
if(dx<dy){ //New row because Y distance is greater. | ||
rowstarts<<bit; | ||
}else{ //Existing row because X distance is greater. | ||
nearest->nexttoright=bit; | ||
if(firstrun) angle(); | ||
} | ||
} | ||
} | ||
|
||
return linkresults(); | ||
} | ||
|
||
|
||
//Nearest bit from existing rows. | ||
RomBitItem* RomAlignerTilting::nearestBit(RomBitItem *item){ | ||
/* This returns the best match, but it does not guarantee that it | ||
* is a good match. If the best match is further in Y than in | ||
* X, you should start a new row. | ||
*/ | ||
|
||
RomBitItem* nearest=0; | ||
qreal nearestdy=1000.0; | ||
auto itempos=transform.map(item->pos()); | ||
|
||
for(RomBitItem* bit: rowstarts){ | ||
/* Rather than chase the entire linked list, we try to keep the frist | ||
* bit of each row pointing to either the end or some item near the end. | ||
* | ||
* We compare the mapped positions, not the raw positions. | ||
*/ | ||
RomBitItem* startbit=bit; | ||
if(bit->lastinrow) | ||
bit=bit->lastinrow; | ||
while(bit->nexttoright) bit=bit->nexttoright; | ||
startbit->lastinrow=bit; | ||
|
||
auto bitpos=transform.map(bit->pos()); | ||
qreal dy=qFabs(itempos.y()-bitpos.y()); | ||
if(dy<nearestdy){ | ||
nearestdy=dy; | ||
nearest=bit; | ||
} | ||
} | ||
|
||
return nearest; | ||
} | ||
|
||
|
||
//This updates the linked lists that MRT uses internally. | ||
RomBitItem* RomAlignerTilting::linkresults(){ | ||
//Having assembled rows, we now need to sort them and return the top left. | ||
std::sort(rowstarts.begin(), rowstarts.end(), above); | ||
|
||
//Apply the linked list. | ||
RomBitItem* lastbit=0; | ||
for(RomBitItem *bit: rowstarts){ | ||
if(lastbit) | ||
lastbit->nextrow=bit; | ||
lastbit=bit; | ||
} | ||
|
||
//Number the bits by row and column. | ||
int expectedcols=0; | ||
int row=0; | ||
for(RomBitItem* bit: rowstarts){ | ||
int col=0; | ||
while(bit){ | ||
bit->col=col; | ||
bit->row=row; | ||
bit=bit->nexttoright; | ||
col++; | ||
} | ||
row++; | ||
} | ||
|
||
//Do I want to correct by a tilt or by a shear? | ||
qDebug()<<"Tilt angle of the first row after alignment is "<<angle(); | ||
|
||
if(row) | ||
return rowstarts[0]; | ||
|
||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
#ifndef ROMALIGNERTILTING_H | ||
#define ROMALIGNERTILTING_H | ||
|
||
/* This is a fork of RomAlignerReliable that also measures | ||
* the tilt of the lines and corrects for that tilt to better | ||
* support images with a gap in the middle. It works by calculating | ||
* a "tilt" of the image on each pass, then using the Reliable algorithm | ||
* on the tilted bit positions. | ||
* | ||
* It isn't well tested yet, so if you do have need for it, consider | ||
* pre-rotating your image. Gaps inside rows (between columns) | ||
* will trigger the problem with RomAlignerReliable, but | ||
* gaps inside columns (between rows) will not. | ||
* | ||
* https://github.com/travisgoodspeed/maskromtool/issues/120 | ||
*/ | ||
|
||
#include "romaligner.h" | ||
|
||
#include<QTransform> | ||
|
||
class RomAlignerTilting : public RomAligner | ||
{ | ||
public: | ||
RomAlignerTilting(); | ||
RomBitItem* markBitTable(MaskRomTool* mrt); | ||
private: | ||
QList<RomBitItem *> rowstarts; //All left-most bits of a row. | ||
QList<RomBitItem *> leftsorted; //All bits sorted from left. | ||
|
||
//Nearest bit from existing rows. | ||
RomBitItem* nearestBit(RomBitItem *item); | ||
RomBitItem* linkresults(); | ||
//QTransform transform; | ||
qreal angle(); | ||
qreal lastangle=0; | ||
bool angleset=false; | ||
}; | ||
|
||
#endif // ROMALIGNERTILTING_H |