annotate align/TransformDTWAligner.cpp @ 771:1d6cca5a5621 pitch-align

Allow use of proper sparse models (i.e. retaining event time info) in alignment; use this to switch to note alignment, which is what we have most recently been doing in the external program. Not currently producing correct results, though
author Chris Cannam
date Fri, 29 May 2020 17:39:02 +0100
parents a316cb6fed81
children 699b5b130ea2
rev   line source
Chris@767 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@767 2
Chris@767 3 /*
Chris@767 4 Sonic Visualiser
Chris@767 5 An audio file viewer and annotation editor.
Chris@767 6 Centre for Digital Music, Queen Mary, University of London.
Chris@767 7
Chris@767 8 This program is free software; you can redistribute it and/or
Chris@767 9 modify it under the terms of the GNU General Public License as
Chris@767 10 published by the Free Software Foundation; either version 2 of the
Chris@767 11 License, or (at your option) any later version. See the file
Chris@767 12 COPYING included with this distribution for more information.
Chris@767 13 */
Chris@767 14
Chris@767 15 #include "TransformDTWAligner.h"
Chris@767 16 #include "DTW.h"
Chris@767 17
Chris@767 18 #include "data/model/SparseTimeValueModel.h"
Chris@771 19 #include "data/model/NoteModel.h"
Chris@767 20 #include "data/model/RangeSummarisableTimeValueModel.h"
Chris@767 21 #include "data/model/AlignmentModel.h"
Chris@767 22 #include "data/model/AggregateWaveModel.h"
Chris@767 23
Chris@767 24 #include "framework/Document.h"
Chris@767 25
Chris@767 26 #include "transform/ModelTransformerFactory.h"
Chris@767 27 #include "transform/FeatureExtractionModelTransformer.h"
Chris@767 28
Chris@767 29 #include <QSettings>
Chris@767 30 #include <QMutex>
Chris@767 31 #include <QMutexLocker>
Chris@767 32
Chris@767 33 using std::vector;
Chris@767 34
Chris@771 35 static
Chris@771 36 TransformDTWAligner::MagnitudePreprocessor identityMagnitudePreprocessor =
Chris@771 37 [](double x) {
Chris@771 38 return x;
Chris@771 39 };
Chris@771 40
Chris@771 41 static
Chris@771 42 TransformDTWAligner::RiseFallPreprocessor identityRiseFallPreprocessor =
Chris@771 43 [](double prev, double curr) {
Chris@771 44 if (prev == curr) {
Chris@771 45 return RiseFallDTW::Value({ RiseFallDTW::Direction::None, 0.0 });
Chris@771 46 } else if (curr > prev) {
Chris@771 47 return RiseFallDTW::Value({ RiseFallDTW::Direction::Up, curr - prev });
Chris@771 48 } else {
Chris@771 49 return RiseFallDTW::Value({ RiseFallDTW::Direction::Down, prev - curr });
Chris@771 50 }
Chris@771 51 };
Chris@771 52
Chris@771 53 QMutex
Chris@771 54 TransformDTWAligner::m_dtwMutex;
Chris@771 55
Chris@767 56 TransformDTWAligner::TransformDTWAligner(Document *doc,
Chris@767 57 ModelId reference,
Chris@767 58 ModelId toAlign,
Chris@767 59 Transform transform,
Chris@767 60 DTWType dtwType) :
Chris@767 61 m_document(doc),
Chris@767 62 m_reference(reference),
Chris@767 63 m_toAlign(toAlign),
Chris@767 64 m_transform(transform),
Chris@767 65 m_dtwType(dtwType),
Chris@768 66 m_incomplete(true),
Chris@771 67 m_magnitudePreprocessor(identityMagnitudePreprocessor),
Chris@771 68 m_riseFallPreprocessor(identityRiseFallPreprocessor)
Chris@768 69 {
Chris@768 70 }
Chris@768 71
Chris@768 72 TransformDTWAligner::TransformDTWAligner(Document *doc,
Chris@768 73 ModelId reference,
Chris@768 74 ModelId toAlign,
Chris@768 75 Transform transform,
Chris@771 76 MagnitudePreprocessor outputPreprocessor) :
Chris@768 77 m_document(doc),
Chris@768 78 m_reference(reference),
Chris@768 79 m_toAlign(toAlign),
Chris@768 80 m_transform(transform),
Chris@771 81 m_dtwType(Magnitude),
Chris@768 82 m_incomplete(true),
Chris@771 83 m_magnitudePreprocessor(outputPreprocessor),
Chris@771 84 m_riseFallPreprocessor(identityRiseFallPreprocessor)
Chris@771 85 {
Chris@771 86 }
Chris@771 87
Chris@771 88 TransformDTWAligner::TransformDTWAligner(Document *doc,
Chris@771 89 ModelId reference,
Chris@771 90 ModelId toAlign,
Chris@771 91 Transform transform,
Chris@771 92 RiseFallPreprocessor outputPreprocessor) :
Chris@771 93 m_document(doc),
Chris@771 94 m_reference(reference),
Chris@771 95 m_toAlign(toAlign),
Chris@771 96 m_transform(transform),
Chris@771 97 m_dtwType(RiseFall),
Chris@771 98 m_incomplete(true),
Chris@771 99 m_magnitudePreprocessor(identityMagnitudePreprocessor),
Chris@771 100 m_riseFallPreprocessor(outputPreprocessor)
Chris@767 101 {
Chris@767 102 }
Chris@767 103
Chris@767 104 TransformDTWAligner::~TransformDTWAligner()
Chris@767 105 {
Chris@767 106 if (m_incomplete) {
Chris@767 107 if (auto toAlign = ModelById::get(m_toAlign)) {
Chris@767 108 toAlign->setAlignment({});
Chris@767 109 }
Chris@767 110 }
Chris@767 111
Chris@767 112 ModelById::release(m_referenceOutputModel);
Chris@767 113 ModelById::release(m_toAlignOutputModel);
Chris@767 114 }
Chris@767 115
Chris@767 116 bool
Chris@767 117 TransformDTWAligner::isAvailable()
Chris@767 118 {
Chris@767 119 //!!! needs to be isAvailable(QString transformId)?
Chris@767 120 return true;
Chris@767 121 }
Chris@767 122
Chris@767 123 void
Chris@767 124 TransformDTWAligner::begin()
Chris@767 125 {
Chris@767 126 auto reference =
Chris@767 127 ModelById::getAs<RangeSummarisableTimeValueModel>(m_reference);
Chris@767 128 auto toAlign =
Chris@767 129 ModelById::getAs<RangeSummarisableTimeValueModel>(m_toAlign);
Chris@767 130
Chris@767 131 if (!reference || !toAlign) return;
Chris@767 132
Chris@767 133 SVCERR << "TransformDTWAligner[" << this << "]: begin(): aligning "
Chris@767 134 << m_toAlign << " against reference " << m_reference << endl;
Chris@767 135
Chris@767 136 ModelTransformerFactory *mtf = ModelTransformerFactory::getInstance();
Chris@767 137
Chris@767 138 QString message;
Chris@767 139
Chris@767 140 m_referenceOutputModel = mtf->transform(m_transform, m_reference, message);
Chris@767 141 auto referenceOutputModel = ModelById::get(m_referenceOutputModel);
Chris@767 142 if (!referenceOutputModel) {
Chris@767 143 SVCERR << "Align::alignModel: ERROR: Failed to create reference output model (no plugin?)" << endl;
Chris@767 144 emit failed(m_toAlign, message);
Chris@767 145 return;
Chris@767 146 }
Chris@767 147
Chris@767 148 SVCERR << "TransformDTWAligner[" << this << "]: begin(): transform id "
Chris@767 149 << m_transform.getIdentifier()
Chris@767 150 << " is running on reference model" << endl;
Chris@767 151
Chris@767 152 message = "";
Chris@767 153
Chris@767 154 m_toAlignOutputModel = mtf->transform(m_transform, m_toAlign, message);
Chris@767 155 auto toAlignOutputModel = ModelById::get(m_toAlignOutputModel);
Chris@767 156 if (!toAlignOutputModel) {
Chris@767 157 SVCERR << "Align::alignModel: ERROR: Failed to create toAlign output model (no plugin?)" << endl;
Chris@767 158 emit failed(m_toAlign, message);
Chris@767 159 return;
Chris@767 160 }
Chris@767 161
Chris@767 162 SVCERR << "TransformDTWAligner[" << this << "]: begin(): transform id "
Chris@767 163 << m_transform.getIdentifier()
Chris@767 164 << " is running on toAlign model" << endl;
Chris@767 165
Chris@767 166 connect(referenceOutputModel.get(), SIGNAL(completionChanged(ModelId)),
Chris@767 167 this, SLOT(completionChanged(ModelId)));
Chris@767 168 connect(toAlignOutputModel.get(), SIGNAL(completionChanged(ModelId)),
Chris@767 169 this, SLOT(completionChanged(ModelId)));
Chris@767 170
Chris@767 171 auto alignmentModel = std::make_shared<AlignmentModel>
Chris@768 172 (m_reference, m_toAlign, ModelId());
Chris@767 173 m_alignmentModel = ModelById::add(alignmentModel);
Chris@767 174
Chris@767 175 toAlign->setAlignment(m_alignmentModel);
Chris@767 176 m_document->addNonDerivedModel(m_alignmentModel);
Chris@767 177
Chris@767 178 // we wouldn't normally expect these to be true here, but...
Chris@767 179 int completion = 0;
Chris@767 180 if (referenceOutputModel->isReady(&completion) &&
Chris@767 181 toAlignOutputModel->isReady(&completion)) {
Chris@767 182 SVCERR << "TransformDTWAligner[" << this << "]: begin(): output models "
Chris@767 183 << "are ready already! calling performAlignment" << endl;
Chris@767 184 if (performAlignment()) {
Chris@767 185 emit complete(m_alignmentModel);
Chris@767 186 } else {
Chris@767 187 emit failed(m_toAlign, tr("Failed to calculate alignment using DTW"));
Chris@767 188 }
Chris@767 189 }
Chris@767 190 }
Chris@767 191
Chris@767 192 void
Chris@767 193 TransformDTWAligner::completionChanged(ModelId id)
Chris@767 194 {
Chris@767 195 if (!m_incomplete) {
Chris@767 196 return;
Chris@767 197 }
Chris@771 198 /*
Chris@767 199 SVCERR << "TransformDTWAligner[" << this << "]: completionChanged: "
Chris@767 200 << "model " << id << endl;
Chris@771 201 */
Chris@767 202 auto referenceOutputModel = ModelById::get(m_referenceOutputModel);
Chris@767 203 auto toAlignOutputModel = ModelById::get(m_toAlignOutputModel);
Chris@768 204 auto alignmentModel = ModelById::getAs<AlignmentModel>(m_alignmentModel);
Chris@767 205
Chris@768 206 if (!referenceOutputModel || !toAlignOutputModel || !alignmentModel) {
Chris@767 207 return;
Chris@767 208 }
Chris@767 209
Chris@767 210 int referenceCompletion = 0, toAlignCompletion = 0;
Chris@767 211 bool referenceReady = referenceOutputModel->isReady(&referenceCompletion);
Chris@767 212 bool toAlignReady = toAlignOutputModel->isReady(&toAlignCompletion);
Chris@767 213
Chris@767 214 if (referenceReady && toAlignReady) {
Chris@767 215
Chris@767 216 SVCERR << "TransformDTWAligner[" << this << "]: completionChanged: "
Chris@771 217 << "both models ready, calling performAlignment" << endl;
Chris@767 218
Chris@768 219 alignmentModel->setCompletion(95);
Chris@767 220
Chris@767 221 if (performAlignment()) {
Chris@767 222 emit complete(m_alignmentModel);
Chris@767 223 } else {
Chris@767 224 emit failed(m_toAlign, tr("Alignment of transform outputs failed"));
Chris@767 225 }
Chris@767 226
Chris@767 227 } else {
Chris@771 228 /*
Chris@767 229 SVCERR << "TransformDTWAligner[" << this << "]: completionChanged: "
Chris@767 230 << "not ready yet: reference completion " << referenceCompletion
Chris@767 231 << ", toAlign completion " << toAlignCompletion << endl;
Chris@771 232 */
Chris@768 233 int completion = std::min(referenceCompletion,
Chris@768 234 toAlignCompletion);
Chris@768 235 completion = (completion * 94) / 100;
Chris@768 236 alignmentModel->setCompletion(completion);
Chris@767 237 }
Chris@767 238 }
Chris@767 239
Chris@767 240 bool
Chris@767 241 TransformDTWAligner::performAlignment()
Chris@767 242 {
Chris@767 243 if (m_dtwType == Magnitude) {
Chris@767 244 return performAlignmentMagnitude();
Chris@767 245 } else {
Chris@767 246 return performAlignmentRiseFall();
Chris@767 247 }
Chris@767 248 }
Chris@767 249
Chris@767 250 bool
Chris@771 251 TransformDTWAligner::getValuesFrom(ModelId modelId,
Chris@771 252 vector<sv_frame_t> &frames,
Chris@771 253 vector<double> &values,
Chris@771 254 sv_frame_t &resolution)
Chris@767 255 {
Chris@771 256 EventVector events;
Chris@767 257
Chris@771 258 if (auto model = ModelById::getAs<SparseTimeValueModel>(modelId)) {
Chris@771 259 resolution = model->getResolution();
Chris@771 260 events = model->getAllEvents();
Chris@771 261 } else if (auto model = ModelById::getAs<NoteModel>(modelId)) {
Chris@771 262 resolution = model->getResolution();
Chris@771 263 events = model->getAllEvents();
Chris@771 264 } else {
Chris@771 265 SVCERR << "TransformDTWAligner::getValuesFrom: Type of model "
Chris@771 266 << modelId << " is not supported" << endl;
Chris@767 267 return false;
Chris@767 268 }
Chris@767 269
Chris@771 270 frames.clear();
Chris@771 271 values.clear();
Chris@771 272
Chris@771 273 for (auto e: events) {
Chris@771 274 frames.push_back(e.getFrame());
Chris@771 275 values.push_back(e.getValue());
Chris@771 276 }
Chris@771 277
Chris@771 278 return true;
Chris@771 279 }
Chris@771 280
Chris@771 281 Path
Chris@771 282 TransformDTWAligner::makePath(const vector<size_t> &alignment,
Chris@771 283 const vector<sv_frame_t> &refFrames,
Chris@771 284 const vector<sv_frame_t> &otherFrames,
Chris@771 285 sv_samplerate_t sampleRate,
Chris@771 286 sv_frame_t resolution)
Chris@771 287 {
Chris@771 288 Path path(sampleRate, resolution);
Chris@771 289
Chris@771 290 for (int i = 0; in_range_for(alignment, i); ++i) {
Chris@771 291
Chris@771 292 // DTW returns "the index into s2 for each element in s1"
Chris@771 293 sv_frame_t refFrame = refFrames[i];
Chris@771 294
Chris@771 295 if (!in_range_for(otherFrames, alignment[i])) {
Chris@771 296 SVCERR << "TransformDTWAligner::makePath: Internal error: "
Chris@771 297 << "DTW maps index " << i << " in reference frame vector "
Chris@771 298 << "(size " << refFrames.size() << ") onto index "
Chris@771 299 << alignment[i] << " in other frame vector "
Chris@771 300 << "(only size " << otherFrames.size() << ")" << endl;
Chris@771 301 continue;
Chris@771 302 }
Chris@771 303
Chris@771 304 sv_frame_t alignedFrame = otherFrames[alignment[i]];
Chris@771 305 path.add(PathPoint(alignedFrame, refFrame));
Chris@771 306 }
Chris@771 307
Chris@771 308 return path;
Chris@771 309 }
Chris@771 310
Chris@771 311 bool
Chris@771 312 TransformDTWAligner::performAlignmentMagnitude()
Chris@771 313 {
Chris@771 314 auto alignmentModel = ModelById::getAs<AlignmentModel>(m_alignmentModel);
Chris@767 315 if (!alignmentModel) {
Chris@767 316 return false;
Chris@767 317 }
Chris@771 318
Chris@771 319 vector<sv_frame_t> refFrames, otherFrames;
Chris@771 320 vector<double> refValues, otherValues;
Chris@771 321 sv_frame_t resolution = 0;
Chris@771 322
Chris@771 323 if (!getValuesFrom(m_referenceOutputModel,
Chris@771 324 refFrames, refValues, resolution)) {
Chris@771 325 return false;
Chris@771 326 }
Chris@771 327
Chris@771 328 if (!getValuesFrom(m_toAlignOutputModel,
Chris@771 329 otherFrames, otherValues, resolution)) {
Chris@771 330 return false;
Chris@771 331 }
Chris@767 332
Chris@767 333 vector<double> s1, s2;
Chris@771 334 for (double v: refValues) {
Chris@771 335 s1.push_back(m_magnitudePreprocessor(v));
Chris@771 336 }
Chris@771 337 for (double v: otherValues) {
Chris@771 338 s2.push_back(m_magnitudePreprocessor(v));
Chris@767 339 }
Chris@767 340
Chris@769 341 SVCERR << "TransformDTWAligner[" << this << "]: performAlignmentMagnitude: "
Chris@767 342 << "Have " << s1.size() << " events from reference, "
Chris@767 343 << s2.size() << " from toAlign" << endl;
Chris@771 344
Chris@767 345 MagnitudeDTW dtw;
Chris@767 346 vector<size_t> alignment;
Chris@767 347
Chris@767 348 {
Chris@767 349 SVCERR << "TransformDTWAligner[" << this
Chris@767 350 << "]: serialising DTW to avoid over-allocation" << endl;
Chris@771 351 QMutexLocker locker(&m_dtwMutex);
Chris@767 352 alignment = dtw.alignSeries(s1, s2);
Chris@767 353 }
Chris@767 354
Chris@769 355 SVCERR << "TransformDTWAligner[" << this << "]: performAlignmentMagnitude: "
Chris@767 356 << "DTW produced " << alignment.size() << " points:" << endl;
Chris@771 357 for (int i = 0; in_range_for(alignment, i) && i < 100; ++i) {
Chris@767 358 SVCERR << alignment[i] << " ";
Chris@767 359 }
Chris@767 360 SVCERR << endl;
Chris@767 361
Chris@771 362 alignmentModel->setPath(makePath(alignment,
Chris@771 363 refFrames,
Chris@771 364 otherFrames,
Chris@771 365 alignmentModel->getSampleRate(),
Chris@771 366 resolution));
Chris@768 367 alignmentModel->setCompletion(100);
Chris@767 368
Chris@771 369 SVCERR << "TransformDTWAligner[" << this
Chris@771 370 << "]: performAlignmentMagnitude: Done" << endl;
Chris@767 371
Chris@767 372 m_incomplete = false;
Chris@767 373 return true;
Chris@767 374 }
Chris@767 375
Chris@767 376 bool
Chris@767 377 TransformDTWAligner::performAlignmentRiseFall()
Chris@767 378 {
Chris@771 379 auto alignmentModel = ModelById::getAs<AlignmentModel>(m_alignmentModel);
Chris@767 380 if (!alignmentModel) {
Chris@767 381 return false;
Chris@767 382 }
Chris@768 383
Chris@771 384 vector<sv_frame_t> refFrames, otherFrames;
Chris@771 385 vector<double> refValues, otherValues;
Chris@771 386 sv_frame_t resolution = 0;
Chris@771 387
Chris@771 388 if (!getValuesFrom(m_referenceOutputModel,
Chris@771 389 refFrames, refValues, resolution)) {
Chris@771 390 return false;
Chris@771 391 }
Chris@771 392
Chris@771 393 if (!getValuesFrom(m_toAlignOutputModel,
Chris@771 394 otherFrames, otherValues, resolution)) {
Chris@771 395 return false;
Chris@771 396 }
Chris@771 397
Chris@771 398 auto preprocess =
Chris@771 399 [this](const std::vector<double> &vv) {
Chris@768 400 vector<RiseFallDTW::Value> s;
Chris@768 401 double prev = 0.0;
Chris@771 402 for (auto curr: vv) {
Chris@771 403 s.push_back(m_riseFallPreprocessor(prev, curr));
Chris@771 404 prev = curr;
Chris@768 405 }
Chris@768 406 return s;
Chris@771 407 };
Chris@767 408
Chris@771 409 vector<RiseFallDTW::Value> s1 = preprocess(refValues);
Chris@771 410 vector<RiseFallDTW::Value> s2 = preprocess(otherValues);
Chris@767 411
Chris@769 412 SVCERR << "TransformDTWAligner[" << this << "]: performAlignmentRiseFall: "
Chris@767 413 << "Have " << s1.size() << " events from reference, "
Chris@767 414 << s2.size() << " from toAlign" << endl;
Chris@767 415
Chris@771 416 SVCERR << "Reference:" << endl;
Chris@771 417 for (int i = 0; in_range_for(s1, i) && i < 100; ++i) {
Chris@771 418 SVCERR << s1[i] << " ";
Chris@771 419 }
Chris@771 420 SVCERR << endl;
Chris@771 421
Chris@771 422 SVCERR << "toAlign:" << endl;
Chris@771 423 for (int i = 0; in_range_for(s2, i) && i < 100; ++i) {
Chris@771 424 SVCERR << s2[i] << " ";
Chris@771 425 }
Chris@771 426 SVCERR << endl;
Chris@771 427
Chris@767 428 RiseFallDTW dtw;
Chris@767 429 vector<size_t> alignment;
Chris@767 430
Chris@767 431 {
Chris@767 432 SVCERR << "TransformDTWAligner[" << this
Chris@767 433 << "]: serialising DTW to avoid over-allocation" << endl;
Chris@771 434 QMutexLocker locker(&m_dtwMutex);
Chris@767 435 alignment = dtw.alignSeries(s1, s2);
Chris@767 436 }
Chris@767 437
Chris@769 438 SVCERR << "TransformDTWAligner[" << this << "]: performAlignmentRiseFall: "
Chris@767 439 << "DTW produced " << alignment.size() << " points:" << endl;
Chris@767 440 for (int i = 0; i < alignment.size() && i < 100; ++i) {
Chris@767 441 SVCERR << alignment[i] << " ";
Chris@767 442 }
Chris@767 443 SVCERR << endl;
Chris@767 444
Chris@771 445 alignmentModel->setPath(makePath(alignment,
Chris@771 446 refFrames,
Chris@771 447 otherFrames,
Chris@771 448 alignmentModel->getSampleRate(),
Chris@771 449 resolution));
Chris@771 450
Chris@768 451 alignmentModel->setCompletion(100);
Chris@767 452
Chris@769 453 SVCERR << "TransformDTWAligner[" << this << "]: performAlignmentRiseFall: Done"
Chris@767 454 << endl;
Chris@767 455
Chris@767 456 m_incomplete = false;
Chris@767 457 return true;
Chris@767 458 }