MuseScore/omr/omrpage.cpp
2018-12-18 14:55:54 +01:00

1720 lines
55 KiB
C++

//=============================================================================
// MusE Reader
// Music Score Reader
//
// Copyright (C) 2010-2011 Werner Schweer
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#include "omrpage.h"
#include "image.h"
#include "utils.h"
#include "omr.h"
#ifdef OCR
#include "ocr.h"
#endif
#include "libmscore/score.h"
#include "libmscore/text.h"
#include "libmscore/measurebase.h"
#include "libmscore/box.h"
#include "libmscore/sym.h"
#include "libmscore/note.h"
#include "pattern.h"
namespace Ms {
//static const double noteTH = 1.0;
static const double timesigTH = 0.7;
//static const double clefTH = 0.7;
static const double keysigTH = 0.8;
struct Hv {
int x;
int val;
Hv(int a, int b) : x(a), val(b) {}
bool operator< (const Hv& a) const { return a.val < val; }
};
struct Peak {
int x;
double val;
int sym;
bool operator<(const Peak& p) const { return p.val < val; }
Peak(int _x, double _val) : x(_x), val(_val) {}
Peak(int _x, double _val, int s) : x(_x), val(_val), sym(s) {}
};
//---------------------------------------------------------
// Lv
// line + value
//---------------------------------------------------------
struct Lv {
int line;
double val;
Lv(int a, double b) : line(a), val(b) {}
bool operator< (const Lv& a) const { return a.val < val; }
};
//---------------------------------------------------------
// OmrPage
//---------------------------------------------------------
OmrPage::OmrPage(Omr* parent)
{
_omr = parent;
cropL = cropR = cropT = cropB = 0;
}
//---------------------------------------------------------
// dot
//---------------------------------------------------------
bool OmrPage::dot(int x, int y) const
{
const uint* p = scanLine(y) + (x / 32);
return (*p) & (0x1 << (x % 32));
}
//---------------------------------------------------------
// isBlack
//---------------------------------------------------------
bool OmrPage::isBlack(int x, int y) const
{
QRgb c = _image.pixel(x,y);
return (qGray(c) < 100);
}
//---------------------------------------------------------
// read
//---------------------------------------------------------
void OmrPage::read()
{
//removeBorder();
crop();
slice();
deSkew();
crop();
slice();
getStaffLines();
getRatio();
}
struct SysState {
int status;// 0 for start_staff, 1 for end_staff
int index;//staff index
};
struct BAR_STATE {
int x;
int status;//0 represents white space, 1 represents bar
};
//---------------------------------------------------------
// searchBarLines
//---------------------------------------------------------
float OmrPage::searchBarLines(int start_staff, int end_staff)
{
OmrStaff& r1 = staves[start_staff];
OmrStaff& r2 = staves[end_staff];
int x1 = r1.x();
int x2 = x1 + r1.width();
int y1 = r1.y();
int y2 = r2.y() + r2.height();
int th = 0;
for (int i = start_staff; i <= end_staff; ++i) {
th += staves[i].height() / 2;
}
int vpw = x2 - x1;
#if (!defined (_MSCVER) && !defined (_MSC_VER))
float vp[vpw];
memset(vp, 0, sizeof(float) * vpw);
//using note constraints
//searchNotes();
int note_constraints[x2 - x1];
#else
// MSVC does not support VLA. Replace with std::vector. If profiling determines that the
// heap allocation is slow, an optimization might be used.
std::vector<float> vp(vpw); // Default-initialized, doesn't need to be cleared
std::vector<int> note_constraints(x2 - x1);
#endif
for (OmrNote* n : r1.notes()) {
for(int x = n->x(); x <= n->x() + n->width(); ++x)
note_constraints[x - x1] = 1;
}
for (OmrNote* n : r2.notes()) {
for(int x = n->x(); x <= n->x() + n->width(); ++x)
note_constraints[x - x1] = 1;
}
//
// compute vertical projections
//
for (int x = x1; x < x2; ++x) {
int dots = 0;
for (int y = y1; y < y2; ++y) {
if (this->dot(x, y))
++dots;
}
if (!note_constraints[x - x1])
vp[x - x1] = dots - th;
else
vp[x - x1] = -HUGE_VAL;
}
#if (!defined (_MSCVER) && !defined (_MSC_VER))
float scores[x2 - x1 + 1][2];
BAR_STATE pred[x2 - x1 + 1][2];
#else
// MSVC does not support VLA. Replace with std::vector. If profiling determines that the
// heap allocation is slow, an optimization might be used.
std::vector<float[2]> scores(x2 - x1 + 1);
std::vector<BAR_STATE[2]> pred(x2 - x1 + 1);
#endif
BAR_STATE bs;
//initialization
bs.x = -1; bs.status = -1;
for (int x = x1; x <= x2; ++x) {
int i = x - x1;
for (int status = 0; status <= 1; ++status) {
scores[i][status] = -HUGE_VAL;
pred[i][status] = bs;
}
}
scores[0][0] = 0;
//forward pass
for (int x = x1; x <= x2; ++x) {
int i = x - x1;
for (int cur = 0; cur <= 1; ++cur) {
//current state
if (scores[i][cur] < -1000)
continue;
if (cur) {
scores[i][cur] += vp[i];
int next = 0;
int step = 3 * _spatium;//constraints between adjacent barlines
if (step + i <= x2 - x1) {
if (scores[step + i][next] < scores[i][cur]) {
scores[step + i][next] = scores[i][cur];
bs.x = x; bs.status = cur;
pred[step + i][next] = bs;
}
}
}
else {
for (int next = 0; next <= 1; ++next) {
int step = 1;
if (step + i <= x2 - x1) {
if (scores[step + i][next] < scores[i][cur]) {
scores[step + i][next] = scores[i][cur];
bs.x = x; bs.status = cur;
pred[step + i][next] = bs;
}
}
}
}
}
}
return(scores[x2][0]);
}
//---------------------------------------------------------
// identifySystems
//---------------------------------------------------------
void OmrPage::identifySystems()
{
int numStaves = staves.size();
if(numStaves == 0) return;
//
//memory allocation
//
float **temp_scores = new float*[numStaves];
for (int i = 0; i < numStaves; i++)
temp_scores[i] = new float[numStaves];
int **hashed = new int*[numStaves];
for (int i = 0; i < numStaves; i++)
hashed[i] = new int[numStaves];
float **scores = new float*[numStaves];
for (int i = 0; i < numStaves; i++)
scores[i] = new float[2];
SysState **pred = new SysState*[numStaves];
for (int i = 0; i < numStaves; i++)
pred[i] = new SysState[2];
//
//initialization
//
for (int i = 0; i < numStaves; i++) {
for (int j = 0; j < numStaves; j++) {
hashed[i][j] = 0;
}
}
SysState ss;
ss.index = -1; ss.status = -1;
for (int i = 0; i < numStaves; ++i) {
for (int j = 0; j < 2; j++) {
scores[i][j] = -HUGE_VAL;
pred[i][j] = ss;
}
}
scores[0][0] = 0;
//
//identify solid note heads
//
int **note_labels = new int*[numStaves];
for (int i = 0; i < numStaves; i++)
note_labels[i] = new int[width()];
for (int i = 0; i < numStaves; i++){
for (int j = 0; j < width(); j++){
note_labels[i][j] = 0;
}
}
//
// search notes for each system
//
// int note_ran = spatium()/4.0;
// for (int i = 0; i < numStaves; i++){
// OmrSystem omrSystem(this);
// omrSystem.staves().append(staves[i]);
// omrSystem.searchNotes(note_labels[i], note_ran);
// }
//
// System Identification
//
int status;
float cur_score;
int cur_staff,next_staff;
for (cur_staff = 0; cur_staff < numStaves; ++cur_staff) {
status = 0;//start_staff
for (next_staff = cur_staff; next_staff < numStaves; ++next_staff) { //connects to end_staff
if (hashed[cur_staff][next_staff]) {
cur_score = temp_scores[cur_staff][next_staff];
}
else {
//evaluate staff segment [c,n], dp here
OmrSystem omrSystem(this);
for (int i = cur_staff; i <= next_staff; ++i) {
omrSystem.staves().append(staves[i]);
}
cur_score = omrSystem.searchBarLinesvar(next_staff - cur_staff + 1, note_labels + cur_staff);
temp_scores[cur_staff][next_staff] = cur_score;
hashed[cur_staff][next_staff] = 1;
}
//forward pass
if (scores[cur_staff][status] + cur_score > scores[next_staff][1 - status]) {
scores[next_staff][1 - status] = scores[cur_staff][status] + cur_score;
ss.status = status; ss.index = cur_staff;
pred[next_staff][1 - status] = ss;
}
}
status = 1;//end_staff
next_staff = cur_staff + 1;
if (next_staff < numStaves) {
//forward pass
if (scores[cur_staff][status] > scores[next_staff][1 - status]) {
scores[next_staff][1 - status] = scores[cur_staff][status];
ss.status = status; ss.index = cur_staff;
pred[next_staff][1 - status] = ss;
}
}
}
//backtrack
status = 1;//end_staff
cur_staff = numStaves - 1;//last staff index
while (cur_staff > 0 || status != 0) {
ss = pred[cur_staff][status];
if (!ss.status) {
//add system here
OmrSystem omrSystem(this);
for (int i = ss.index; i <= cur_staff; ++i) {
omrSystem.staves().append(staves[i]);
}
omrSystem.searchBarLinesvar(cur_staff - ss.index + 1, note_labels + ss.index);
_systems.append(omrSystem);
}
cur_staff = ss.index;
status = ss.status;
}
int systems = _systems.size();
for (int i = 0; i < systems; ++i) {
OmrSystem* system = &_systems[i];
int n = system->barLines.size();
for (int k = 0; k < n - 1; ++k) {
const QLine& l1 = system->barLines[k];
const QLine& l2 = system->barLines[k + 1];
OmrMeasure m(l1.x1(), l2.x1());
system->measures().append(m);
}
}
//delete allocated space
for (int i = 0; i < numStaves; i++) {
delete[] scores[i];
delete[] pred[i];
delete[] temp_scores[i];
delete[] hashed[i];
delete[] note_labels[i];
}
delete[] note_labels;
delete[] scores;
delete[] pred;
delete[] temp_scores;
delete[] hashed;
}
//---------------------------------------------------------
// readBarLines
//---------------------------------------------------------
void OmrPage::readBarLines()
{
//QFuture<void> bl = QtConcurrent::run(_systems, &OmrSystem::searchSysBarLines());
//bl.waitForFinished();
//int numStaves = staves.size();
int systems = _systems.size();
for (int i = 0; i < systems; ++i) {
OmrSystem* system = &_systems[i];
int n = system->barLines.size();
for (int k = 0; k < n - 1; ++k) {
const QLine& l1 = system->barLines[k];
const QLine& l2 = system->barLines[k + 1];
OmrMeasure m(l1.x1(), l2.x1());
for (int ss = 0; ss < system->staves().size(); ++ss) {
OmrStaff& staff = system->staves()[ss];
QList<OmrChord> chords;
int nx = 0;
SymId nsym = SymId::noSym;
OmrChord chord;
for(OmrNote* n1 : staff.notes()) {
int x = n1->x();
if (x >= m.x2())
break;
if (x >= m.x1() && x < m.x2()) {
if (qAbs(x - nx) > int(_spatium / 2) || (nsym != n1->sym)) {
if (!chord.notes.isEmpty()) {
SymId sym = chord.notes.front()->sym;
if (sym == SymId::noteheadBlack)
chord.duration.setType(TDuration::DurationType::V_QUARTER);
else if (sym == SymId::noteheadHalf)
chord.duration.setType(TDuration::DurationType::V_HALF);
chords.append(chord);
chord.notes.clear();
}
}
nx = x;
nsym = n1->sym;
chord.notes.append(n1);
}
}
if (!chord.notes.isEmpty()) {
SymId sym = chord.notes.front()->sym;
if (sym == SymId::noteheadBlack)
chord.duration.setType(TDuration::DurationType::V_QUARTER);
else if (sym == SymId::noteheadHalf)
chord.duration.setType(TDuration::DurationType::V_HALF);
chords.append(chord);
}
m.chords().append(chords);
}
system->measures().append(m);
}
}
}
//---------------------------------------------------------
// searchClef
//---------------------------------------------------------
OmrClef OmrPage::searchClef(OmrSystem* system, OmrStaff* staff)
{
std::vector<Pattern*> pl = {
Omr::trebleclefPattern,
Omr::bassclefPattern
};
const OmrMeasure& m = system->measures().front();
//printf("search clef %d %d-%d\n", staff->y(), m.x1(), m.x2());
int x1 = m.x1() + 2;
int x2 = x1 + (m.x2() - x1) / 2;
OmrPattern p = searchPattern(pl, staff->y(), x1, x2);
OmrClef clef(p);
if (p.sym == SymId::gClef)
clef.type = ClefType::G;
else if (p.sym == SymId::fClef)
clef.type = ClefType::F;
else
clef.type = ClefType::G;
return clef;
}
//---------------------------------------------------------
// searchPattern
//---------------------------------------------------------
OmrPattern OmrPage::searchPattern(const std::vector<Pattern*>& pl, int y, int x1, int x2)
{
OmrPattern p;
p.sym = SymId::noSym;
p.prob = 0.0;
for (Pattern* pattern : pl) {
double val = 0.0;
int xx = 0;
int hw = pattern->w();
for (int x = x1; x < (x2 - hw); ++x) {
double val1 = pattern->match(&image(), x - pattern->base().x(), y - pattern->base().y());
if (val1 > val) {
val = val1;
xx = x;
}
}
if (val > p.prob) {
p.setRect(xx, y, pattern->w(), pattern->h());
p.sym = pattern->id();
p.prob = val;
}
//printf("Pattern found %d %f %d\n", int(pattern->id()), val, xx);
}
return p;
}
//---------------------------------------------------------
// searchTimeSig
//---------------------------------------------------------
OmrTimesig* OmrPage::searchTimeSig(OmrSystem* system)
{
int z = -1;
int n = -1;
double zval = 0;
double nval = 0;
QRect rz, rn;
int y = system->staves().front().y();
OmrMeasure* m = &system->measures().front();
int x1 = m->x1();
for (int i = 0; i < 10; ++i) {
Pattern* pattern = Omr::timesigPattern[i];
double val = 0.0;
int hh = pattern->h();
int hw = pattern->w();
int x2 = m->x2() - hw;
QRect r;
for (int x = x1; x < x2; ++x) {
double val1 = pattern->match(&image(), x, y);
if (val1 > val) {
val = val1;
r = QRect(x, y, hw, hh);
}
}
if (val > timesigTH && val > zval) {
z = i;
zval = val;
rz = r;
}
//printf(" found %d %f\n", i, val);
}
if (z < 0)
return 0;
y = system->staves().front().y() + lrint(_spatium * 2);
x1 = rz.x();
int x2 = x1 + 1;
OmrTimesig* ts = 0;
for (int i = 0; i < 10; ++i) {
Pattern* pattern = Omr::timesigPattern[i];
double val = 0.0;
int hh = pattern->h();
int hw = pattern->w();
QRect r;
for (int x = x1; x < x2; ++x) {
double val1 = pattern->match(&image(), x, y);
if (val1 > val) {
val = val1;
r = QRect(x, y, hw, hh);
}
}
if (val > timesigTH && val > nval) {
n = i;
nval = val;
rn = r;
}
//printf(" found %d %f\n", i, val);
}
if (n > 0) {
ts = new OmrTimesig(rz | rn);
ts->timesig = Fraction(z, n);
//printf("timesig %d/%d\n", z, n);
}
return ts;
}
//---------------------------------------------------------
// searchKeySig
//---------------------------------------------------------
void OmrPage::searchKeySig(OmrSystem* system, OmrStaff* staff)
{
Pattern* pl[2];
pl[0] = Omr::sharpPattern;
pl[1] = Omr::flatPattern;
double zval = 0;
int y = system->staves().front().y();
OmrMeasure* m = &system->measures().front();
int x1 = m->x1();
for (int i = 0; i < 2; ++i) {
Pattern* pattern = pl[i];
double val = 0.0;
int hh = pattern->h();
int hw = pattern->w();
int x2 = m->x2() - hw;
QRect r;
for (int x = x1; x < x2; ++x) {
double val1 = pattern->match(&image(), x, y - hh / 2);
if (val1 > val) {
val = val1;
r = QRect(x, y, hw, hh);
}
}
if (val > keysigTH && val > zval) {
zval = val;
OmrKeySig key(r);
key.type = i == 0 ? 1 : -1;
staff->setKeySig(key);
}
//printf(" key found %d %f\n", i, val);
}
}
//---------------------------------------------------------
// maxP
//---------------------------------------------------------
int maxP(int* projection, int x1, int x2)
{
int xx = x1;
int max = 0;
for (int x = x1; x < x2; ++x) {
if (projection[x] > max) {
max = projection[x];
xx = x;
}
}
return xx;
}
//---------------------------------------------------------
// BSTATE
//---------------------------------------------------------
struct BSTATE {
int x;
int status;//0 represents white space, 1 represents bar
};
//---------------------------------------------------------
// searchSysBarLines
//---------------------------------------------------------
void OmrSystem::searchSysBarLines()
{
OmrStaff& r1 = _staves[0];
OmrStaff& r2 = _staves[_staves.size() - 1];//[1];
int x1 = r1.x();
int x2 = x1 + r1.width();
int y1 = r1.y();
int y2 = r2.y() + r2.height();
int h = y2 - y1 + 1;
int th = /*r1.height() + r2.height() - 5;*/ h / 2; // threshold, data score for null model
int vpw = x2 - x1;
#if (!defined (_MSCVER) && !defined (_MSC_VER))
float vp[vpw];
memset(vp, 0, sizeof(float) * vpw);
//using note constraints
searchNotes();
int note_constraints[x2 - x1];
#else
// MSVC does not support VLA. Replace with std::vector. If profiling determines that the
// heap allocation is slow, an optimization might be used.
std::vector<float> vp(vpw); // Default-initialized, doesn't need to be cleared
//using note constraints
searchNotes();
std::vector<int> note_constraints(x2 - x1);
#endif
for (int i = 0; i < _staves.size(); i++) {
OmrStaff& r = _staves[i];
for (OmrNote* n : r.notes()) {
for (int x = n->x(); x <= n->x() + n->width(); ++x)
note_constraints[x - x1] = 1;
}
}
//
// compute vertical projections
//
for (int x = x1; x < x2; ++x) {
int dots = 0;
for (int y = y1; y < y2; ++y) {
if (_page->dot(x, y))
++dots;
}
if (!note_constraints[x - x1])
vp[x - x1] = dots - th;
else
vp[x - x1] = -HUGE_VAL;
}
#if (!defined (_MSCVER) && !defined (_MSC_VER))
float scores[x2 - x1 + 1][2];
BSTATE pred[x2 - x1 + 1][2];
#else
// MSVC does not support VLA. Replace with std::vector. If profiling determines that the
// heap allocation is slow, an optimization might be used.
std::vector<float[2]> scores(x2 - x1 + 1);
std::vector<BSTATE[2]> pred(x2 - x1 + 1);
#endif
BSTATE bs;
//initialization
bs.x = -1; bs.status = -1;
for (int x = x1; x <= x2; ++x) {
int i = x - x1;
for (int status = 0; status <= 1; ++status) {
scores[i][status] = -HUGE_VAL;
pred[i][status] = bs;
}
}
scores[0][0] = 0;
//forward pass
for (int x = x1; x < x2; ++x) {
int i = x - x1;
for (int cur = 0; cur <= 1; ++cur) {
//current state
if (scores[i][cur] < -1000) continue;
if (cur) {
scores[i][cur] += vp[i];
int next = 0;
int step = 3 * _page->spatium();//constraints between adjacent barlines
if (step + i <= x2 - x1) {
if (scores[step + i][next] < scores[i][cur]) {
scores[step + i][next] = scores[i][cur];
bs.x = x; bs.status = cur;
pred[step + i][next] = bs;
}
}
}
else {
for (int next = 0; next <= 1; ++next) {
int step = 1;
if (step + i <= x2 - x1) {
if(scores[step + i][next] < scores[i][cur]) {
scores[step + i][next] = scores[i][cur];
bs.x = x; bs.status = cur;
pred[step + i][next] = bs;
}
}
}
}
}
}
//trace back
int state = 0;
for (int x = x2; x > x1; ) {
int i = x - x1;
bs = pred[i][state];
state = bs.status;
x = bs.x;
if(state)
barLines.append(QLine(x, y1, x, y2));
}
}
//-------------------------------------------------------------------
// searchBarLinesvar: dynamic programming for system identification
//-------------------------------------------------------------------
float OmrSystem::searchBarLinesvar(int n_staff, int **note_labels)
{
OmrStaff& r1 = _staves[0];
OmrStaff& r2 = _staves[n_staff - 1];
int x1 = r1.left();
int x2 = r1.right();
int y1 = r1.top();
int y2 = r2.bottom();
int th = (y2 - y1) / 2;//threshold
//
//compute note constraints
//
int *note_constraints = new int[x2 - x1 + 1];
memset(note_constraints, 0, sizeof(int) * (x2 - x1 + 1));
for (int i = 0; i < n_staff; ++i) {
for (int j = x1; j <= x2; ++j) {
if (note_labels[i][j] == 1)
note_constraints[j - x1] = 1;
}
}
//
// compute vertical projections
//
int vpw = x2 - x1 + 1;
#if (!defined (_MSCVER) && !defined (_MSC_VER))
float vp[vpw];
for(int i = 0; i < vpw; i++) vp[i] = -HUGE_VAL;
#else
// MSVC does not support VLA. Replace with std::vector. If profiling determines that the
// heap allocation is slow, an optimization might be used.
std::vector<float> vp(vpw, -HUGE_VAL); // auto-initialized to -HUGE_VAL, doesn't need the loop
#endif
int bar_max_width = 3;
for (int x = x1 + bar_max_width; x < x2 - bar_max_width; ++x) {
int dots = 0;
for (int y = y1; y < y2; ++y) {
for (int w = -bar_max_width; w <= bar_max_width; w++) {
if (_page->dot(x + w, y)) {
++dots;
break;
}
}
}
if (!note_constraints[x - x1])
vp[x - x1] = dots - th;
else
vp[x - x1] = -HUGE_VAL;
}
float **scores = new float *[x2 - x1 + 1];
BSTATE **pred = new BSTATE *[x2 - x1 + 1];
for (int i = 0; i< x2 - x1 + 1; i++) {
scores[i] = new float[2];
pred[i] = new BSTATE[2];
}
BSTATE bs;
//initialization
bs.x = -1; bs.status = -1;
for (int x = x1; x <= x2; ++x) {
int i = x - x1;
for (int status = 0; status <= 1; ++status) {
scores[i][status] = -HUGE_VAL;
pred[i][status] = bs;
}
}
scores[0][0] = 0;
//forward pass
for (int x = x1; x <= x2; ++x) {
int i = x - x1;
for(int cur = 0; cur <= 1; ++cur) {
//current state
if (scores[i][cur] < -1000)
continue;
if (cur) {
scores[i][cur] += vp[i];
int next = 0;
int step = 8 * _page->spatium();//minimum distance between adjacent barlines
if (step + i <= x2 - x1) {
if (scores[step + i][next] < scores[i][cur]) {
scores[step + i][next] = scores[i][cur];
bs.x = x; bs.status = cur;
pred[step + i][next] = bs;
}
}
else {
if (scores[x2 - x1][next] < scores[i][cur]) {
scores[x2 - x1][next] = scores[i][cur];
bs.x = x; bs.status = cur;
pred[x2 - x1][next] = bs;
}
}
}
else {
for (int next = 0; next <= 1; ++next) {
int step = 1;
if (step + i <= x2 - x1) {
if (scores[step + i][next] < scores[i][cur]) {
scores[step + i][next] = scores[i][cur];
bs.x = x; bs.status = cur;
pred[step + i][next] = bs;
}
}
}
}
}
}
//trace back
int state = 0;
for (int x = x2; x > x1; ) {
int i = x - x1;
bs = pred[i][state];
state = bs.status;
x = bs.x;
if(state) {
barLines.append(QLine(x, y1, x, y2));
}
}
float best_score = scores[x2 - x1][0];
delete []note_constraints;
for (int i = 0; i< x2 - x1 + 1; i++) {
delete []scores[i];
delete []pred[i];
}
delete []scores;
delete []pred;
return(best_score);
}
//---------------------------------------------------------
// noteCompare
//---------------------------------------------------------
static bool noteCompare(OmrNote* n1, OmrNote* n2)
{
return n1->x() < n2->x();
}
static bool intersectFuzz(const QRect& a, const QRect& b, int fuzz)
{
int xfuzz = fuzz;
int yfuzz = fuzz;
if (a.x() > b.x())
xfuzz = -xfuzz;
if (a.y() > b.y())
yfuzz = -yfuzz;
return (a.intersects(b.translated(xfuzz, yfuzz)));
}
//---------------------------------------------------------
// searchNotes
//---------------------------------------------------------
void OmrSystem::searchNotes()
{
for (int i = 0; i < _staves.size(); ++i) {
OmrStaff* r = &_staves[i];
int x1 = r->x();
int x2 = x1 + r->width();
//
// search notes on a range of vertical line position
//
for (int line = 0; line < 8; ++line)
searchNotes(&r->notes(), x1, x2, r->y(), line);
//
// detect collisions
//
int fuzz = int(_page->spatium()) / 2;
for(OmrNote* n : r->notes()) {
for(OmrNote* m : r->notes()) {
if (m == n)
continue;
if (intersectFuzz(*m, *n, fuzz)) {
if (m->prob > n->prob)
r->notes().removeOne(n);
else
r->notes().removeOne(m);
}
}
}
qSort(r->notes().begin(), r->notes().end(), noteCompare);
}
}
//---------------------------------------------------------
// searchNotes
//---------------------------------------------------------
void OmrSystem::searchNotes(int *note_labels, int ran)
{
for (int i = 0; i < _staves.size(); ++i) {
OmrStaff* r = &_staves[i];
int x1 = r->x();
int x2 = x1 + r->width();
//
// search notes on a range of vertical line position
//
for (int line = -5; line < 14; ++line)
searchNotes(&r->notes(), x1, x2, r->y(), line);
//
// save detected note horizontal positions into note_labels
//
for(OmrNote* n : r->notes()) {
QPoint p = n->center();
int h_cent = p.x();
for(int h = h_cent - ran; h <= h_cent + ran; h++){
if(h >= x1 && h < x2) note_labels[h] = 1;
}
}
}
}
//---------------------------------------------------------
// addText
//---------------------------------------------------------
#ifdef OCR
static void addText(Score* score, int subtype, const QString& s)
{
#if 0 //TODO-1
MeasureBase* measure = score->first();
if (measure == 0 || measure->type() != Element::VBOX) {
measure = new VBox(score);
measure->setNext(score->first());
measure->setTick(0);
score->add(measure);
}
Text* text = new Text(score);
switch(subtype) {
case TEXT_TITLE: text->setTextStyle(TEXT_STYLE_TITLE); break;
case TEXT_SUBTITLE: text->setTextStyle(TEXT_STYLE_SUBTITLE); break;
case TEXT_COMPOSER: text->setTextStyle(TEXT_STYLE_COMPOSER); break;
case TEXT_POET: text->setTextStyle(TEXT_STYLE_POET); break;
}
text->setSubtype(subtype);
text->setText(s);
measure->add(text);
#endif
}
#endif
//---------------------------------------------------------
// readHeader
//---------------------------------------------------------
void OmrPage::readHeader(Score*)
{
if (_slices.isEmpty())
return;
double maxHeight = _spatium * 4 * 2;
int slice = 0;
double maxH = 0.0;
// int maxIdx;
for (; slice < _slices.size(); ++slice) {
double h = _slices[slice].height();
if (h > maxHeight)
break;
if (h > maxH) {
maxH = h;
// maxIdx = slice;
}
}
#ifdef OCR
//
// assume that highest slice contains header text
//
OcrImage img = OcrImage(_image.bits(), _slices[maxIdx], (_image.width() + 31) / 32);
QString s = _omr->ocr()->readLine(img).trimmed();
if (!s.isEmpty())
score->addText("title", s);
QString subTitle;
for (int i = maxIdx + 1; i < slice; ++i) {
OcrImage img = OcrImage(_image.bits(), _slices[i], (_image.width() + 31) / 32);
QString s = _omr->ocr()->readLine(img).trimmed();
if (!s.isEmpty()) {
if (!subTitle.isEmpty())
subTitle += "\n";
subTitle += s;
}
}
if (!subTitle.isEmpty())
score->addText("subtitle", subTitle);
#endif
#if 0
OcrImage img = OcrImage(_image.bits(), _slices[0], (_image.width() + 31) / 32);
QString s = _omr->ocr()->readLine(img).trimmed();
if (!s.isEmpty())
addText(score, TEXT_TITLE, s);
img = OcrImage(_image.bits(), _slices[1], (_image.width() + 31) / 32);
s = _omr->ocr()->readLine(img).trimmed();
if (!s.isEmpty())
addText(score, TEXT_SUBTITLE, s);
img = OcrImage(_image.bits(), _slices[2], (_image.width() + 31) / 32);
s = _omr->ocr()->readLine(img).trimmed();
if (!s.isEmpty())
addText(score, TEXT_COMPOSER, s);
#endif
}
//---------------------------------------------------------
// removeBorder
//---------------------------------------------------------
void OmrPage::removeBorder()
{
#if 0
cropT = cropB = cropL = cropR = 0;
for (int y = 0; y < height(); ++y) {
for (int x = 0; x < width(); ++x) {
QRgb c = _image.pixel(x,y);
if(qGray(c) > 0){
cropT = y;
break;
}
}
if (cropT)
break;
}
for (int y = height() - 1; y >= cropT; --y) {
for (int x = 0; x < width(); ++x) {
QRgb c = _image.pixel(x,y);
if(qGray(c) > 0){
cropB = y;
break;
}
}
if (cropB)
break;
}
int y1 = cropT;
int y2 = cropB;
for (int x = 0; x < width(); ++x) {
for (int y = y1; y < y2; ++y) {
QRgb c = _image.pixel(x,y);
if(qGray(c) > 0){
cropL = x;
break;
}
}
if (cropL)
break;
}
for (int x = width() - 1; x >= cropL; --x) {
for (int y = y1; y < y2; ++y) {
QRgb c = _image.pixel(x,y);
if(qGray(c) > 0){
cropR = x;
break;
}
}
if (cropR)
break;
}
_image = _image.copy(cropL, cropT, cropR - cropL + 1, cropB - cropT + 1);
#endif
}
//---------------------------------------------------------
// crop
//---------------------------------------------------------
void OmrPage::crop()
{
int wl = wordsPerLine();
cropT = cropB = cropL = cropR = 0;
for (int y = 0; y < height(); ++y) {
const uint* p = scanLine(y);
for (int k = 0; k < wl; ++k) {
if (*p++) {
cropT = y;
break;
}
}
if (cropT)
break;
}
for (int y = height() - 1; y >= cropT; --y) {
const uint* p = scanLine(y);
for (int k = 0; k < wl; ++k) {
if (*p++) {
cropB = height() - y - 1;
break;
}
}
if (cropB)
break;
}
int y1 = cropT;
int y2 = height() - cropT - cropB;
for (int x = 0; x < wl; ++x) {
for (int y = y1; y < y2; ++y) {
if (*(scanLine(y) + x)) {
cropL = x;
break;
}
}
if (cropL)
break;
}
for (int x = wl - 1; x >= cropL; --x) {
for (int y = y1; y < y2; ++y) {
if (*(scanLine(y) + x)) {
cropR = wl - x - 1;
break;
}
}
if (cropR)
break;
}
//printf("*** crop: T%d B%d L%d R:%d\n", cropT, cropB, cropL, cropR);
}
//---------------------------------------------------------
// slice
//---------------------------------------------------------
void OmrPage::slice()
{
_slices.clear();
int y1 = cropT;
int y2 = height() - cropB;
int x1 = cropL;
int x2 = wordsPerLine() - cropR;
int xbits = (x2 - x1) * 32;
// _slices.append(QRect(cropL*32, y1, xbits, y2-y1));
#if 1
for (int y = y1; y < y2;) {
//
// skip contents
//
int ys = y;
for (; y < y2; ++y) {
const uint* p = scanLine(y) + cropL;
bool bits = false;
for (int x = cropL; x < x2; ++x) {
if (*p) {
bits = true;
break;
}
++p;
}
if (!bits)
break;
}
if (y - ys)
_slices.append(QRect(cropL * 32, ys, xbits, y - ys));
//
// skip space
//
for (; y < y2; ++y) {
const uint* p = scanLine(y) + cropL;
bool bits = false;
for (int x = cropL; x < x2; ++x) {
if (*p) {
bits = true;
break;
}
++p;
}
if (bits)
break;
}
}
#endif
}
//---------------------------------------------------------
// deSkew
//---------------------------------------------------------
void OmrPage::deSkew()
{
int wl = wordsPerLine();
int h = height();
uint* db = new uint[wl * h];
memset(db, 0, wl * h * sizeof(uint));
for(const QRect& r : _slices) {
double rot = skew(r);
if (qAbs(rot) < 0.1) {
memcpy(db + wl * r.y(), scanLine(r.y()), wl * r.height() * sizeof(uint));
continue;
}
QTransform t;
t.rotate(rot);
QTransform tt = QImage::trueMatrix(t, width(), r.height());
double m11 = tt.m11();
double m12 = tt.m12();
double m21 = tt.m21();
double m22 = tt.m22();
double dx = tt.m31();
double dy = tt.m32();
double m21y = r.y() * m21;
double m22y = r.y() * m22;
int y2 = r.y() + r.height();
for (int y = r.y(); y < y2; ++y) {
const uint* s = scanLine(y);
m21y += m21;
m22y += m22;
for (int x = 0; x < wl; ++x) {
uint c = *s++;
if (c == 0)
continue;
uint mask = 1;
for (int xx = 0; xx < 32; ++xx) {
if (c & mask) {
int xs = x * 32 + xx;
int xd = lrint(m11 * xs + m21y + dx);
int yd = lrint(m22y + m12 * xs + dy);
int wxd = xd / 32;
if ((xd >= 0) && (wxd < wl) && (yd >= 0) && (yd < h)) {
uint* d = db + wl * yd + wxd;
if( d < db + wl * h) //check that we are in the bounds.
*d |= (0x1 << (xd % 32));
}
}
mask <<= 1;
}
}
}
}
memcpy(_image.bits(), db, wl * h * sizeof(uint));
delete[] db;
}
struct ScanLine {
int run;
int x1, x2;
ScanLine() { run = 0; x1 = 100000; x2 = 0; }
};
struct H {
int y;
int bits;
H(int a, int b) : y(a), bits(b) {}
};
//---------------------------------------------------------
// xproject
//---------------------------------------------------------
int OmrPage::xproject(const uint* p, int wl)
{
int run = 0;
int w = wl - cropL - cropR;
int x1 = cropL + w / 4; // only look at part of page
int x2 = x1 + w / 2;
for (int x = cropL; x < x2; ++x) {
uint v = *p++;
run += Omr::bitsSetTable[v & 0xff]
+ Omr::bitsSetTable[(v >> 8) & 0xff]
+ Omr::bitsSetTable[(v >> 16) & 0xff]
+ Omr::bitsSetTable[v >> 24];
}
return run;
}
//---------------------------------------------------------
// xproject2
//---------------------------------------------------------
double OmrPage::xproject2(int y1)
{
int wl = wordsPerLine();
const uint* db = bits();
double val = 0.0;
int w = wl - cropL - cropR;
int x1 = (cropL + w / 4) * 32; // only look at part of page
int x2 = x1 + (w / 2 * 32);
int ddx = x2 - x1;
for (int dy = -12; dy < 12; ++dy) {
int onRun = 0;
int offRun = 0;
int on = 0;
int off = 0;
bool onFlag = false;
int incy = (dy > 0) ? 1 : (dy < 0) ? -1 : 0;
int ddy = dy < 0 ? -dy : dy;
int y = y1;
if (y < 1)
y = 0;
int err = ddx / 2;
for (int x = x1; x < x2;) {
const uint* d = db + wl * y + (x / 32);
if (d < db + wl)
break;
if (d >= db + (wl - 1) * height()) //check that we are in the bounds.
break;
bool bit = ((*d) & (0x1 << (x % 32)));
bit = bit || ((*(d + wl)) & (0x1 << (x % 32)));
bit = bit || ((*(d - wl)) & (0x1 << (x % 32)));
if (bit != onFlag) {
if (!onFlag) {
//
// end of offrun:
//
if (offRun > 20) {
off += offRun * offRun;
on += onRun * onRun;
onRun = 0;
offRun = 0;
}
else {
onRun += offRun;
offRun = 0;
}
}
onFlag = bit;
}
(bit ? onRun : offRun)++;
if (offRun > 100) {
offRun = 0;
off = 1;
on = 0;
onRun = 0;
break;
}
err -= ddy;
if (err < 0) {
err += ddx;
y += incy;
if (y < 1)
y = 1;
else if (y >= height())
y = height() - 1;
}
++x;
}
if (offRun > 20)
off += offRun * offRun;
else
onRun += offRun;
on += onRun * onRun;
if (off == 0)
off = 1;
double nval = double(on) / double(off);
if (nval > val)
val = nval;
}
return val;
}
static bool sortLvStaves(const Lv& a, const Lv& b)
{
return a.line < b.line;
}
//---------------------------------------------------------
// getStaffLines
//---------------------------------------------------------
void OmrPage::getStaffLines()
{
staves.clear();
int h = height();
int wl = wordsPerLine();
//printf("getStaffLines %d %d crop %d %d\n", h, wl, cropT, cropB);
if (h < 1)
return;
int y1 = cropT;
int y2 = h - cropB;
if (y2 >= h)
--y2;
#if (!defined (_MSCVER) && !defined (_MSC_VER))
double projection[h];
#else
// MSVC does not support VLA. Replace with std::vector. If profiling determines that the
// heap allocation is slow, an optimization might be used.
std::vector<double> projection(h);
#endif
for (int y = 0; y < y1; ++y)
projection[y] = 0;
for (int y = y2; y < h; ++y)
projection[y] = 0;
for (int y = y1; y < y2; ++y)
projection[y] = xproject2(y);
int autoTableSize = (wl * 32) / 20; // 1/20 page width
if (autoTableSize > y2 - y1)
autoTableSize = y2 - y1;
#if (!defined (_MSCVER) && !defined (_MSC_VER))
double autoTable[autoTableSize];
memset(autoTable, 0, sizeof(autoTable));
for (int i = 0; i < autoTableSize; ++i) {
autoTable[i] = covariance(projection + y1, projection + i + y1, y2 - y1 - i);
}
#else
// MSVC does not support VLA. Replace with std::vector. If profiling determines that the
// heap allocation is slow, an optimization might be used.
std::vector<double> autoTable(autoTableSize); // Default-initialized, doesn't need to be cleared
for(int i = 0; i < autoTableSize; ++i)
{
autoTable[i] = covariance(&(projection[y1]), &(projection[i + y1]), y2 - y1 - i);
}
#endif
//
// search for first maximum in covariance starting at 10 to skip
// line width. Staff line distance (spatium) must be at least 10 dots
//
double maxCorrelation = 0;
_spatium = 0;
for (int i = 5; i < autoTableSize; ++i) {
if (autoTable[i] > maxCorrelation) {
maxCorrelation = autoTable[i];
_spatium = i;
}
}
if (_spatium == 0) {
//printf("*** no staff lines found\n");
return;
}
//---------------------------------------------------
// look for staves
//---------------------------------------------------
QList<Lv> lv;
int ly = 0;
int lval = -1000.0;
for (int y = y1; y < (y2 - _spatium * 4); ++y) {
double val = 0.0;
for (int i = 0; i < 5; ++i)
val += projection[y + i * int(_spatium)];
if (val < lval) {
lv.append(Lv(ly, lval));
}
lval = val;
ly = y;
}
qSort(lv);
QList<Lv> staveTop;
int staffHeight = _spatium * 6;
for (Lv a : lv) {
if (a.val < 500) // MAGIC to avoid false positives
continue;
int line = a.line;
bool ok = true;
for (Lv b : staveTop) {
if ((line > (b.line - staffHeight)) && (line < (b.line + staffHeight))) {
ok = false;
break;
}
}
if (ok)
staveTop.append(a);
}
qSort(staveTop.begin(), staveTop.end(), sortLvStaves);
for (Lv a : staveTop) {
staves.append(OmrStaff(cropL * 32, a.line, (wordsPerLine() - cropL - cropR) * 32, _spatium * 4));
}
}
//---------------------------------------------------------
// getRatio
//---------------------------------------------------------
void OmrPage::getRatio()
{
double num_black;
double num_white;
_ratio = 0.0;
num_black = 1;
num_white = 1;
for (int x = 0; x < width(); ++x) {
for (int y = 0; y < height(); ++y) {
if(isBlack(x,y)) num_black++;
else num_white++;
}
}
_ratio = num_black / (num_black + num_white);
}
//---------------------------------------------------------
// searchNotes
//---------------------------------------------------------
void OmrSystem::searchNotes(QList<OmrNote*>* noteList, int x1, int x2, int y, int line)
{
//a simple and cheap note detector (heuristic approach)
double _spatium = _page->spatium();
y += line * _spatium * .5;
QList<Peak> notePeaks;
Pattern* pattern = Omr::quartheadPattern;
int hh = pattern->h();
int hw = pattern->w();
double val;
int step_size = 2;
int note_thresh = 50;
for (int x = x1; x < (x2 - hw); x += step_size) {
val = pattern->match(&_page->image(), x, y - hh / 2, _page->ratio());
if (val > note_thresh) {
notePeaks.append(Peak(x, val, 0));
}
}
int n = notePeaks.size();
for (int i = 0; i < n; ++i) {
OmrNote* note = new OmrNote;
int hh1 = pattern->h();
int hw1 = pattern->w();
note->setRect(notePeaks[i].x, y - hh1 / 2, hw1, hh1);
note->line = line;
note->sym = pattern->id();
note->prob = notePeaks[i].val;
noteList->append(note);
}
return;
}
//---------------------------------------------------------
// staffDistance
//---------------------------------------------------------
double OmrPage::staffDistance() const
{
if (staves.size() < 2)
return 5;
return ((staves[1].y() - staves[0].y()) / _spatium) - 4.0;
}
//---------------------------------------------------------
// systemDistance
//---------------------------------------------------------
double OmrPage::systemDistance() const
{
if (staves.size() < 3)
return 6.0;
return ((staves[2].y() - staves[1].y()) / _spatium) - 4.0;
}
//---------------------------------------------------------
// write
//---------------------------------------------------------
void OmrPage::write(XmlWriter& xml) const
{
xml.stag("OmrPage");
xml.tag("cropL", cropL);
xml.tag("cropR", cropR);
xml.tag("cropT", cropT);
xml.tag("cropB", cropB);
for(const QRect& r : staves)
xml.tag("staff", QRectF(r));
xml.etag();
}
//---------------------------------------------------------
// read
//---------------------------------------------------------
void OmrPage::read(XmlReader& e)
{
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "cropL")
cropL = e.readInt();
else if (tag == "cropR")
cropR = e.readInt();
else if (tag == "cropT")
cropT = e.readInt();
else if (tag == "cropB")
cropB = e.readInt();
else if (tag == "staff") {
OmrStaff r(e.readRect().toRect());
staves.append(r);
}
else
e.unknown();
}
}
}