Chris@235
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@235
|
2
|
Chris@235
|
3 /*
|
Chris@235
|
4 Vamp feature extraction plugin using the MATCH audio alignment
|
Chris@235
|
5 algorithm.
|
Chris@235
|
6
|
Chris@235
|
7 Centre for Digital Music, Queen Mary, University of London.
|
Chris@236
|
8 Copyright (c) 2007-2020 Simon Dixon, Chris Cannam, and Queen Mary
|
Chris@235
|
9 University of London, Copyright (c) 2014-2015 Tido GmbH.
|
Chris@235
|
10
|
Chris@235
|
11 This program is free software; you can redistribute it and/or
|
Chris@235
|
12 modify it under the terms of the GNU General Public License as
|
Chris@235
|
13 published by the Free Software Foundation; either version 2 of the
|
Chris@235
|
14 License, or (at your option) any later version. See the file
|
Chris@235
|
15 COPYING included with this distribution for more information.
|
Chris@235
|
16 */
|
Chris@235
|
17
|
Chris@235
|
18 #include "FullDTW.h"
|
Chris@235
|
19
|
Chris@235
|
20 #include <stdexcept>
|
Chris@238
|
21 #include <algorithm>
|
Chris@242
|
22 #include <iostream>
|
Chris@242
|
23 #include <cmath>
|
Chris@242
|
24
|
Chris@242
|
25 //#define DEBUG_DTW 1
|
Chris@235
|
26
|
Chris@235
|
27 FullDTW::FullDTW(Parameters params, DistanceMetric::Parameters dparams) :
|
Chris@235
|
28 m_params(params),
|
Chris@235
|
29 m_metric(dparams)
|
Chris@235
|
30 {
|
Chris@235
|
31 }
|
Chris@235
|
32
|
Chris@235
|
33 pathcost_t
|
Chris@235
|
34 FullDTW::choose(CostOption x, CostOption y, CostOption d) {
|
Chris@235
|
35 if (x.present && y.present) {
|
Chris@235
|
36 if (!d.present) {
|
Chris@235
|
37 throw std::logic_error("if x & y both exist, so must diagonal");
|
Chris@235
|
38 }
|
Chris@235
|
39 return std::min(std::min(x.cost, y.cost), d.cost);
|
Chris@235
|
40 } else if (x.present) {
|
Chris@235
|
41 return x.cost;
|
Chris@235
|
42 } else if (y.present) {
|
Chris@235
|
43 return y.cost;
|
Chris@235
|
44 } else {
|
Chris@235
|
45 return 0.0;
|
Chris@235
|
46 }
|
Chris@235
|
47 }
|
Chris@235
|
48
|
Chris@235
|
49 pathcostmat_t
|
Chris@235
|
50 FullDTW::costSequences(const featureseq_t &s1, const featureseq_t &s2) {
|
Chris@235
|
51
|
Chris@235
|
52 pathcostmat_t costs(s1.size(), pathcostvec_t(s2.size(), 0));
|
Chris@235
|
53
|
Chris@242
|
54 #ifdef DEBUG_DTW
|
Chris@242
|
55 std::cerr << "Distance matrix:" << std::endl;
|
Chris@242
|
56 #endif
|
Chris@242
|
57
|
Chris@235
|
58 for (size_t j = 0; j < s1.size(); ++j) {
|
Chris@235
|
59 for (size_t i = 0; i < s2.size(); ++i) {
|
Chris@235
|
60 distance_t dist = m_metric.calcDistance(s1[j], s2[i]);
|
Chris@242
|
61 #ifdef DEBUG_DTW
|
Chris@244
|
62 std::cerr << distance_print_t(dist) << " ";
|
Chris@242
|
63 #endif
|
Chris@242
|
64
|
Chris@235
|
65 if (i == 0 && m_params.subsequence) {
|
Chris@235
|
66 costs[j][i] = dist;
|
Chris@235
|
67 } else {
|
Chris@235
|
68 costs[j][i] = choose
|
Chris@235
|
69 (
|
Chris@235
|
70 { j > 0,
|
Chris@242
|
71 j > 0 ?
|
Chris@242
|
72 (dist + costs[j-1][i])
|
Chris@242
|
73 : 0
|
Chris@235
|
74 },
|
Chris@235
|
75 { i > 0,
|
Chris@242
|
76 i > 0 ?
|
Chris@242
|
77 (dist + costs[j][i-1])
|
Chris@242
|
78 : 0
|
Chris@235
|
79 },
|
Chris@242
|
80 { (j > 0 && i > 0),
|
Chris@242
|
81 (j > 0 && i > 0) ?
|
Chris@242
|
82 pathcost_t(round(m_params.diagonalWeight * dist)
|
Chris@242
|
83 + costs[j-1][i-1])
|
Chris@242
|
84 : 0
|
Chris@235
|
85 });
|
Chris@235
|
86 }
|
Chris@235
|
87 }
|
Chris@242
|
88 #ifdef DEBUG_DTW
|
Chris@242
|
89 std::cerr << "\n";
|
Chris@242
|
90 #endif
|
Chris@235
|
91 }
|
Chris@235
|
92
|
Chris@235
|
93 return costs;
|
Chris@235
|
94 }
|
Chris@235
|
95
|
Chris@235
|
96 std::vector<size_t>
|
Chris@235
|
97 FullDTW::align(const featureseq_t &s1, const featureseq_t &s2) {
|
Chris@235
|
98
|
Chris@235
|
99 // Return the index into s1 for each element in s2
|
Chris@235
|
100
|
Chris@235
|
101 std::vector<size_t> alignment(s2.size(), 0);
|
Chris@235
|
102
|
Chris@235
|
103 if (s1.empty() || s2.empty()) {
|
Chris@235
|
104 return alignment;
|
Chris@235
|
105 }
|
Chris@235
|
106
|
Chris@235
|
107 auto costs = costSequences(s1, s2);
|
Chris@235
|
108
|
Chris@235
|
109 #ifdef DEBUG_DTW
|
Chris@242
|
110 std::cerr << "Path cost matrix:" << std::endl;
|
Chris@242
|
111 for (auto v: costs) {
|
Chris@235
|
112 for (auto x: v) {
|
Chris@242
|
113 std::cerr << x << " ";
|
Chris@235
|
114 }
|
Chris@242
|
115 std::cerr << "\n";
|
Chris@235
|
116 }
|
Chris@235
|
117 #endif
|
Chris@235
|
118
|
Chris@235
|
119 size_t j = s1.size() - 1;
|
Chris@235
|
120 size_t i = s2.size() - 1;
|
Chris@235
|
121
|
Chris@235
|
122 if (m_params.subsequence) {
|
Chris@235
|
123 pathcost_t min = 0.0;
|
Chris@235
|
124 size_t minidx = 0;
|
Chris@235
|
125 for (size_t j = 0; j < s1.size(); ++j) {
|
Chris@235
|
126 if (j == 0 || costs[j][i] < min) {
|
Chris@235
|
127 min = costs[j][i];
|
Chris@235
|
128 minidx = j;
|
Chris@235
|
129 }
|
Chris@235
|
130 }
|
Chris@235
|
131 j = minidx;
|
Chris@235
|
132 #ifdef DEBUG_DTW
|
Chris@242
|
133 std::cerr << "Lowest cost at end of subsequence = " << min
|
Chris@242
|
134 << " at index " << j << ", tracking back from there"
|
Chris@242
|
135 << std::endl;
|
Chris@235
|
136 #endif
|
Chris@235
|
137 }
|
Chris@242
|
138
|
Chris@242
|
139 while (i > 0 || j > 0) {
|
Chris@235
|
140
|
Chris@235
|
141 alignment[i] = j;
|
Chris@242
|
142
|
Chris@242
|
143 if (i == 0) {
|
Chris@242
|
144 if (m_params.subsequence) {
|
Chris@242
|
145 break;
|
Chris@242
|
146 } else {
|
Chris@242
|
147 --j;
|
Chris@242
|
148 continue;
|
Chris@242
|
149 }
|
Chris@242
|
150 }
|
Chris@242
|
151
|
Chris@242
|
152 if (j == 0) {
|
Chris@242
|
153 --i;
|
Chris@242
|
154 continue;
|
Chris@242
|
155 }
|
Chris@242
|
156
|
Chris@235
|
157 pathcost_t a = costs[j-1][i];
|
Chris@235
|
158 pathcost_t b = costs[j][i-1];
|
Chris@235
|
159 pathcost_t both = costs[j-1][i-1];
|
Chris@235
|
160
|
Chris@235
|
161 if (a < b) {
|
Chris@235
|
162 --j;
|
Chris@235
|
163 if (both <= a) {
|
Chris@235
|
164 --i;
|
Chris@235
|
165 }
|
Chris@235
|
166 } else {
|
Chris@235
|
167 --i;
|
Chris@235
|
168 if (both <= b) {
|
Chris@235
|
169 --j;
|
Chris@235
|
170 }
|
Chris@235
|
171 }
|
Chris@235
|
172 }
|
Chris@235
|
173
|
Chris@235
|
174 if (m_params.subsequence) {
|
Chris@235
|
175 alignment[0] = j;
|
Chris@235
|
176 }
|
Chris@242
|
177
|
Chris@242
|
178 #ifdef DEBUG_DTW
|
Chris@246
|
179 std::cerr << "Costed path:" << std::endl;
|
Chris@242
|
180 pathcost_t prevcost = 0;
|
Chris@242
|
181 int indent = 0;
|
Chris@242
|
182 size_t prevj = 0;
|
Chris@242
|
183 for (size_t i = 0; i < alignment.size(); ++i) {
|
Chris@242
|
184 size_t j = alignment[i];
|
Chris@242
|
185 pathcost_t cost = costs[j][i];
|
Chris@242
|
186 if (prevcost > 1000) indent += 5;
|
Chris@242
|
187 else if (prevcost > 100) indent += 4;
|
Chris@242
|
188 else if (prevcost > 10) indent += 3;
|
Chris@242
|
189 else if (prevcost > 0) indent += 2;
|
Chris@242
|
190 if (j > prevj) {
|
Chris@242
|
191 while (j > prevj) {
|
Chris@242
|
192 std::cerr << "\n";
|
Chris@242
|
193 ++prevj;
|
Chris@242
|
194 }
|
Chris@242
|
195 for (int k = 0; k < indent; ++k) std::cerr << " ";
|
Chris@242
|
196 } else {
|
Chris@242
|
197 std::cerr << " ";
|
Chris@242
|
198 }
|
Chris@242
|
199 std::cerr << cost;
|
Chris@242
|
200 prevcost = cost;
|
Chris@242
|
201 if (prevcost == 0) prevcost = 1;
|
Chris@242
|
202 }
|
Chris@242
|
203 std::cerr << "\n";
|
Chris@246
|
204
|
Chris@246
|
205 std::cerr << "Alignment:" << std::endl;
|
Chris@246
|
206 for (size_t i = 0; i < alignment.size(); ++i) {
|
Chris@246
|
207 std::cerr << i << " -> " << alignment[i] << "\n";
|
Chris@246
|
208 }
|
Chris@242
|
209 #endif
|
Chris@242
|
210
|
Chris@235
|
211 return alignment;
|
Chris@235
|
212 }
|