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 }