Mercurial > hg > vamp-plugin-sdk
comparison vamp-sdk/hostext/PluginSummarisingAdapter.cpp @ 180:9a58bd07aa4d
* Part way to providing support for continuous-time averaging summaries
author | cannam |
---|---|
date | Wed, 03 Sep 2008 15:59:09 +0000 |
parents | a5ede8515893 |
children | cd16cbf80c87 |
comparison
equal
deleted
inserted
replaced
179:72bf540da84f | 180:9a58bd07aa4d |
---|---|
52 FeatureSet process(const float *const *inputBuffers, RealTime timestamp); | 52 FeatureSet process(const float *const *inputBuffers, RealTime timestamp); |
53 FeatureSet getRemainingFeatures(); | 53 FeatureSet getRemainingFeatures(); |
54 | 54 |
55 void setSummarySegmentBoundaries(const SegmentBoundaries &); | 55 void setSummarySegmentBoundaries(const SegmentBoundaries &); |
56 | 56 |
57 FeatureList getSummaryForOutput(int output, SummaryType type); | 57 FeatureList getSummaryForOutput(int output, |
58 FeatureSet getSummaryForAllOutputs(SummaryType type); | 58 SummaryType type, |
59 AveragingMethod avg); | |
60 | |
61 FeatureSet getSummaryForAllOutputs(SummaryType type, | |
62 AveragingMethod avg); | |
59 | 63 |
60 protected: | 64 protected: |
61 Plugin *m_plugin; | 65 Plugin *m_plugin; |
62 | 66 |
63 SegmentBoundaries m_boundaries; | 67 SegmentBoundaries m_boundaries; |
64 | 68 |
65 typedef std::vector<float> ValueList; | 69 typedef std::vector<float> ValueList; |
66 typedef std::map<int, ValueList> BinValueMap; | 70 typedef std::map<int, ValueList> BinValueMap; |
71 typedef std::vector<RealTime> DurationList; | |
67 | 72 |
68 struct OutputAccumulator { | 73 struct OutputAccumulator { |
69 int count; | 74 int count; |
70 BinValueMap values; | 75 BinValueMap values; // bin number -> values ordered by time |
71 OutputAccumulator() : count(0), values() { } | 76 DurationList durations; |
77 OutputAccumulator() : count(0), values(), durations() { } | |
72 }; | 78 }; |
73 | 79 |
74 typedef std::map<int, OutputAccumulator> OutputAccumulatorMap; | 80 typedef std::map<int, OutputAccumulator> OutputAccumulatorMap; |
75 OutputAccumulatorMap m_accumulators; | 81 OutputAccumulatorMap m_accumulators; // output number -> accumulator |
82 | |
83 typedef std::map<int, RealTime> OutputTimestampMap; | |
84 OutputTimestampMap m_prevTimestamps; // output number -> timestamp | |
76 | 85 |
77 struct OutputBinSummary { | 86 struct OutputBinSummary { |
87 | |
88 int count; | |
89 | |
90 // extents | |
78 float minimum; | 91 float minimum; |
79 float maximum; | 92 float maximum; |
93 float sum; | |
94 | |
95 // sample-average results | |
80 float median; | 96 float median; |
81 float mode; | 97 float mode; |
82 float sum; | |
83 float variance; | 98 float variance; |
84 int count; | 99 |
100 // continuous-time average results | |
101 float median_c; | |
102 float mode_c; | |
103 float mean_c; | |
104 float variance_c; | |
85 }; | 105 }; |
86 | 106 |
87 typedef std::map<int, OutputBinSummary> OutputSummary; | 107 typedef std::map<int, OutputBinSummary> OutputSummary; |
88 typedef std::map<RealTime, OutputSummary> SummarySegmentMap; | 108 typedef std::map<RealTime, OutputSummary> SummarySegmentMap; |
89 typedef std::map<int, SummarySegmentMap> OutputSummarySegmentMap; | 109 typedef std::map<int, SummarySegmentMap> OutputSummarySegmentMap; |
90 | 110 |
91 OutputSummarySegmentMap m_summaries; | 111 OutputSummarySegmentMap m_summaries; |
92 | 112 |
93 RealTime m_lastTimestamp; | 113 RealTime m_lastTimestamp; |
94 | 114 RealTime m_prevDuration; |
95 void accumulate(const FeatureSet &fs, RealTime); | 115 |
96 void accumulate(int output, const Feature &f, RealTime); | 116 void accumulate(const FeatureSet &fs, RealTime, bool final); |
117 void accumulate(int output, const Feature &f, RealTime, bool final); | |
97 void reduce(); | 118 void reduce(); |
98 }; | 119 }; |
99 | 120 |
100 PluginSummarisingAdapter::PluginSummarisingAdapter(Plugin *plugin) : | 121 PluginSummarisingAdapter::PluginSummarisingAdapter(Plugin *plugin) : |
101 PluginWrapper(plugin) | 122 PluginWrapper(plugin) |
119 { | 140 { |
120 return m_impl->getRemainingFeatures(); | 141 return m_impl->getRemainingFeatures(); |
121 } | 142 } |
122 | 143 |
123 Plugin::FeatureList | 144 Plugin::FeatureList |
124 PluginSummarisingAdapter::getSummaryForOutput(int output, SummaryType type) | 145 PluginSummarisingAdapter::getSummaryForOutput(int output, |
125 { | 146 SummaryType type, |
126 return m_impl->getSummaryForOutput(output, type); | 147 AveragingMethod avg) |
127 } | 148 { |
128 | 149 return m_impl->getSummaryForOutput(output, type, avg); |
129 Plugin::FeatureSet | 150 } |
130 PluginSummarisingAdapter::getSummaryForAllOutputs(SummaryType type) | 151 |
131 { | 152 Plugin::FeatureSet |
132 return m_impl->getSummaryForAllOutputs(type); | 153 PluginSummarisingAdapter::getSummaryForAllOutputs(SummaryType type, |
154 AveragingMethod avg) | |
155 { | |
156 return m_impl->getSummaryForAllOutputs(type, avg); | |
133 } | 157 } |
134 | 158 |
135 PluginSummarisingAdapter::Impl::Impl(Plugin *plugin, float inputSampleRate) : | 159 PluginSummarisingAdapter::Impl::Impl(Plugin *plugin, float inputSampleRate) : |
136 m_plugin(plugin) | 160 m_plugin(plugin) |
137 { | 161 { |
143 | 167 |
144 Plugin::FeatureSet | 168 Plugin::FeatureSet |
145 PluginSummarisingAdapter::Impl::process(const float *const *inputBuffers, RealTime timestamp) | 169 PluginSummarisingAdapter::Impl::process(const float *const *inputBuffers, RealTime timestamp) |
146 { | 170 { |
147 FeatureSet fs = m_plugin->process(inputBuffers, timestamp); | 171 FeatureSet fs = m_plugin->process(inputBuffers, timestamp); |
148 accumulate(fs, timestamp); | 172 accumulate(fs, timestamp, false); |
149 m_lastTimestamp = timestamp; | 173 m_lastTimestamp = timestamp; |
150 return fs; | 174 return fs; |
151 } | 175 } |
152 | 176 |
153 Plugin::FeatureSet | 177 Plugin::FeatureSet |
154 PluginSummarisingAdapter::Impl::getRemainingFeatures() | 178 PluginSummarisingAdapter::Impl::getRemainingFeatures() |
155 { | 179 { |
156 FeatureSet fs = m_plugin->getRemainingFeatures(); | 180 FeatureSet fs = m_plugin->getRemainingFeatures(); |
157 accumulate(fs, m_lastTimestamp); | 181 accumulate(fs, m_lastTimestamp, true); |
158 reduce(); | 182 reduce(); |
159 return fs; | 183 return fs; |
160 } | 184 } |
161 | 185 |
162 Plugin::FeatureList | 186 Plugin::FeatureList |
163 PluginSummarisingAdapter::Impl::getSummaryForOutput(int output, SummaryType type) | 187 PluginSummarisingAdapter::Impl::getSummaryForOutput(int output, |
164 { | 188 SummaryType type, |
189 AveragingMethod avg) | |
190 { | |
191 bool continuous = (avg == ContinuousTimeAverage); | |
192 | |
165 //!!! need to ensure that this is only called after processing is | 193 //!!! need to ensure that this is only called after processing is |
166 //!!! complete (at the moment processing is "completed" in the | 194 //!!! complete (at the moment processing is "completed" in the |
167 //!!! call to getRemainingFeatures, but we don't want to require | 195 //!!! call to getRemainingFeatures, but we don't want to require |
168 //!!! the host to call getRemainingFeatures at all unless it | 196 //!!! the host to call getRemainingFeatures at all unless it |
169 //!!! actually wants the raw features too -- calling getSummary | 197 //!!! actually wants the raw features too -- calling getSummary |
197 case Maximum: | 225 case Maximum: |
198 result = summary.maximum; | 226 result = summary.maximum; |
199 break; | 227 break; |
200 | 228 |
201 case Mean: | 229 case Mean: |
202 if (summary.count) { | 230 if (continuous) { |
231 result = summary.mean_c; | |
232 } else if (summary.count) { | |
203 result = summary.sum / summary.count; | 233 result = summary.sum / summary.count; |
204 } | 234 } |
205 break; | 235 break; |
206 | 236 |
207 case Median: | 237 case Median: |
208 result = summary.median; | 238 if (continuous) result = summary.median_c; |
239 else result = summary.median; | |
209 break; | 240 break; |
210 | 241 |
211 case Mode: | 242 case Mode: |
212 result = summary.mode; | 243 if (continuous) result = summary.mode_c; |
244 else result = summary.mode; | |
213 break; | 245 break; |
214 | 246 |
215 case Sum: | 247 case Sum: |
216 result = summary.sum; | 248 result = summary.sum; |
217 break; | 249 break; |
218 | 250 |
219 case Variance: | 251 case Variance: |
220 result = summary.variance; | 252 if (continuous) result = summary.variance_c; |
253 else result = summary.variance; | |
221 break; | 254 break; |
222 | 255 |
223 case StandardDeviation: | 256 case StandardDeviation: |
224 result = sqrtf(summary.variance); | 257 if (continuous) result = sqrtf(summary.variance_c); |
258 else result = sqrtf(summary.variance); | |
225 break; | 259 break; |
226 | 260 |
227 case Count: | 261 case Count: |
228 result = summary.count; | 262 result = summary.count; |
263 break; | |
264 | |
265 default: | |
229 break; | 266 break; |
230 } | 267 } |
231 | 268 |
232 f.values.push_back(result); | 269 f.values.push_back(result); |
233 } | 270 } |
236 } | 273 } |
237 return fl; | 274 return fl; |
238 } | 275 } |
239 | 276 |
240 Plugin::FeatureSet | 277 Plugin::FeatureSet |
241 PluginSummarisingAdapter::Impl::getSummaryForAllOutputs(SummaryType type) | 278 PluginSummarisingAdapter::Impl::getSummaryForAllOutputs(SummaryType type, |
279 AveragingMethod avg) | |
242 { | 280 { |
243 FeatureSet fs; | 281 FeatureSet fs; |
244 for (OutputSummarySegmentMap::const_iterator i = m_summaries.begin(); | 282 for (OutputSummarySegmentMap::const_iterator i = m_summaries.begin(); |
245 i != m_summaries.end(); ++i) { | 283 i != m_summaries.end(); ++i) { |
246 fs[i->first] = getSummaryForOutput(i->first, type); | 284 fs[i->first] = getSummaryForOutput(i->first, type, avg); |
247 } | 285 } |
248 return fs; | 286 return fs; |
249 } | 287 } |
250 | 288 |
251 void | 289 void |
252 PluginSummarisingAdapter::Impl::accumulate(const FeatureSet &fs, | 290 PluginSummarisingAdapter::Impl::accumulate(const FeatureSet &fs, |
253 RealTime timestamp) | 291 RealTime timestamp, |
292 bool final) | |
254 { | 293 { |
255 for (FeatureSet::const_iterator i = fs.begin(); i != fs.end(); ++i) { | 294 for (FeatureSet::const_iterator i = fs.begin(); i != fs.end(); ++i) { |
256 for (FeatureList::const_iterator j = i->second.begin(); | 295 for (FeatureList::const_iterator j = i->second.begin(); |
257 j != i->second.end(); ++j) { | 296 j != i->second.end(); ++j) { |
258 accumulate(i->first, *j, timestamp); | 297 accumulate(i->first, *j, timestamp, final); |
259 } | 298 } |
260 } | 299 } |
261 } | 300 } |
262 | 301 |
263 void | 302 void |
264 PluginSummarisingAdapter::Impl::accumulate(int output, | 303 PluginSummarisingAdapter::Impl::accumulate(int output, |
265 const Feature &f, | 304 const Feature &f, |
266 RealTime timestamp) | 305 RealTime timestamp, |
267 { | 306 bool final) |
268 //!!! use timestamp to determine which segment we're on | 307 { |
308 //!!! to do: use timestamp to determine which segment we're on | |
309 | |
269 m_accumulators[output].count++; | 310 m_accumulators[output].count++; |
311 | |
312 if (m_prevDuration == RealTime::zeroTime) { | |
313 if (m_prevTimestamps.find(output) != m_prevTimestamps.end()) { | |
314 m_prevDuration = timestamp - m_prevTimestamps[output]; | |
315 } | |
316 } | |
317 if (m_prevDuration != RealTime::zeroTime || | |
318 !m_accumulators[output].durations.empty()) { | |
319 // ... i.e. if not first result. We don't push a duration | |
320 // when we process the first result; then the duration we push | |
321 // each time is that for the result before the one we're | |
322 // processing, and we push an extra one at the end. This | |
323 // permits handling the case where the feature itself doesn't | |
324 // have a duration field, and we have to calculate it from the | |
325 // time to the following feature. The net effect is simply | |
326 // that values[n] and durations[n] refer to the same result. | |
327 m_accumulators[output].durations.push_back(m_prevDuration); | |
328 } | |
329 | |
330 m_prevTimestamps[output] = timestamp; | |
331 | |
270 for (int i = 0; i < int(f.values.size()); ++i) { | 332 for (int i = 0; i < int(f.values.size()); ++i) { |
271 | |
272 | |
273 //!!! we really want to associate this occurrence of this | |
274 //!!! value with the duration it covers. | |
275 | |
276 //!!! for dense values, the duration can be 1 or the sample | |
277 //!!! rate or whatever -- doesn't matter so long as it's the | |
278 //!!! same for every value. | |
279 | |
280 //!!! for sparse values, the duration should be that from this | |
281 //!!! feature to the next. | |
282 | |
283 //!!! if the feature has a duration, should be using that | |
284 //!!! instead. | |
285 | |
286 m_accumulators[output].values[i].push_back(f.values[i]); | 333 m_accumulators[output].values[i].push_back(f.values[i]); |
287 } | 334 } |
335 | |
336 if (final) { | |
337 RealTime finalDuration; | |
338 if (f.hasDuration) finalDuration = f.duration; | |
339 m_accumulators[output].durations.push_back(finalDuration); | |
340 } | |
341 | |
342 if (f.hasDuration) m_prevDuration = f.duration; | |
343 else m_prevDuration = RealTime::zeroTime; | |
288 } | 344 } |
289 | 345 |
290 void | 346 void |
291 PluginSummarisingAdapter::Impl::reduce() | 347 PluginSummarisingAdapter::Impl::reduce() |
292 { | 348 { |
296 i != m_accumulators.end(); ++i) { | 352 i != m_accumulators.end(); ++i) { |
297 | 353 |
298 int output = i->first; | 354 int output = i->first; |
299 OutputAccumulator &accumulator = i->second; | 355 OutputAccumulator &accumulator = i->second; |
300 | 356 |
357 RealTime totalDuration; | |
358 size_t dindex = 0; | |
359 | |
360 while (dindex < accumulator.durations.size()) { | |
361 totalDuration = totalDuration + accumulator.durations[dindex++]; | |
362 } | |
363 | |
364 dindex = 0; | |
365 | |
301 for (BinValueMap::iterator j = accumulator.values.begin(); | 366 for (BinValueMap::iterator j = accumulator.values.begin(); |
302 j != accumulator.values.end(); ++j) { | 367 j != accumulator.values.end(); ++j) { |
303 | 368 |
369 // work on all values over time for a single bin | |
370 | |
304 int bin = j->first; | 371 int bin = j->first; |
305 ValueList &values = j->second; | 372 ValueList &values = j->second; |
373 const DurationList &durations = accumulator.durations; | |
306 | 374 |
307 OutputBinSummary summary; | 375 OutputBinSummary summary; |
376 | |
377 summary.count = accumulator.count; | |
378 | |
308 summary.minimum = 0.f; | 379 summary.minimum = 0.f; |
309 summary.maximum = 0.f; | 380 summary.maximum = 0.f; |
381 | |
310 summary.median = 0.f; | 382 summary.median = 0.f; |
311 summary.mode = 0.f; | 383 summary.mode = 0.f; |
312 summary.sum = 0.f; | 384 summary.sum = 0.f; |
313 summary.variance = 0.f; | 385 summary.variance = 0.f; |
314 summary.count = accumulator.count; | 386 |
387 summary.median_c = 0.f; | |
388 summary.mode_c = 0.f; | |
389 summary.mean_c = 0.f; | |
390 summary.variance_c = 0.f; | |
391 | |
315 if (summary.count == 0 || values.empty()) continue; | 392 if (summary.count == 0 || values.empty()) continue; |
316 | 393 |
317 std::sort(values.begin(), values.end()); | 394 std::sort(values.begin(), values.end()); |
318 int sz = values.size(); | 395 int sz = values.size(); |
396 | |
397 if (sz != durations.size()) { | |
398 //!!! is this reasonable? | |
399 std::cerr << "WARNING: sz " << sz << " != durations.size() " | |
400 << durations.size() << std::endl; | |
401 } | |
319 | 402 |
320 summary.minimum = values[0]; | 403 summary.minimum = values[0]; |
321 summary.maximum = values[sz-1]; | 404 summary.maximum = values[sz-1]; |
322 | 405 |
323 if (sz % 2 == 1) { | 406 if (sz % 2 == 1) { |
328 | 411 |
329 std::map<float, int> distribution; | 412 std::map<float, int> distribution; |
330 | 413 |
331 for (int k = 0; k < sz; ++k) { | 414 for (int k = 0; k < sz; ++k) { |
332 summary.sum += values[k]; | 415 summary.sum += values[k]; |
333 ++distribution[values[k]]; | 416 distribution[values[k]] += 1; |
334 } | 417 } |
335 | 418 |
336 int md = 0; | 419 int md = 0; |
337 | 420 |
338 //!!! I don't like this. Really the mode should be the | |
339 //!!! value that spans the longest period of time, not the | |
340 //!!! one that appears in the largest number of distinct | |
341 //!!! features. I suppose that a median by time rather | |
342 //!!! than number of features would also be useful. | |
343 | |
344 for (std::map<float, int>::iterator di = distribution.begin(); | 421 for (std::map<float, int>::iterator di = distribution.begin(); |
345 di != distribution.end(); ++di) { | 422 di != distribution.end(); ++di) { |
346 if (di->second > md) { | 423 if (di->second > md) { |
347 md = di->second; | 424 md = di->second; |
348 summary.mode = di->first; | 425 summary.mode = di->first; |
349 } | 426 } |
350 } | 427 } |
351 | 428 |
352 distribution.clear(); | 429 distribution.clear(); |
353 | 430 |
431 //!!! we want to omit this bit if the features all have | |
432 //!!! equal duration (and set mode_c equal to mode instead) | |
433 | |
434 std::map<float, RealTime> distribution_c; | |
435 | |
436 for (int k = 0; k < sz; ++k) { | |
437 distribution_c[values[k]] = | |
438 distribution_c[values[k]] + durations[k]; | |
439 } | |
440 | |
441 RealTime mrd = RealTime::zeroTime; | |
442 | |
443 for (std::map<float, RealTime>::iterator di = distribution_c.begin(); | |
444 di != distribution_c.end(); ++di) { | |
445 if (di->second > mrd) { | |
446 mrd = di->second; | |
447 summary.mode_c = di->first; | |
448 } | |
449 } | |
450 | |
451 distribution_c.clear(); | |
452 | |
453 //!!! handle mean_c, median_c, variance_c | |
454 | |
354 float mean = summary.sum / summary.count; | 455 float mean = summary.sum / summary.count; |
355 | 456 |
356 for (int k = 0; k < sz; ++k) { | 457 for (int k = 0; k < sz; ++k) { |
357 summary.variance += (values[k] - mean) * (values[k] - mean); | 458 summary.variance += (values[k] - mean) * (values[k] - mean); |
358 } | 459 } |