changeset 235:4d0e9225c179 subsequence

Add a full-DTW implementation for subsequence experiments
author Chris Cannam
date Thu, 02 Jul 2020 16:29:39 +0100
parents 4b272c839f7e
children 39fe8728e1ca
files Makefile.inc README src/FullDTW.cpp src/FullDTW.h src/MatchFeatureFeeder.cpp src/MatchFeatureFeeder.h src/MatchVampPlugin.cpp src/Matcher.h
diffstat 8 files changed, 270 insertions(+), 31 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile.inc	Fri Jun 10 13:56:05 2016 +0100
+++ b/Makefile.inc	Thu Jul 02 16:29:39 2020 +0100
@@ -41,14 +41,13 @@
 # DO NOT DELETE
 
 src/DistanceMetric.o: src/DistanceMetric.h src/MatchTypes.h
-src/Path.o: src/Path.h
 src/FeatureConditioner.o: src/FeatureConditioner.h src/MatchTypes.h
-src/MatchFeatureFeeder.o: src/MatchFeatureFeeder.h src/Matcher.h
-src/MatchFeatureFeeder.o: src/DistanceMetric.h src/MatchTypes.h src/Finder.h
 src/FeatureExtractor.o: src/FeatureExtractor.h src/MatchTypes.h
 src/Finder.o: src/Finder.h src/Matcher.h src/DistanceMetric.h
 src/Finder.o: src/MatchTypes.h src/Path.h
 src/Matcher.o: src/Matcher.h src/DistanceMetric.h src/MatchTypes.h
+src/MatchFeatureFeeder.o: src/MatchFeatureFeeder.h src/Matcher.h
+src/MatchFeatureFeeder.o: src/DistanceMetric.h src/MatchTypes.h src/Finder.h
 src/MatchPipeline.o: src/MatchPipeline.h src/Matcher.h src/DistanceMetric.h
 src/MatchPipeline.o: src/MatchTypes.h src/Finder.h src/FeatureExtractor.h
 src/MatchPipeline.o: src/FeatureConditioner.h src/MatchFeatureFeeder.h
@@ -57,19 +56,21 @@
 src/MatchVampPlugin.o: src/Finder.h src/FeatureExtractor.h
 src/MatchVampPlugin.o: src/FeatureConditioner.h src/MatchFeatureFeeder.h
 src/MatchVampPlugin.o: src/Path.h
+src/Path.o: src/Path.h
+src/DistanceMetric.o: src/MatchTypes.h
+src/FeatureConditioner.o: src/MatchTypes.h
+src/FeatureExtractor.o: src/MatchTypes.h
+src/Finder.o: src/Matcher.h src/DistanceMetric.h src/MatchTypes.h
+src/FullDTW.o: src/DistanceMetric.h src/MatchTypes.h
+src/Matcher.o: src/DistanceMetric.h src/MatchTypes.h
 src/MatchFeatureFeeder.o: src/Matcher.h src/DistanceMetric.h src/MatchTypes.h
 src/MatchFeatureFeeder.o: src/Finder.h
-src/FeatureExtractor.o: src/MatchTypes.h
-src/Finder.o: src/Matcher.h src/DistanceMetric.h src/MatchTypes.h
-src/Matcher.o: src/DistanceMetric.h src/MatchTypes.h
 src/MatchPipeline.o: src/Matcher.h src/DistanceMetric.h src/MatchTypes.h
 src/MatchPipeline.o: src/Finder.h src/FeatureExtractor.h
 src/MatchPipeline.o: src/FeatureConditioner.h src/MatchFeatureFeeder.h
 src/MatchVampPlugin.o: src/MatchPipeline.h src/Matcher.h src/DistanceMetric.h
 src/MatchVampPlugin.o: src/MatchTypes.h src/Finder.h src/FeatureExtractor.h
 src/MatchVampPlugin.o: src/FeatureConditioner.h src/MatchFeatureFeeder.h
-src/DistanceMetric.o: src/MatchTypes.h
-src/FeatureConditioner.o: src/MatchTypes.h
+test/TestDistanceMetric.o: src/DistanceMetric.h src/MatchTypes.h
 test/TestFeatureConditioner.o: src/FeatureConditioner.h src/MatchTypes.h
