annotate plugins/SimilarityPlugin.cpp @ 46:26a2e341d358

* Use KLDivergence in separate file
author Chris Cannam <c.cannam@qmul.ac.uk>
date Fri, 18 Jan 2008 14:40:51 +0000
parents 5d7ce1d87301
children f8c5f11e60a6
rev   line source
c@41 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
c@41 2
c@41 3 /*
c@41 4 * SegmenterPlugin.cpp
c@41 5 *
c@41 6 * Copyright 2008 Centre for Digital Music, Queen Mary, University of London.
c@41 7 * All rights reserved.
c@41 8 */
c@41 9
c@41 10 #include <iostream>
c@44 11 #include <cstdio>
c@41 12
c@41 13 #include "SimilarityPlugin.h"
c@42 14 #include "base/Pitch.h"
c@41 15 #include "dsp/mfcc/MFCC.h"
c@42 16 #include "dsp/chromagram/Chromagram.h"
c@41 17 #include "dsp/rateconversion/Decimator.h"
c@46 18 #include "maths/KLDivergence.cpp"
c@41 19
c@41 20 using std::string;
c@41 21 using std::vector;
c@41 22 using std::cerr;
c@41 23 using std::endl;
c@41 24 using std::ostringstream;
c@41 25
c@41 26 SimilarityPlugin::SimilarityPlugin(float inputSampleRate) :
c@41 27 Plugin(inputSampleRate),
c@42 28 m_type(TypeMFCC),
c@41 29 m_mfcc(0),
c@42 30 m_chromagram(0),
c@41 31 m_decimator(0),
c@42 32 m_featureColumnSize(20),
c@41 33 m_blockSize(0),
c@41 34 m_channels(0)
c@41 35 {
c@41 36
c@41 37 }
c@41 38
c@41 39 SimilarityPlugin::~SimilarityPlugin()
c@41 40 {
c@41 41 delete m_mfcc;
c@42 42 delete m_chromagram;
c@41 43 delete m_decimator;
c@41 44 }
c@41 45
c@41 46 string
c@41 47 SimilarityPlugin::getIdentifier() const
c@41 48 {
c@41 49 return "qm-similarity";
c@41 50 }
c@41 51
c@41 52 string
c@41 53 SimilarityPlugin::getName() const
c@41 54 {
c@41 55 return "Similarity";
c@41 56 }
c@41 57
c@41 58 string
c@41 59 SimilarityPlugin::getDescription() const
c@41 60 {
c@42 61 return "Return a distance matrix for similarity between the input audio channels";
c@41 62 }
c@41 63
c@41 64 string
c@41 65 SimilarityPlugin::getMaker() const
c@41 66 {
c@45 67 return "Mark Levy and Chris Cannam, Queen Mary, University of London";
c@41 68 }
c@41 69
c@41 70 int
c@41 71 SimilarityPlugin::getPluginVersion() const
c@41 72 {
c@41 73 return 1;
c@41 74 }
c@41 75
c@41 76 string
c@41 77 SimilarityPlugin::getCopyright() const
c@41 78 {
c@41 79 return "Copyright (c) 2008 - All Rights Reserved";
c@41 80 }
c@41 81
c@41 82 size_t
c@41 83 SimilarityPlugin::getMinChannelCount() const
c@41 84 {
c@43 85 return 1;
c@41 86 }
c@41 87
c@41 88 size_t
c@41 89 SimilarityPlugin::getMaxChannelCount() const
c@41 90 {
c@41 91 return 1024;
c@41 92 }
c@41 93
c@41 94 bool
c@41 95 SimilarityPlugin::initialise(size_t channels, size_t stepSize, size_t blockSize)
c@41 96 {
c@41 97 if (channels < getMinChannelCount() ||
c@41 98 channels > getMaxChannelCount()) return false;
c@41 99
c@41 100 if (stepSize != getPreferredStepSize()) {
c@43 101 //!!! actually this perhaps shouldn't be an error... similarly
c@43 102 //using more than getMaxChannelCount channels
c@41 103 std::cerr << "SimilarityPlugin::initialise: supplied step size "
c@41 104 << stepSize << " differs from required step size "
c@41 105 << getPreferredStepSize() << std::endl;
c@41 106 return false;
c@41 107 }
c@41 108
c@41 109 if (blockSize != getPreferredBlockSize()) {
c@41 110 std::cerr << "SimilarityPlugin::initialise: supplied block size "
c@41 111 << blockSize << " differs from required block size "
c@41 112 << getPreferredBlockSize() << std::endl;
c@41 113 return false;
c@41 114 }
c@41 115
c@41 116 m_blockSize = blockSize;
c@41 117 m_channels = channels;
c@41 118
c@44 119 m_lastNonEmptyFrame = std::vector<int>(m_channels);
c@44 120 for (int i = 0; i < m_channels; ++i) m_lastNonEmptyFrame[i] = -1;
c@44 121 m_frameNo = 0;
c@44 122
c@41 123 int decimationFactor = getDecimationFactor();
c@41 124 if (decimationFactor > 1) {
c@42 125 m_decimator = new Decimator(m_blockSize, decimationFactor);
c@41 126 }
c@41 127
c@42 128 if (m_type == TypeMFCC) {
c@42 129
c@42 130 m_featureColumnSize = 20;
c@42 131
c@45 132 MFCCConfig config(lrintf(m_inputSampleRate) / decimationFactor);
c@42 133 config.fftsize = 2048;
c@42 134 config.nceps = m_featureColumnSize - 1;
c@42 135 config.want_c0 = true;
c@45 136 config.logpower = 1;
c@42 137 m_mfcc = new MFCC(config);
c@42 138 m_fftSize = m_mfcc->getfftlength();
c@42 139
c@43 140 std::cerr << "MFCC FS = " << config.FS << ", FFT size = " << m_fftSize<< std::endl;
c@43 141
c@42 142 } else if (m_type == TypeChroma) {
c@42 143
c@42 144 m_featureColumnSize = 12;
c@42 145
c@42 146 ChromaConfig config;
c@42 147 config.FS = lrintf(m_inputSampleRate) / decimationFactor;
c@42 148 config.min = Pitch::getFrequencyForPitch(24, 0, 440);
c@42 149 config.max = Pitch::getFrequencyForPitch(96, 0, 440);
c@42 150 config.BPO = 12;
c@42 151 config.CQThresh = 0.0054;
c@42 152 config.isNormalised = true;
c@42 153 m_chromagram = new Chromagram(config);
c@42 154 m_fftSize = m_chromagram->getFrameSize();
c@42 155
c@42 156 std::cerr << "min = "<< config.min << ", max = " << config.max << std::endl;
c@42 157
c@42 158 } else {
c@42 159
c@42 160 std::cerr << "SimilarityPlugin::initialise: internal error: unknown type " << m_type << std::endl;
c@42 161 return false;
c@42 162 }
c@41 163
c@41 164 for (int i = 0; i < m_channels; ++i) {
c@42 165 m_values.push_back(FeatureMatrix());
c@41 166 }
c@41 167
c@41 168 return true;
c@41 169 }
c@41 170
c@41 171 void
c@41 172 SimilarityPlugin::reset()
c@41 173 {
c@41 174 //!!!
c@41 175 }
c@41 176
c@41 177 int
c@41 178 SimilarityPlugin::getDecimationFactor() const
c@41 179 {
c@41 180 int rate = lrintf(m_inputSampleRate);
c@41 181 int internalRate = 22050;
c@41 182 int decimationFactor = rate / internalRate;
c@41 183 if (decimationFactor < 1) decimationFactor = 1;
c@41 184
c@41 185 // must be a power of two
c@41 186 while (decimationFactor & (decimationFactor - 1)) ++decimationFactor;
c@41 187
c@41 188 return decimationFactor;
c@41 189 }
c@41 190
c@41 191 size_t
c@41 192 SimilarityPlugin::getPreferredStepSize() const
c@41 193 {
c@42 194 if (m_blockSize == 0) calculateBlockSize();
c@45 195 return m_blockSize/2;
c@41 196 }
c@41 197
c@41 198 size_t
c@41 199 SimilarityPlugin::getPreferredBlockSize() const
c@41 200 {
c@42 201 if (m_blockSize == 0) calculateBlockSize();
c@42 202 return m_blockSize;
c@42 203 }
c@42 204
c@42 205 void
c@42 206 SimilarityPlugin::calculateBlockSize() const
c@42 207 {
c@42 208 if (m_blockSize != 0) return;
c@42 209 int decimationFactor = getDecimationFactor();
c@42 210 if (m_type == TypeChroma) {
c@42 211 ChromaConfig config;
c@42 212 config.FS = lrintf(m_inputSampleRate) / decimationFactor;
c@42 213 config.min = Pitch::getFrequencyForPitch(24, 0, 440);
c@42 214 config.max = Pitch::getFrequencyForPitch(96, 0, 440);
c@42 215 config.BPO = 12;
c@42 216 config.CQThresh = 0.0054;
c@42 217 config.isNormalised = false;
c@42 218 Chromagram *c = new Chromagram(config);
c@42 219 size_t sz = c->getFrameSize();
c@42 220 delete c;
c@42 221 m_blockSize = sz * decimationFactor;
c@42 222 } else {
c@42 223 m_blockSize = 2048 * decimationFactor;
c@42 224 }
c@41 225 }
c@41 226
c@41 227 SimilarityPlugin::ParameterList SimilarityPlugin::getParameterDescriptors() const
c@41 228 {
c@41 229 ParameterList list;
c@42 230
c@42 231 ParameterDescriptor desc;
c@42 232 desc.identifier = "featureType";
c@42 233 desc.name = "Feature Type";
c@45 234 desc.description = "Audio feature used for similarity measure. Timbral: use the first 20 MFCCs (19 plus C0). Chromatic: use 12 bin-per-octave chroma.";
c@42 235 desc.unit = "";
c@42 236 desc.minValue = 0;
c@42 237 desc.maxValue = 1;
c@42 238 desc.defaultValue = 0;
c@42 239 desc.isQuantized = true;
c@42 240 desc.quantizeStep = 1;
c@42 241 desc.valueNames.push_back("Timbral (MFCC)");
c@42 242 desc.valueNames.push_back("Chromatic (Chroma)");
c@42 243 list.push_back(desc);
c@42 244
c@41 245 return list;
c@41 246 }
c@41 247
c@41 248 float
c@41 249 SimilarityPlugin::getParameter(std::string param) const
c@41 250 {
c@42 251 if (param == "featureType") {
c@42 252 if (m_type == TypeMFCC) return 0;
c@42 253 else if (m_type == TypeChroma) return 1;
c@42 254 else return 0;
c@42 255 }
c@42 256
c@41 257 std::cerr << "WARNING: SimilarityPlugin::getParameter: unknown parameter \""
c@41 258 << param << "\"" << std::endl;
c@41 259 return 0.0;
c@41 260 }
c@41 261
c@41 262 void
c@41 263 SimilarityPlugin::setParameter(std::string param, float value)
c@41 264 {
c@42 265 if (param == "featureType") {
c@42 266 int v = int(value + 0.1);
c@42 267 Type prevType = m_type;
c@42 268 if (v == 0) m_type = TypeMFCC;
c@42 269 else if (v == 1) m_type = TypeChroma;
c@42 270 if (m_type != prevType) m_blockSize = 0;
c@42 271 return;
c@42 272 }
c@42 273
c@41 274 std::cerr << "WARNING: SimilarityPlugin::setParameter: unknown parameter \""
c@41 275 << param << "\"" << std::endl;
c@41 276 }
c@41 277
c@41 278 SimilarityPlugin::OutputList
c@41 279 SimilarityPlugin::getOutputDescriptors() const
c@41 280 {
c@41 281 OutputList list;
c@41 282
c@41 283 OutputDescriptor similarity;
c@43 284 similarity.identifier = "distancematrix";
c@43 285 similarity.name = "Distance Matrix";
c@43 286 similarity.description = "Distance matrix for similarity metric. Smaller = more similar. Should be symmetrical.";
c@41 287 similarity.unit = "";
c@41 288 similarity.hasFixedBinCount = true;
c@41 289 similarity.binCount = m_channels;
c@41 290 similarity.hasKnownExtents = false;
c@41 291 similarity.isQuantized = false;
c@41 292 similarity.sampleType = OutputDescriptor::FixedSampleRate;
c@41 293 similarity.sampleRate = 1;
c@41 294
c@43 295 m_distanceMatrixOutput = list.size();
c@41 296 list.push_back(similarity);
c@41 297
c@43 298 OutputDescriptor simvec;
c@43 299 simvec.identifier = "distancevector";
c@43 300 simvec.name = "Distance from First Channel";
c@43 301 simvec.description = "Distance vector for similarity of each channel to the first channel. Smaller = more similar.";
c@43 302 simvec.unit = "";
c@43 303 simvec.hasFixedBinCount = true;
c@43 304 simvec.binCount = m_channels;
c@43 305 simvec.hasKnownExtents = false;
c@43 306 simvec.isQuantized = false;
c@43 307 simvec.sampleType = OutputDescriptor::FixedSampleRate;
c@43 308 simvec.sampleRate = 1;
c@43 309
c@43 310 m_distanceVectorOutput = list.size();
c@43 311 list.push_back(simvec);
c@43 312
c@44 313 OutputDescriptor sortvec;
c@44 314 sortvec.identifier = "sorteddistancevector";
c@44 315 sortvec.name = "Ordered Distances from First Channel";
c@44 316 sortvec.description = "Vector of the order of other channels in similarity to the first, followed by distance vector for similarity of each to the first. Smaller = more similar.";
c@44 317 sortvec.unit = "";
c@44 318 sortvec.hasFixedBinCount = true;
c@44 319 sortvec.binCount = m_channels;
c@44 320 sortvec.hasKnownExtents = false;
c@44 321 sortvec.isQuantized = false;
c@44 322 sortvec.sampleType = OutputDescriptor::FixedSampleRate;
c@44 323 sortvec.sampleRate = 1;
c@44 324
c@44 325 m_sortedVectorOutput = list.size();
c@44 326 list.push_back(sortvec);
c@44 327
c@41 328 OutputDescriptor means;
c@41 329 means.identifier = "means";
c@42 330 means.name = "Feature Means";
c@43 331 means.description = "Means of the feature bins. Feature time (sec) corresponds to input channel. Number of bins depends on selected feature type.";
c@41 332 means.unit = "";
c@41 333 means.hasFixedBinCount = true;
c@43 334 means.binCount = m_featureColumnSize;
c@41 335 means.hasKnownExtents = false;
c@41 336 means.isQuantized = false;
c@43 337 means.sampleType = OutputDescriptor::FixedSampleRate;
c@43 338 means.sampleRate = 1;
c@41 339
c@43 340 m_meansOutput = list.size();
c@41 341 list.push_back(means);
c@41 342
c@41 343 OutputDescriptor variances;
c@41 344 variances.identifier = "variances";
c@42 345 variances.name = "Feature Variances";
c@43 346 variances.description = "Variances of the feature bins. Feature time (sec) corresponds to input channel. Number of bins depends on selected feature type.";
c@41 347 variances.unit = "";
c@41 348 variances.hasFixedBinCount = true;
c@43 349 variances.binCount = m_featureColumnSize;
c@41 350 variances.hasKnownExtents = false;
c@41 351 variances.isQuantized = false;
c@43 352 variances.sampleType = OutputDescriptor::FixedSampleRate;
c@43 353 variances.sampleRate = 1;
c@41 354
c@43 355 m_variancesOutput = list.size();
c@41 356 list.push_back(variances);
c@41 357
c@41 358 return list;
c@41 359 }
c@41 360
c@41 361 SimilarityPlugin::FeatureSet
c@41 362 SimilarityPlugin::process(const float *const *inputBuffers, Vamp::RealTime /* timestamp */)
c@41 363 {
c@41 364 double *dblbuf = new double[m_blockSize];
c@41 365 double *decbuf = dblbuf;
c@42 366 if (m_decimator) decbuf = new double[m_fftSize];
c@42 367
c@42 368 double *raw = 0;
c@42 369 bool ownRaw = false;
c@42 370
c@42 371 if (m_type == TypeMFCC) {
c@42 372 raw = new double[m_featureColumnSize];
c@42 373 ownRaw = true;
c@42 374 }
c@41 375
c@43 376 float threshold = 1e-10;
c@43 377
c@41 378 for (size_t c = 0; c < m_channels; ++c) {
c@41 379
c@43 380 bool empty = true;
c@43 381
c@41 382 for (int i = 0; i < m_blockSize; ++i) {
c@43 383 float val = inputBuffers[c][i];
c@43 384 if (fabs(val) > threshold) empty = false;
c@43 385 dblbuf[i] = val;
c@41 386 }
c@41 387
c@43 388 if (empty) continue;
c@44 389 m_lastNonEmptyFrame[c] = m_frameNo;
c@43 390
c@41 391 if (m_decimator) {
c@41 392 m_decimator->process(dblbuf, decbuf);
c@41 393 }
c@42 394
c@42 395 if (m_type == TypeMFCC) {
c@45 396 m_mfcc->process(decbuf, raw);
c@42 397 } else if (m_type == TypeChroma) {
c@42 398 raw = m_chromagram->process(decbuf);
c@42 399 }
c@41 400
c@42 401 FeatureColumn mf(m_featureColumnSize);
c@44 402 // std::cout << m_frameNo << ":" << c << ": ";
c@44 403 for (int i = 0; i < m_featureColumnSize; ++i) {
c@44 404 mf[i] = raw[i];
c@44 405 // std::cout << raw[i] << " ";
c@44 406 }
c@44 407 // std::cout << std::endl;
c@41 408
c@42 409 m_values[c].push_back(mf);
c@41 410 }
c@41 411
c@41 412 if (m_decimator) delete[] decbuf;
c@41 413 delete[] dblbuf;
c@42 414
c@42 415 if (ownRaw) delete[] raw;
c@41 416
c@44 417 ++m_frameNo;
c@44 418
c@41 419 return FeatureSet();
c@41 420 }
c@41 421
c@41 422 SimilarityPlugin::FeatureSet
c@41 423 SimilarityPlugin::getRemainingFeatures()
c@41 424 {
c@42 425 std::vector<FeatureColumn> m(m_channels);
c@42 426 std::vector<FeatureColumn> v(m_channels);
c@41 427
c@41 428 for (int i = 0; i < m_channels; ++i) {
c@41 429
c@42 430 FeatureColumn mean(m_featureColumnSize), variance(m_featureColumnSize);
c@41 431
c@42 432 for (int j = 0; j < m_featureColumnSize; ++j) {
c@41 433
c@43 434 mean[j] = 0.0;
c@43 435 variance[j] = 0.0;
c@41 436 int count;
c@41 437
c@44 438 // We want to take values up to, but not including, the
c@44 439 // last non-empty frame (which may be partial)
c@43 440
c@44 441 int sz = m_lastNonEmptyFrame[i];
c@44 442 if (sz < 0) sz = 0;
c@43 443
c@43 444 // std::cout << "\nBin " << j << ":" << std::endl;
c@42 445
c@41 446 count = 0;
c@43 447 for (int k = 0; k < sz; ++k) {
c@42 448 double val = m_values[i][k][j];
c@42 449 // std::cout << val << " ";
c@41 450 if (isnan(val) || isinf(val)) continue;
c@41 451 mean[j] += val;
c@41 452 ++count;
c@41 453 }
c@41 454 if (count > 0) mean[j] /= count;
c@43 455 // std::cout << "\n" << count << " non-NaN non-inf values, so mean = " << mean[j] << std::endl;
c@41 456
c@41 457 count = 0;
c@43 458 for (int k = 0; k < sz; ++k) {
c@42 459 double val = ((m_values[i][k][j] - mean[j]) *
c@42 460 (m_values[i][k][j] - mean[j]));
c@41 461 if (isnan(val) || isinf(val)) continue;
c@41 462 variance[j] += val;
c@41 463 ++count;
c@41 464 }
c@41 465 if (count > 0) variance[j] /= count;
c@43 466 // std::cout << "... and variance = " << variance[j] << std::endl;
c@41 467 }
c@41 468
c@41 469 m[i] = mean;
c@41 470 v[i] = variance;
c@41 471 }
c@41 472
c@42 473 // we want to return a matrix of the distances between channels,
c@41 474 // but Vamp doesn't have a matrix return type so we actually
c@41 475 // return a series of vectors
c@41 476
c@41 477 std::vector<std::vector<double> > distances;
c@41 478
c@42 479 // "Despite the fact that MFCCs extracted from music are clearly
c@42 480 // not Gaussian, [14] showed, somewhat surprisingly, that a
c@42 481 // similarity function comparing single Gaussians modelling MFCCs
c@42 482 // for each track can perform as well as mixture models. A great
c@42 483 // advantage of using single Gaussians is that a simple closed
c@42 484 // form exists for the KL divergence." -- Mark Levy, "Lightweight
c@42 485 // measures for timbral similarity of musical audio"
c@42 486 // (http://www.elec.qmul.ac.uk/easaier/papers/mlevytimbralsimilarity.pdf)
c@46 487
c@46 488 KLDivergence kld;
c@42 489
c@41 490 for (int i = 0; i < m_channels; ++i) {
c@41 491 distances.push_back(std::vector<double>());
c@41 492 for (int j = 0; j < m_channels; ++j) {
c@46 493 double d = kld.distance(m[i], v[i], m[j], v[j]);
c@41 494 distances[i].push_back(d);
c@41 495 }
c@41 496 }
c@41 497
c@44 498 // We give all features a timestamp, otherwise hosts will tend to
c@44 499 // stamp them at the end of the file, which is annoying
c@44 500
c@41 501 FeatureSet returnFeatures;
c@41 502
c@44 503 Feature feature;
c@44 504 feature.hasTimestamp = true;
c@44 505
c@43 506 Feature distanceVectorFeature;
c@43 507 distanceVectorFeature.label = "Distance from first channel";
c@44 508 distanceVectorFeature.hasTimestamp = true;
c@44 509 distanceVectorFeature.timestamp = Vamp::RealTime::zeroTime;
c@44 510
c@44 511 std::map<double, int> sorted;
c@44 512
c@44 513 char labelBuffer[100];
c@43 514
c@41 515 for (int i = 0; i < m_channels; ++i) {
c@41 516
c@41 517 feature.timestamp = Vamp::RealTime(i, 0);
c@41 518
c@44 519 sprintf(labelBuffer, "Means for channel %d", i+1);
c@44 520 feature.label = labelBuffer;
c@44 521
c@41 522 feature.values.clear();
c@42 523 for (int k = 0; k < m_featureColumnSize; ++k) {
c@41 524 feature.values.push_back(m[i][k]);
c@41 525 }
c@41 526
c@43 527 returnFeatures[m_meansOutput].push_back(feature);
c@41 528
c@44 529 sprintf(labelBuffer, "Variances for channel %d", i+1);
c@44 530 feature.label = labelBuffer;
c@44 531
c@41 532 feature.values.clear();
c@42 533 for (int k = 0; k < m_featureColumnSize; ++k) {
c@41 534 feature.values.push_back(v[i][k]);
c@41 535 }
c@41 536
c@43 537 returnFeatures[m_variancesOutput].push_back(feature);
c@41 538
c@41 539 feature.values.clear();
c@41 540 for (int j = 0; j < m_channels; ++j) {
c@41 541 feature.values.push_back(distances[i][j]);
c@41 542 }
c@43 543
c@44 544 sprintf(labelBuffer, "Distances from channel %d", i+1);
c@44 545 feature.label = labelBuffer;
c@41 546
c@43 547 returnFeatures[m_distanceMatrixOutput].push_back(feature);
c@43 548
c@43 549 distanceVectorFeature.values.push_back(distances[0][i]);
c@44 550
c@44 551 sorted[distances[0][i]] = i;
c@41 552 }
c@41 553
c@43 554 returnFeatures[m_distanceVectorOutput].push_back(distanceVectorFeature);
c@43 555
c@44 556 feature.label = "Order of channels by similarity to first channel";
c@44 557 feature.values.clear();
c@44 558 feature.timestamp = Vamp::RealTime(0, 0);
c@44 559
c@44 560 for (std::map<double, int>::iterator i = sorted.begin();
c@44 561 i != sorted.end(); ++i) {
c@45 562 feature.values.push_back(i->second + 1);
c@44 563 }
c@44 564
c@44 565 returnFeatures[m_sortedVectorOutput].push_back(feature);
c@44 566
c@44 567 feature.label = "Ordered distances of channels from first channel";
c@44 568 feature.values.clear();
c@44 569 feature.timestamp = Vamp::RealTime(1, 0);
c@44 570
c@44 571 for (std::map<double, int>::iterator i = sorted.begin();
c@44 572 i != sorted.end(); ++i) {
c@44 573 feature.values.push_back(i->first);
c@44 574 }
c@44 575
c@44 576 returnFeatures[m_sortedVectorOutput].push_back(feature);
c@44 577
c@41 578 return returnFeatures;
c@41 579 }