annotate src/Matcher.cpp @ 191:f415747b151b re-minimise

Normalised path costs should use a different type from un-normalised ones (because they are in a different range)
author Chris Cannam
date Thu, 26 Feb 2015 15:52:16 +0000
parents 487261a22b18
children b5deca82e074
rev   line source
cannam@0 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
cannam@0 2
cannam@0 3 /*
cannam@0 4 Vamp feature extraction plugin using the MATCH audio alignment
cannam@0 5 algorithm.
cannam@0 6
cannam@0 7 Centre for Digital Music, Queen Mary, University of London.
cannam@0 8 This file copyright 2007 Simon Dixon, Chris Cannam and QMUL.
cannam@0 9
cannam@0 10 This program is free software; you can redistribute it and/or
cannam@0 11 modify it under the terms of the GNU General Public License as
cannam@0 12 published by the Free Software Foundation; either version 2 of the
cannam@0 13 License, or (at your option) any later version. See the file
cannam@0 14 COPYING included with this distribution for more information.
cannam@0 15 */
cannam@0 16
cannam@0 17 #include "Matcher.h"
cannam@0 18
cannam@0 19 #include <iostream>
cannam@0 20
cannam@4 21 #include <cstdlib>
Chris@16 22 #include <cassert>
cannam@4 23
Chris@72 24 using namespace std;
Chris@72 25
Chris@10 26 //#define DEBUG_MATCHER 1
Chris@10 27
Chris@143 28 Matcher::Matcher(Parameters parameters, DistanceMetric::Parameters dparams,
Chris@143 29 Matcher *p) :
Chris@43 30 m_params(parameters),
Chris@143 31 m_metric(dparams)
Chris@23 32 {
Chris@23 33 #ifdef DEBUG_MATCHER
Chris@143 34 cerr << "*** Matcher: hopTime = " << parameters.hopTime
Chris@140 35 << ", blockTime = " << parameters.blockTime
Chris@140 36 << ", maxRunCount = " << parameters.maxRunCount
Chris@140 37 << ", diagonalWeight = " << parameters.diagonalWeight << endl;
Chris@23 38 #endif
Chris@140 39
Chris@43 40 m_otherMatcher = p; // the first matcher will need this to be set later
Chris@43 41 m_firstPM = (!p);
Chris@43 42 m_frameCount = 0;
Chris@43 43 m_runCount = 0;
Chris@43 44 m_blockSize = 0;
cannam@0 45
Chris@180 46 m_blockSize = int(m_params.blockTime / m_params.hopTime + 0.5);
Chris@15 47 #ifdef DEBUG_MATCHER
Chris@43 48 cerr << "Matcher: m_blockSize = " << m_blockSize << endl;
Chris@15 49 #endif
cannam@0 50
Chris@43 51 m_initialised = false;
Chris@23 52 }
cannam@0 53
cannam@0 54 Matcher::~Matcher()
cannam@0 55 {
Chris@10 56 #ifdef DEBUG_MATCHER
Chris@15 57 cerr << "Matcher(" << this << ")::~Matcher()" << endl;
Chris@10 58 #endif
cannam@0 59 }
cannam@0 60
cannam@0 61 void
cannam@0 62 Matcher::init()
cannam@0 63 {
Chris@43 64 if (m_initialised) return;
cannam@0 65
Chris@182 66 m_features = featureseq_t(m_blockSize);
cannam@0 67
Chris@43 68 m_distXSize = m_blockSize * 2;
Chris@45 69
Chris@41 70 size();
cannam@0 71
Chris@43 72 m_frameCount = 0;
Chris@43 73 m_runCount = 0;
Chris@38 74
Chris@43 75 m_initialised = true;
Chris@16 76 }
Chris@16 77
Chris@87 78 bool
Chris@154 79 Matcher::isRowAvailable(int i)
Chris@154 80 {
Chris@154 81 if (i < 0 || i >= int(m_first.size())) return false;
Chris@154 82
Chris@154 83 for (int j = m_first[i]; j < int(m_first[i] + m_bestPathCost[i].size()); ++j) {
Chris@154 84 if (isAvailable(i, j)) {
Chris@154 85 return true;
Chris@154 86 }
Chris@154 87 }
Chris@154 88
Chris@154 89 return false;
Chris@154 90 }
Chris@154 91
Chris@154 92 bool
Chris@154 93 Matcher::isColAvailable(int i)
Chris@154 94 {
Chris@154 95 return m_otherMatcher->isRowAvailable(i);
Chris@154 96 }
Chris@154 97
Chris@154 98 bool
Chris@87 99 Matcher::isInRange(int i, int j)
Chris@87 100 {
Chris@87 101 if (m_firstPM) {
Chris@87 102 return ((i >= 0) &&
Chris@87 103 (i < int(m_first.size())) &&
Chris@87 104 (j >= m_first[i]) &&
Chris@87 105 (j < int(m_first[i] + m_bestPathCost[i].size())));
Chris@87 106 } else {
Chris@87 107 return m_otherMatcher->isInRange(j, i);
Chris@87 108 }
Chris@87 109 }
Chris@87 110
Chris@87 111 bool
Chris@87 112 Matcher::isAvailable(int i, int j)
Chris@87 113 {
Chris@87 114 if (m_firstPM) {
Chris@87 115 if (isInRange(i, j)) {
Chris@87 116 return (m_bestPathCost[i][j - m_first[i]] >= 0);
Chris@87 117 } else {
Chris@87 118 return false;
Chris@87 119 }
Chris@87 120 } else {
Chris@87 121 return m_otherMatcher->isAvailable(j, i);
Chris@87 122 }
Chris@87 123 }
Chris@87 124
Chris@87 125 pair<int, int>
Chris@87 126 Matcher::getColRange(int i)
Chris@87 127 {
Chris@87 128 if (i < 0 || i >= int(m_first.size())) {
Chris@87 129 cerr << "ERROR: Matcher::getColRange(" << i << "): Index out of range"
Chris@87 130 << endl;
Chris@87 131 throw "Index out of range";
Chris@87 132 } else {
Chris@87 133 return pair<int, int>(m_first[i], m_last[i]);
Chris@87 134 }
Chris@87 135 }
Chris@87 136
Chris@87 137 pair<int, int>
Chris@87 138 Matcher::getRowRange(int i)
Chris@87 139 {
Chris@87 140 return m_otherMatcher->getColRange(i);
Chris@87 141 }
Chris@87 142
Chris@182 143 distance_t
Chris@87 144 Matcher::getDistance(int i, int j)
Chris@87 145 {
Chris@87 146 if (m_firstPM) {
Chris@87 147 if (!isInRange(i, j)) {
Chris@87 148 cerr << "ERROR: Matcher::getDistance(" << i << ", " << j << "): "
Chris@87 149 << "Location is not in range" << endl;
Chris@87 150 throw "Distance not available";
Chris@87 151 }
Chris@182 152 distance_t dist = m_distance[i][j - m_first[i]];
Chris@183 153 if (dist == InvalidDistance) {
Chris@87 154 cerr << "ERROR: Matcher::getDistance(" << i << ", " << j << "): "
Chris@87 155 << "Location is in range, but distance ("
Chris@191 156 << distance_print_t(dist)
Chris@191 157 << ") is invalid or has not been set" << endl;
Chris@87 158 throw "Distance not available";
Chris@87 159 }
Chris@87 160 return dist;
Chris@87 161 } else {
Chris@87 162 return m_otherMatcher->getDistance(j, i);
Chris@87 163 }
Chris@87 164 }
Chris@87 165
Chris@87 166 void
Chris@182 167 Matcher::setDistance(int i, int j, distance_t distance)
Chris@87 168 {
Chris@87 169 if (m_firstPM) {
Chris@87 170 if (!isInRange(i, j)) {
Chris@87 171 cerr << "ERROR: Matcher::setDistance(" << i << ", " << j << ", "
Chris@191 172 << distance_print_t(distance)
Chris@191 173 << "): Location is out of range" << endl;
Chris@87 174 throw "Indices out of range";
Chris@87 175 }
Chris@87 176 m_distance[i][j - m_first[i]] = distance;
Chris@87 177 } else {
Chris@87 178 m_otherMatcher->setDistance(j, i, distance);
Chris@87 179 }
Chris@87 180 }
Chris@87 181
Chris@191 182 normpathcost_t
Chris@135 183 Matcher::getNormalisedPathCost(int i, int j)
Chris@135 184 {
Chris@135 185 // normalised for path length. 1+ prevents division by zero here
Chris@191 186 return normpathcost_t(getPathCost(i, j)) / normpathcost_t(1 + i + j);
Chris@135 187 }
Chris@135 188
Chris@182 189 pathcost_t
Chris@87 190 Matcher::getPathCost(int i, int j)
Chris@87 191 {
Chris@87 192 if (m_firstPM) {
Chris@87 193 if (!isAvailable(i, j)) {
Chris@87 194 if (!isInRange(i, j)) {
Chris@87 195 cerr << "ERROR: Matcher::getPathCost(" << i << ", " << j << "): "
Chris@87 196 << "Location is not in range" << endl;
Chris@87 197 } else {
Chris@87 198 cerr << "ERROR: Matcher::getPathCost(" << i << ", " << j << "): "
Chris@87 199 << "Location is in range, but pathCost ("
Chris@87 200 << m_bestPathCost[i][j - m_first[i]]
Chris@87 201 << ") is invalid or has not been set" << endl;
Chris@87 202 }
Chris@87 203 throw "Path cost not available";
Chris@87 204 }
Chris@87 205 return m_bestPathCost[i][j - m_first[i]];
Chris@87 206 } else {
Chris@87 207 return m_otherMatcher->getPathCost(j, i);
Chris@87 208 }
Chris@87 209 }
Chris@87 210
Chris@87 211 void
Chris@182 212 Matcher::setPathCost(int i, int j, advance_t dir, pathcost_t pathCost)
Chris@87 213 {
Chris@87 214 if (m_firstPM) {
Chris@87 215 if (!isInRange(i, j)) {
Chris@87 216 cerr << "ERROR: Matcher::setPathCost(" << i << ", " << j << ", "
Chris@87 217 << dir << ", " << pathCost
Chris@87 218 << "): Location is out of range" << endl;
Chris@87 219 throw "Indices out of range";
Chris@87 220 }
Chris@87 221 m_advance[i][j - m_first[i]] = dir;
Chris@87 222 m_bestPathCost[i][j - m_first[i]] = pathCost;
Chris@87 223 } else {
Chris@87 224 if (dir == AdvanceThis) {
Chris@87 225 dir = AdvanceOther;
Chris@87 226 } else if (dir == AdvanceOther) {
Chris@87 227 dir = AdvanceThis;
Chris@87 228 }
Chris@87 229 m_otherMatcher->setPathCost(j, i, dir, pathCost);
Chris@87 230 }
Chris@87 231 }
Chris@87 232
cannam@0 233 void
Chris@41 234 Matcher::size()
cannam@0 235 {
Chris@43 236 int distSize = (m_params.maxRunCount + 1) * m_blockSize;
Chris@183 237 m_bestPathCost.resize(m_distXSize, pathcostvec_t(distSize, InvalidPathCost));
Chris@183 238 m_distance.resize(m_distXSize, distancevec_t(distSize, InvalidDistance));
Chris@182 239 m_advance.resize(m_distXSize, advancevec_t(distSize, AdvanceNone));
Chris@43 240 m_first.resize(m_distXSize, 0);
Chris@43 241 m_last.resize(m_distXSize, 0);
Chris@38 242 }
cannam@0 243
Chris@23 244 void
Chris@182 245 Matcher::consumeFeatureVector(const feature_t &feature)
Chris@23 246 {
Chris@43 247 if (!m_initialised) init();
Chris@43 248 int frameIndex = m_frameCount % m_blockSize;
Chris@182 249 m_features[frameIndex] = feature;
Chris@23 250 calcAdvance();
Chris@21 251 }
Chris@21 252
Chris@21 253 void
Chris@21 254 Matcher::calcAdvance()
Chris@21 255 {
Chris@43 256 int frameIndex = m_frameCount % m_blockSize;
Chris@21 257
Chris@43 258 if (m_frameCount >= m_distXSize) {
Chris@43 259 m_distXSize *= 2;
Chris@41 260 size();
cannam@0 261 }
cannam@0 262
Chris@43 263 if (m_firstPM && (m_frameCount >= m_blockSize)) {
cannam@0 264
Chris@43 265 int len = m_last[m_frameCount - m_blockSize] -
Chris@43 266 m_first[m_frameCount - m_blockSize];
cannam@0 267
Chris@43 268 // We need to copy distance[m_frameCount-m_blockSize] to
Chris@43 269 // distance[m_frameCount], and then truncate
Chris@43 270 // distance[m_frameCount-m_blockSize] to its first len elements.
cannam@0 271 // Same for bestPathCost.
cannam@0 272
Chris@182 273 distancevec_t dOld(m_distance[m_frameCount - m_blockSize]);
Chris@183 274 distancevec_t dNew(len, InvalidDistance);
cannam@0 275
Chris@182 276 pathcostvec_t bpcOld(m_bestPathCost[m_frameCount - m_blockSize]);
Chris@183 277 pathcostvec_t bpcNew(len, InvalidPathCost);
Chris@69 278
Chris@182 279 advancevec_t adOld(m_advance[m_frameCount - m_blockSize]);
Chris@182 280 advancevec_t adNew(len, AdvanceNone);
Chris@69 281
Chris@69 282 for (int i = 0; i < len; ++i) {
Chris@69 283 dNew[i] = dOld[i];
Chris@69 284 bpcNew[i] = bpcOld[i];
Chris@69 285 adNew[i] = adOld[i];
Chris@69 286 }
Chris@45 287
Chris@69 288 m_distance[m_frameCount] = dOld;
Chris@69 289 m_distance[m_frameCount - m_blockSize] = dNew;
Chris@69 290
Chris@69 291 m_bestPathCost[m_frameCount] = bpcOld;
Chris@69 292 m_bestPathCost[m_frameCount - m_blockSize] = bpcNew;
Chris@69 293
Chris@69 294 m_advance[m_frameCount] = adOld;
Chris@69 295 m_advance[m_frameCount - m_blockSize] = adNew;
cannam@0 296 }
cannam@0 297
Chris@43 298 int stop = m_otherMatcher->m_frameCount;
Chris@43 299 int index = stop - m_blockSize;
Chris@86 300 if (index < 0) index = 0;
Chris@86 301
Chris@43 302 m_first[m_frameCount] = index;
Chris@43 303 m_last[m_frameCount] = stop;
cannam@0 304
cannam@0 305 for ( ; index < stop; index++) {
Chris@26 306
Chris@188 307 distance_t distance = m_metric.calcDistance
Chris@182 308 (m_features[frameIndex],
Chris@182 309 m_otherMatcher->m_features[index % m_blockSize]);
Chris@26 310
Chris@188 311 pathcost_t straightIncrement(distance);
Chris@188 312 pathcost_t diagIncrement = pathcost_t(distance * m_params.diagonalWeight);
Chris@89 313
Chris@86 314 if ((m_frameCount == 0) && (index == 0)) { // first element
Chris@86 315
Chris@86 316 updateValue(0, 0, AdvanceNone,
Chris@86 317 0,
Chris@86 318 distance);
Chris@86 319
Chris@86 320 } else if (m_frameCount == 0) { // first row
Chris@86 321
Chris@71 322 updateValue(0, index, AdvanceOther,
Chris@86 323 getPathCost(0, index-1),
Chris@86 324 distance);
Chris@86 325
Chris@86 326 } else if (index == 0) { // first column
Chris@86 327
Chris@71 328 updateValue(m_frameCount, index, AdvanceThis,
Chris@86 329 getPathCost(m_frameCount - 1, 0),
Chris@86 330 distance);
Chris@86 331
Chris@86 332 } else if (index == m_otherMatcher->m_frameCount - m_blockSize) {
Chris@86 333
cannam@0 334 // missing value(s) due to cutoff
cannam@0 335 // - no previous value in current row (resp. column)
cannam@0 336 // - no diagonal value if prev. dir. == curr. dirn
Chris@86 337
Chris@182 338 pathcost_t min2 = getPathCost(m_frameCount - 1, index);
Chris@86 339
Chris@91 340 // cerr << "NOTE: missing value at i = " << m_frameCount << ", j = "
Chris@91 341 // << index << " (first = " << m_firstPM << ")" << endl;
Chris@86 342
Chris@43 343 // if ((m_firstPM && (first[m_frameCount - 1] == index)) ||
Chris@43 344 // (!m_firstPM && (m_last[index-1] < m_frameCount)))
Chris@86 345 if (m_first[m_frameCount - 1] == index) {
Chris@86 346
Chris@86 347 updateValue(m_frameCount, index, AdvanceThis,
Chris@86 348 min2, distance);
Chris@86 349
Chris@86 350 } else {
Chris@86 351
Chris@182 352 pathcost_t min1 = getPathCost(m_frameCount - 1, index - 1);
Chris@188 353 if (min1 + diagIncrement <= min2 + straightIncrement) {
Chris@86 354 updateValue(m_frameCount, index, AdvanceBoth,
Chris@86 355 min1, distance);
Chris@86 356 } else {
Chris@86 357 updateValue(m_frameCount, index, AdvanceThis,
Chris@86 358 min2, distance);
Chris@86 359 }
cannam@0 360 }
Chris@86 361
cannam@0 362 } else {
Chris@86 363
Chris@182 364 pathcost_t min1 = getPathCost(m_frameCount, index - 1);
Chris@182 365 pathcost_t min2 = getPathCost(m_frameCount - 1, index);
Chris@182 366 pathcost_t min3 = getPathCost(m_frameCount - 1, index - 1);
Chris@87 367
Chris@188 368 pathcost_t cost1 = min1 + straightIncrement;
Chris@188 369 pathcost_t cost2 = min2 + straightIncrement;
Chris@188 370 pathcost_t cost3 = min3 + diagIncrement;
Chris@93 371
Chris@93 372 // Choosing is easy if there is a strict cheapest of the
Chris@93 373 // three. If two or more share the lowest cost, we choose
Chris@93 374 // in order of preference: cost3 (AdvanceBoth), cost2
Chris@94 375 // (AdvanceThis), cost1 (AdvanceOther) if we are the first
Chris@94 376 // matcher; and cost3 (AdvanceBoth), cost1 (AdvanceOther),
Chris@94 377 // cost2 (AdvanceThis) if we are the second matcher. That
Chris@94 378 // is, we always prioritise the diagonal followed by the
Chris@94 379 // first matcher.
Chris@94 380
Chris@94 381 if (( m_firstPM && (cost1 < cost2)) ||
Chris@94 382 (!m_firstPM && (cost1 <= cost2))) {
Chris@89 383 if (cost3 <= cost1) {
Chris@86 384 updateValue(m_frameCount, index, AdvanceBoth,
Chris@86 385 min3, distance);
Chris@86 386 } else {
Chris@86 387 updateValue(m_frameCount, index, AdvanceOther,
Chris@86 388 min1, distance);
Chris@86 389 }
cannam@0 390 } else {
Chris@89 391 if (cost3 <= cost2) {
Chris@86 392 updateValue(m_frameCount, index, AdvanceBoth,
Chris@86 393 min3, distance);
Chris@86 394 } else {
Chris@86 395 updateValue(m_frameCount, index, AdvanceThis,
Chris@86 396 min2, distance);
Chris@86 397 }
cannam@0 398 }
cannam@0 399 }
Chris@86 400
Chris@43 401 m_otherMatcher->m_last[index]++;
cannam@0 402 } // loop for row (resp. column)
cannam@0 403
Chris@43 404 m_frameCount++;
Chris@43 405 m_runCount++;
cannam@0 406
Chris@43 407 m_otherMatcher->m_runCount = 0;
Chris@21 408 }
cannam@0 409
cannam@0 410 void
Chris@182 411 Matcher::updateValue(int i, int j, advance_t dir, pathcost_t value, distance_t distance)
cannam@0 412 {
Chris@188 413 pathcost_t increment = distance;
Chris@83 414 if (dir == AdvanceBoth) {
Chris@188 415 increment = pathcost_t(increment * m_params.diagonalWeight);
Chris@188 416 }
Chris@188 417
Chris@188 418 pathcost_t newValue = value + increment;
Chris@188 419 if (MaxPathCost - increment < value) {
Chris@188 420 cerr << "ERROR: Path cost overflow at i=" << i << ", j=" << j << ": "
Chris@188 421 << value << " + " << increment << " > " << MaxPathCost << endl;
Chris@188 422 newValue = MaxPathCost;
Chris@83 423 }
Chris@83 424
Chris@43 425 if (m_firstPM) {
Chris@45 426
Chris@96 427 setDistance(i, j, distance);
Chris@188 428 setPathCost(i, j, dir, newValue);
Chris@45 429
cannam@0 430 } else {
Chris@45 431
Chris@86 432 if (dir == AdvanceThis) dir = AdvanceOther;
Chris@86 433 else if (dir == AdvanceOther) dir = AdvanceThis;
Chris@84 434
Chris@43 435 int idx = i - m_otherMatcher->m_first[j];
Chris@45 436
Chris@188 437 if (idx < 0 || size_t(idx) == m_otherMatcher->m_distance[j].size()) {
cannam@0 438 // This should never happen, but if we allow arbitrary
cannam@0 439 // pauses in either direction, and arbitrary lengths at
cannam@0 440 // end, it is better than a segmentation fault.
Chris@72 441 cerr << "Emergency resize: " << idx << " -> " << idx * 2 << endl;
Chris@183 442 m_otherMatcher->m_bestPathCost[j].resize(idx * 2, InvalidPathCost);
Chris@183 443 m_otherMatcher->m_distance[j].resize(idx * 2, InvalidDistance);
Chris@46 444 m_otherMatcher->m_advance[j].resize(idx * 2, AdvanceNone);
cannam@0 445 }
Chris@45 446
Chris@96 447 m_otherMatcher->setDistance(j, i, distance);
Chris@188 448 m_otherMatcher->setPathCost(j, i, dir, newValue);
cannam@0 449 }
Chris@71 450 }
cannam@0 451
Chris@181 452 advance_t
Chris@72 453 Matcher::getAdvance(int i, int j)
Chris@72 454 {
Chris@72 455 if (m_firstPM) {
Chris@72 456 if (!isInRange(i, j)) {
Chris@72 457 cerr << "ERROR: Matcher::getAdvance(" << i << ", " << j << "): "
Chris@72 458 << "Location is not in range" << endl;
Chris@72 459 throw "Advance not available";
Chris@72 460 }
Chris@72 461 return m_advance[i][j - m_first[i]];
Chris@72 462 } else {
Chris@72 463 return m_otherMatcher->getAdvance(j, i);
Chris@72 464 }
Chris@72 465 }