-test/TestDistanceMetric.o: src/DistanceMetric.h src/MatchTypes.h
 test/TestFeatureExtractor.o: src/FeatureExtractor.h src/MatchTypes.h
--- a/README	Fri Jun 10 13:56:05 2016 +0100
+++ b/README	Thu Jul 02 16:29:39 2020 +0100
@@ -5,15 +5,11 @@
 This is a Vamp plugin implementation of the MATCH audio alignment
 algorithm:
 
-  http://www.eecs.qmul.ac.uk/~simond/match/index.html
+  https://www.eecs.qmul.ac.uk/~simond/match/index.html
 
 See plugin homepage at
 
-  http://code.soundsoftware.ac.uk/projects/match-vamp/
-
-This is version 1.0 of the plugin, which includes a number of fixes,
-efficiency improvements, and extra options not present in the prior
-0.2.1 release.
+  https://code.soundsoftware.ac.uk/projects/match-vamp/
 
 Plugin code by Chris Cannam and Simon Dixon.
 
@@ -39,7 +35,7 @@
 
 (See the CITATION file for a BibTeX reference.)
 
-Copyright (c) 2007-2015 Simon Dixon, Chris Cannam, and Queen Mary
+Copyright (c) 2007-2020 Simon Dixon, Chris Cannam, and Queen Mary
 University of London, Copyright (c) 2014-2015 Tido GmbH.
 
 Distributed under the GNU General Public License. See the file COPYING
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/FullDTW.cpp	Thu Jul 02 16:29:39 2020 +0100
@@ -0,0 +1,141 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Vamp feature extraction plugin using the MATCH audio alignment
+    algorithm.
+
+    Centre for Digital Music, Queen Mary, University of London.
+    Copyright (c) 2007-2015 Simon Dixon, Chris Cannam, and Queen Mary
+    University of London, Copyright (c) 2014-2015 Tido GmbH.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "FullDTW.h"
+
+#include <stdexcept>
+
+FullDTW::FullDTW(Parameters params, DistanceMetric::Parameters dparams) :
+    m_params(params),
+    m_metric(dparams)
+{
+}
+
+pathcost_t
+FullDTW::choose(CostOption x, CostOption y, CostOption d) {
+    if (x.present && y.present) {
+        if (!d.present) {
+            throw std::logic_error("if x & y both exist, so must diagonal");
+        }
+        return std::min(std::min(x.cost, y.cost), d.cost);
+    } else if (x.present) {
+        return x.cost;
+    } else if (y.present) {
+        return y.cost;
+    } else {
+        return 0.0;
+    }
+}
+
+pathcostmat_t
+FullDTW::costSequences(const featureseq_t &s1, const featureseq_t &s2) {
+
+    pathcostmat_t costs(s1.size(), pathcostvec_t(s2.size(), 0));
+
+    for (size_t j = 0; j < s1.size(); ++j) {
+        for (size_t i = 0; i < s2.size(); ++i) {
+            distance_t dist = m_metric.calcDistance(s1[j], s2[i]);
+            if (i == 0 && m_params.subsequence) {
+                costs[j][i] = dist;
+            } else {
+                costs[j][i] = choose
+                    (
+                        { j > 0,
+                          j > 0 ? dist + costs[j-1][i] : 0
+                        },
+                        { i > 0,
+                          i > 0 ? dist + costs[j][i-1] : 0
+                        },
+                        { j > 0 && i > 0,
+                          j > 0 && i > 0 ? dist + costs[j-1][i-1] : 0
+                        });
+            }
+        }
+    }
+
+    return costs;
+}
+
+std::vector<size_t>
+FullDTW::align(const featureseq_t &s1, const featureseq_t &s2) {
+
+    // Return the index into s1 for each element in s2
+        
+    std::vector<size_t> alignment(s2.size(), 0);
+
+    if (s1.empty() || s2.empty()) {
+        return alignment;
+    }
+
+    auto costs = costSequences(s1, s2);
+
+#ifdef DEBUG_DTW
+    SVCERR << "Cost matrix:" << endl;
+    for (auto v: cost) {
+        for (auto x: v) {
+            SVCERR << x << " ";
+        }
+        SVCERR << "\n";
+    }
+#endif
+
+    size_t j = s1.size() - 1;
+    size_t i = s2.size() - 1;
+
+    if (m_params.subsequence) {
+        pathcost_t min = 0.0;
+        size_t minidx = 0;
+        for (size_t j = 0; j < s1.size(); ++j) {
+            if (j == 0 || costs[j][i] < min) {
+                min = costs[j][i];
+                minidx = j;
+            }
+        }
+        j = minidx;
+#ifdef DEBUG_DTW
+        SVCERR << "Lowest cost at end of subsequence = " << min
+               << " at index " << j << ", tracking back from there" << endl;
+#endif
+    }
+        
+    while (i > 0 && (j > 0 || m_params.subsequence)) {
+
+        alignment[i] = j;
+            
+        pathcost_t a = costs[j-1][i];
+        pathcost_t b = costs[j][i-1];
+        pathcost_t both = costs[j-1][i-1];
+
+        if (a < b) {
+            --j;
+            if (both <= a) {
+                --i;
+            }
+        } else {
+            --i;
+            if (both <= b) {
+                --j;
+            }
+        }
+    }
+
+    if (m_params.subsequence) {
+        alignment[0] = j;
+    }
+        
+    return alignment;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/FullDTW.h	Thu Jul 02 16:29:39 2020 +0100
@@ -0,0 +1,102 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Vamp feature extraction plugin using the MATCH audio alignment
+    algorithm.
+
+    Centre for Digital Music, Queen Mary, University of London.
+    Copyright (c) 2007-2020 Simon Dixon, Chris Cannam, and Queen Mary
+    University of London, Copyright (c) 2014-2015 Tido GmbH.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef FULL_DTW_H
+#define FULL_DTW_H
+
+#include <vector>
+#include <functional>
+
+#include "DistanceMetric.h"
+#include "MatchTypes.h"
+
+/** Represents a full-matrix dynamic time warping aligner. Unlike the
+ *  combination of Matcher and Finder, which implement a "live" online
+ *  DTW that stores only a part of the matrix, FullDTW works offline,
+ *  requiring all features up-front and storing the whole cost
+ *  matrix. This is far simpler than Matcher/Finder, can calculate
+ *  subsequence matches, and always returns the optimal path, but it
+ *  is also much more expensive - O(mn) rather than O(m+n) in both
+ *  time and space.
+ */
+class FullDTW
+{
+public:
+    struct Parameters {
+
+        Parameters(double hopTime_) :
+            hopTime(hopTime_),
+            diagonalWeight(1.0),
+            subsequence(false)
+        {}
+
+        /** Spacing of audio frames (determines the amount of overlap
+         *  or skip between frames). This value is expressed in
+         *  seconds.
+         */
+        double hopTime;
+
+        /** Weight applied to cost of diagonal step relative to
+         *  horizontal or vertical step. The default of 1.0 is normal
+         *  for cases that are not expected to differ wildly in speed.
+         */
+        double diagonalWeight;
+
+        /** Whether this is a subsequence matcher. The FullDTW aligns
+         *  two sequences, and by default it assumes that they are
+         *  anchored to one another at both ends (i.e. they both span
+         *  the same material). If subsequence is true, then it
+         *  instead assumes that the second sequence is intended to
+         *  match some subsequence of the first, and it returns a
+         *  match against the best-matching subsequence rather than
+         *  the whole of the first sequence.
+         */
+        bool subsequence;
+    };
+    
+    /** Constructor for FullDTW.
+     * 
+     *  A FullDTW expects to be provided with two sequences of feature
+     *  vectors calculated by some external code (for example, a
+     *  FeatureExtractor) which it will align.
+     */
+    FullDTW(Parameters params, DistanceMetric::Parameters dparams);
+
+    /**
+     * Align the sequence s2 against the sequence s1, returning the
+     * index into s1 for each element in s2. If the subsequence
+     * parameter was set on construction, then the alignment is
+     * against the best-matching subsequence of s1; otherwise it is
+     * against the whole of s1.
+     */
+    std::vector<size_t> align(const featureseq_t &s1,
+                              const featureseq_t &s2);
+
+private:
+    Parameters m_params;
+    DistanceMetric m_metric;
+
+    struct CostOption {
+        bool present;
+        pathcost_t cost;
+    };
+
+    pathcost_t choose(CostOption x, CostOption y, CostOption d);
+    pathcostmat_t costSequences(const featureseq_t &s1, const featureseq_t &s2);
+};
+
+#endif
--- a/src/MatchFeatureFeeder.cpp	Fri Jun 10 13:56:05 2016 +0100
+++ b/src/MatchFeatureFeeder.cpp	Thu Jul 02 16:29:39 2020 +0100
@@ -18,6 +18,8 @@
 #include "MatchFeatureFeeder.h"
 
 using std::vector;
+using std::cerr;
+using std::endl;
 
 MatchFeatureFeeder::MatchFeatureFeeder(Matcher *m1, Matcher *m2) :
     m_pm1(m1),
--- a/src/MatchFeatureFeeder.h	Fri Jun 10 13:56:05 2016 +0100
+++ b/src/MatchFeatureFeeder.h	Thu Jul 02 16:29:39 2020 +0100
@@ -14,8 +14,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _MATCH_FEATURE_FEEDER_H_
-#define _MATCH_FEATURE_FEEDER_H_
+#ifndef MATCH_FEATURE_FEEDER_H
+#define MATCH_FEATURE_FEEDER_H
 
 #include "Matcher.h"
 #include "Finder.h"
@@ -82,8 +82,8 @@
     std::queue<feature_t> m_q1;
     std::queue<feature_t> m_q2;
 
-    vector<int> m_fpx;
-    vector<int> m_fpy;
+    std::vector<int> m_fpx;
+    std::vector<int> m_fpy;
 
     // not provided:
     MatchFeatureFeeder(const MatchFeatureFeeder &other);
--- a/src/MatchVampPlugin.cpp	Fri Jun 10 13:56:05 2016 +0100
+++ b/src/MatchVampPlugin.cpp	Thu Jul 02 16:29:39 2020 +0100
@@ -29,6 +29,8 @@
 #include <vector>
 #include <algorithm>
 
+using std::string;
+
 //static int extant = 0;
 
 #ifdef _WIN32
--- a/src/Matcher.h	Fri Jun 10 13:56:05 2016 +0100
+++ b/src/Matcher.h	Thu Jul 02 16:29:39 2020 +0100
@@ -26,11 +26,6 @@
 #include "DistanceMetric.h"
 #include "MatchTypes.h"
 
-using std::vector;
-using std::string;
-using std::cerr;
-using std::endl;
-
 /** Represents an audio feature stream that can be matched to another
  *  audio stream of the same piece of music.  The matching algorithm
  *  uses dynamic time warping.
@@ -38,7 +33,7 @@
 class Matcher
 {
 public:
-    static string advanceToString(advance_t a) {
+    static std::string advanceToString(advance_t a) {
         switch (a) {
         case AdvanceNone: return "AdvanceNone";
         case AdvanceBoth: return "AdvanceBoth";
@@ -57,8 +52,8 @@
             diagonalWeight(2.0)
         {}
 
-        /** Spacing of audio frames (determines the amount of overlap or
-         *  skip between frames). This value is expressed in
+        /** Spacing of audio frames (determines the amount of overlap
+         *  or skip between frames). This value is expressed in
          *  seconds.
          */
         double hopTime;
@@ -341,9 +336,9 @@
 
     /** The bounds of each row of data in the distance, path cost, and
      * advance direction matrices.*/
-    vector<int> m_first;
-    vector<int> m_last;
-
+    std::vector<int> m_first;
+    std::vector<int> m_last;
+    
     /** Width of distance, path cost, and advance direction matrices
      * and first and last vectors */
     int m_distXSize;