Chris@755
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@755
|
2
|
Chris@755
|
3 /*
|
Chris@755
|
4 Sonic Visualiser
|
Chris@755
|
5 An audio file viewer and annotation editor.
|
Chris@755
|
6 Centre for Digital Music, Queen Mary, University of London.
|
Chris@755
|
7
|
Chris@755
|
8 This program is free software; you can redistribute it and/or
|
Chris@755
|
9 modify it under the terms of the GNU General Public License as
|
Chris@755
|
10 published by the Free Software Foundation; either version 2 of the
|
Chris@755
|
11 License, or (at your option) any later version. See the file
|
Chris@755
|
12 COPYING included with this distribution for more information.
|
Chris@755
|
13 */
|
Chris@755
|
14
|
Chris@755
|
15 #ifndef SV_DTW_H
|
Chris@755
|
16 #define SV_DTW_H
|
Chris@755
|
17
|
Chris@755
|
18 #include <vector>
|
Chris@757
|
19 #include <functional>
|
Chris@755
|
20
|
Chris@772
|
21 //#define DEBUG_DTW 1
|
Chris@772
|
22
|
Chris@757
|
23 template <typename Value>
|
Chris@755
|
24 class DTW
|
Chris@755
|
25 {
|
Chris@755
|
26 public:
|
Chris@757
|
27 DTW(std::function<double(const Value &, const Value &)> distanceMetric) :
|
Chris@757
|
28 m_metric(distanceMetric) { }
|
Chris@757
|
29
|
Chris@780
|
30 /**
|
Chris@780
|
31 * Align the sequence s2 against the whole of the sequence s1,
|
Chris@780
|
32 * returning the index into s1 for each element in s2.
|
Chris@780
|
33 */
|
Chris@780
|
34 std::vector<size_t> alignSequences(std::vector<Value> s1,
|
Chris@780
|
35 std::vector<Value> s2) {
|
Chris@780
|
36 return align(s1, s2, false);
|
Chris@780
|
37 }
|
Chris@757
|
38
|
Chris@780
|
39 /**
|
Chris@780
|
40 * Align the sequence sub against the best-matching subsequence of
|
Chris@780
|
41 * s, returning the index into s for each element in sub.
|
Chris@780
|
42 */
|
Chris@780
|
43 std::vector<size_t> alignSubsequence(std::vector<Value> s,
|
Chris@780
|
44 std::vector<Value> sub) {
|
Chris@780
|
45 return align(s, sub, true);
|
Chris@757
|
46 }
|
Chris@757
|
47
|
Chris@757
|
48 private:
|
Chris@757
|
49 std::function<double(const Value &, const Value &)> m_metric;
|
Chris@757
|
50
|
Chris@755
|
51 typedef double cost_t;
|
Chris@755
|
52
|
Chris@755
|
53 struct CostOption {
|
Chris@755
|
54 bool present;
|
Chris@755
|
55 cost_t cost;
|
Chris@755
|
56 };
|
Chris@755
|
57
|
Chris@757
|
58 cost_t choose(CostOption x, CostOption y, CostOption d) {
|
Chris@755
|
59 if (x.present && y.present) {
|
Chris@755
|
60 if (!d.present) {
|
Chris@755
|
61 throw std::logic_error("if x & y both exist, so must diagonal");
|
Chris@755
|
62 }
|
Chris@755
|
63 return std::min(std::min(x.cost, y.cost), d.cost);
|
Chris@755
|
64 } else if (x.present) {
|
Chris@755
|
65 return x.cost;
|
Chris@755
|
66 } else if (y.present) {
|
Chris@755
|
67 return y.cost;
|
Chris@755
|
68 } else {
|
Chris@755
|
69 return 0.0;
|
Chris@755
|
70 }
|
Chris@755
|
71 }
|
Chris@755
|
72
|
Chris@780
|
73 std::vector<std::vector<cost_t>> costSequences(std::vector<Value> s1,
|
Chris@780
|
74 std::vector<Value> s2,
|
Chris@780
|
75 bool subsequence) {
|
Chris@757
|
76
|
Chris@757
|
77 std::vector<std::vector<cost_t>> costs
|
Chris@757
|
78 (s1.size(), std::vector<cost_t>(s2.size(), 0.0));
|
Chris@757
|
79
|
Chris@757
|
80 for (size_t j = 0; j < s1.size(); ++j) {
|
Chris@757
|
81 for (size_t i = 0; i < s2.size(); ++i) {
|
Chris@757
|
82 cost_t c = m_metric(s1[j], s2[i]);
|
Chris@780
|
83 if (i == 0 && subsequence) {
|
Chris@780
|
84 costs[j][i] = c;
|
Chris@780
|
85 } else {
|
Chris@780
|
86 costs[j][i] = choose
|
Chris@780
|
87 (
|
Chris@780
|
88 { j > 0,
|
Chris@780
|
89 j > 0 ? c + costs[j-1][i] : 0.0
|
Chris@780
|
90 },
|
Chris@780
|
91 { i > 0,
|
Chris@780
|
92 i > 0 ? c + costs[j][i-1] : 0.0
|
Chris@780
|
93 },
|
Chris@780
|
94 { j > 0 && i > 0,
|
Chris@780
|
95 j > 0 && i > 0 ? c + costs[j-1][i-1] : 0.0
|
Chris@780
|
96 });
|
Chris@780
|
97 }
|
Chris@757
|
98 }
|
Chris@757
|
99 }
|
Chris@757
|
100
|
Chris@757
|
101 return costs;
|
Chris@757
|
102 }
|
Chris@780
|
103
|
Chris@780
|
104 std::vector<size_t> align(const std::vector<Value> &s1,
|
Chris@780
|
105 const std::vector<Value> &s2,
|
Chris@780
|
106 bool subsequence) {
|
Chris@780
|
107
|
Chris@780
|
108 // Return the index into s1 for each element in s2
|
Chris@780
|
109
|
Chris@780
|
110 std::vector<size_t> alignment(s2.size(), 0);
|
Chris@780
|
111
|
Chris@780
|
112 if (s1.empty() || s2.empty()) {
|
Chris@780
|
113 return alignment;
|
Chris@780
|
114 }
|
Chris@780
|
115
|
Chris@780
|
116 auto costs = costSequences(s1, s2, subsequence);
|
Chris@780
|
117
|
Chris@780
|
118 #ifdef DEBUG_DTW
|
Chris@780
|
119 SVCERR << "Cost matrix:" << endl;
|
Chris@780
|
120 for (auto v: cost) {
|
Chris@780
|
121 for (auto x: v) {
|
Chris@780
|
122 SVCERR << x << " ";
|
Chris@780
|
123 }
|
Chris@780
|
124 SVCERR << "\n";
|
Chris@780
|
125 }
|
Chris@780
|
126 #endif
|
Chris@780
|
127
|
Chris@780
|
128 size_t j = s1.size() - 1;
|
Chris@780
|
129 size_t i = s2.size() - 1;
|
Chris@780
|
130
|
Chris@780
|
131 if (subsequence) {
|
Chris@780
|
132 cost_t min = 0.0;
|
Chris@780
|
133 size_t minidx = 0;
|
Chris@780
|
134 for (size_t j = 0; j < s1.size(); ++j) {
|
Chris@780
|
135 if (j == 0 || costs[j][i] < min) {
|
Chris@780
|
136 min = costs[j][i];
|
Chris@780
|
137 minidx = j;
|
Chris@780
|
138 }
|
Chris@780
|
139 }
|
Chris@780
|
140 j = minidx;
|
Chris@780
|
141 #ifdef DEBUG_DTW
|
Chris@780
|
142 SVCERR << "Lowest cost at end of subsequence = " << min
|
Chris@780
|
143 << " at index " << j << ", tracking back from there" << endl;
|
Chris@780
|
144 #endif
|
Chris@780
|
145 }
|
Chris@780
|
146
|
Chris@780
|
147 while (i > 0 && (j > 0 || subsequence)) {
|
Chris@780
|
148
|
Chris@780
|
149 alignment[i] = j;
|
Chris@780
|
150
|
Chris@780
|
151 cost_t a = costs[j-1][i];
|
Chris@780
|
152 cost_t b = costs[j][i-1];
|
Chris@780
|
153 cost_t both = costs[j-1][i-1];
|
Chris@780
|
154
|
Chris@780
|
155 if (a < b) {
|
Chris@780
|
156 --j;
|
Chris@780
|
157 if (both <= a) {
|
Chris@780
|
158 --i;
|
Chris@780
|
159 }
|
Chris@780
|
160 } else {
|
Chris@780
|
161 --i;
|
Chris@780
|
162 if (both <= b) {
|
Chris@780
|
163 --j;
|
Chris@780
|
164 }
|
Chris@780
|
165 }
|
Chris@780
|
166 }
|
Chris@780
|
167
|
Chris@780
|
168 if (subsequence) {
|
Chris@780
|
169 alignment[0] = j;
|
Chris@780
|
170 }
|
Chris@780
|
171
|
Chris@780
|
172 return alignment;
|
Chris@780
|
173 }
|
Chris@757
|
174 };
|
Chris@757
|
175
|
Chris@757
|
176 class MagnitudeDTW
|
Chris@757
|
177 {
|
Chris@757
|
178 public:
|
Chris@757
|
179 MagnitudeDTW() : m_dtw(metric) { }
|
Chris@757
|
180
|
Chris@780
|
181 std::vector<size_t> alignSequences(std::vector<double> s1,
|
Chris@780
|
182 std::vector<double> s2) {
|
Chris@780
|
183 return m_dtw.alignSequences(s1, s2);
|
Chris@780
|
184 }
|
Chris@780
|
185
|
Chris@780
|
186 std::vector<size_t> alignSubsequence(std::vector<double> s,
|
Chris@780
|
187 std::vector<double> sub) {
|
Chris@780
|
188 return m_dtw.alignSubsequence(s, sub);
|
Chris@757
|
189 }
|
Chris@757
|
190
|
Chris@757
|
191 private:
|
Chris@757
|
192 DTW<double> m_dtw;
|
Chris@757
|
193
|
Chris@757
|
194 static double metric(const double &a, const double &b) {
|
Chris@757
|
195 return std::abs(b - a);
|
Chris@757
|
196 }
|
Chris@757
|
197 };
|
Chris@757
|
198
|
Chris@757
|
199 class RiseFallDTW
|
Chris@757
|
200 {
|
Chris@757
|
201 public:
|
Chris@757
|
202 enum class Direction {
|
Chris@757
|
203 None,
|
Chris@757
|
204 Up,
|
Chris@757
|
205 Down
|
Chris@757
|
206 };
|
Chris@757
|
207
|
Chris@757
|
208 struct Value {
|
Chris@757
|
209 Direction direction;
|
Chris@757
|
210 double distance;
|
Chris@757
|
211 };
|
Chris@757
|
212
|
Chris@757
|
213 RiseFallDTW() : m_dtw(metric) { }
|
Chris@757
|
214
|
Chris@780
|
215 std::vector<size_t> alignSequences(std::vector<Value> s1,
|
Chris@780
|
216 std::vector<Value> s2) {
|
Chris@780
|
217 return m_dtw.alignSequences(s1, s2);
|
Chris@780
|
218 }
|
Chris@780
|
219
|
Chris@780
|
220 std::vector<size_t> alignSubsequence(std::vector<Value> s,
|
Chris@780
|
221 std::vector<Value> sub) {
|
Chris@780
|
222 return m_dtw.alignSubsequence(s, sub);
|
Chris@757
|
223 }
|
Chris@757
|
224
|
Chris@757
|
225 private:
|
Chris@757
|
226 DTW<Value> m_dtw;
|
Chris@757
|
227
|
Chris@757
|
228 static double metric(const Value &a, const Value &b) {
|
Chris@757
|
229
|
Chris@757
|
230 auto together = [](double c1, double c2) {
|
Chris@755
|
231 auto diff = std::abs(c1 - c2);
|
Chris@755
|
232 return (diff < 1.0 ? -1.0 :
|
Chris@755
|
233 diff > 3.0 ? 1.0 :
|
Chris@755
|
234 0.0);
|
Chris@755
|
235 };
|
Chris@757
|
236 auto opposing = [](double c1, double c2) {
|
Chris@755
|
237 auto diff = c1 + c2;
|
Chris@755
|
238 return (diff < 2.0 ? 1.0 :
|
Chris@755
|
239 2.0);
|
Chris@755
|
240 };
|
Chris@757
|
241
|
Chris@755
|
242 if (a.direction == Direction::None || b.direction == Direction::None) {
|
Chris@755
|
243 if (a.direction == b.direction) {
|
Chris@755
|
244 return 0.0;
|
Chris@755
|
245 } else {
|
Chris@755
|
246 return 1.0;
|
Chris@755
|
247 }
|
Chris@755
|
248 } else {
|
Chris@755
|
249 if (a.direction == b.direction) {
|
Chris@757
|
250 return together (a.distance, b.distance);
|
Chris@755
|
251 } else {
|
Chris@757
|
252 return opposing (a.distance, b.distance);
|
Chris@755
|
253 }
|
Chris@755
|
254 }
|
Chris@755
|
255 }
|
Chris@755
|
256 };
|
Chris@755
|
257
|
Chris@771
|
258 inline std::ostream &operator<<(std::ostream &s, const RiseFallDTW::Value v) {
|
Chris@771
|
259 return (s <<
|
Chris@771
|
260 (v.direction == RiseFallDTW::Direction::None ? "=" :
|
Chris@771
|
261 v.direction == RiseFallDTW::Direction::Up ? "+" : "-")
|
Chris@771
|
262 << v.distance);
|
Chris@771
|
263 }
|
Chris@771
|
264
|
Chris@755
|
265 #endif
|