comparison runner/FeatureExtractionManager.cpp @ 248:c8e5fcddf8be

Merge
author Chris Cannam
date Fri, 18 Mar 2016 15:15:55 +0000
parents 9a10c3ffff47
children 857ce6ecb163
comparison
equal deleted inserted replaced
247:5eadb3b687bb 248:c8e5fcddf8be
321 << "\" with plugin step size " << actualStepSize 321 << "\" with plugin step size " << actualStepSize
322 << " and block size " << actualBlockSize 322 << " and block size " << actualBlockSize
323 << " (adapter step and block size " << m_blockSize << ")" 323 << " (adapter step and block size " << m_blockSize << ")"
324 << endl; 324 << endl;
325 325
326 // cerr << "NOTE: That transform is: " << transform.toXmlString() << endl;
327
326 if (pida) { 328 if (pida) {
327 cerr << "NOTE: PluginInputDomainAdapter timestamp adjustment is " 329 cerr << "NOTE: PluginInputDomainAdapter timestamp adjustment is "
328 330
329 << pida->getTimestampAdjustment() << endl; 331 << pida->getTimestampAdjustment() << endl;
330 } 332 }
380 } 382 }
381 } 383 }
382 384
383 m_transformPluginMap[transform] = plugin; 385 m_transformPluginMap[transform] = plugin;
384 386
387 // cerr << "NOTE: Assigned plugin " << plugin << " for transform: " << transform.toXmlString() << endl;
388
385 if (!(originalTransform == transform)) { 389 if (!(originalTransform == transform)) {
386 m_transformPluginMap[originalTransform] = plugin; 390 m_transformPluginMap[originalTransform] = plugin;
391 // cerr << "NOTE: Also assigned plugin " << plugin << " for original transform: " << originalTransform.toXmlString() << endl;
387 } 392 }
388 393
389 } else { 394 } else {
390 395
391 plugin = m_transformPluginMap[transform]; 396 plugin = m_transformPluginMap[transform];
425 } 430 }
426 return result; 431 return result;
427 } 432 }
428 433
429 bool FeatureExtractionManager::addFeatureExtractorFromFile 434 bool FeatureExtractionManager::addFeatureExtractorFromFile
430 (QString transformXmlFile, const vector<FeatureWriter*> &writers) 435 (QString transformFile, const vector<FeatureWriter*> &writers)
431 { 436 {
437 // We support two formats for transform description files, XML (in
438 // a format specific to Sonic Annotator) and RDF/Turtle. The RDF
439 // format can describe multiple transforms in a single file, the
440 // XML only one.
441
442 // Possible errors we should report:
443 //
444 // 1. File does not exist or cannot be opened
445 // 2. File is ostensibly XML, but is not parseable
446 // 3. File is ostensibly Turtle, but is not parseable
447 // 4. File is XML, but contains no valid transform (e.g. is unrelated XML)
448 // 5. File is Turtle, but contains no valid transform(s)
449 // 6. File is Turtle and contains both valid and invalid transform(s)
450
451 {
452 // We don't actually need to open this here yet, we just hoist
453 // it to the top for error reporting purposes
454 QFile file(transformFile);
455 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
456 // Error case 1. File does not exist or cannot be opened
457 cerr << "ERROR: Failed to open transform file \"" << transformFile
458 << "\" for reading" << endl;
459 return false;
460 }
461 }
462
432 bool tryRdf = true; 463 bool tryRdf = true;
433 464 if (transformFile.endsWith(".xml") || transformFile.endsWith(".XML")) {
434 if (transformXmlFile.endsWith(".xml") || transformXmlFile.endsWith(".XML")) {
435 // We don't support RDF-XML (and nor does the underlying 465 // We don't support RDF-XML (and nor does the underlying
436 // parser library) so skip the RDF parse if the filename 466 // parser library) so skip the RDF parse if the filename
437 // suggests XML, to avoid puking out a load of errors from 467 // suggests XML, to avoid puking out a load of errors from
438 // feeding XML to a Turtle parser 468 // feeding XML to a Turtle parser
439 tryRdf = false; 469 tryRdf = false;
440 } 470 }
441 471
472 bool tryXml = true;
473 if (transformFile.endsWith(".ttl") || transformFile.endsWith(".TTL") ||
474 transformFile.endsWith(".ntriples") || transformFile.endsWith(".NTRIPLES") ||
475 transformFile.endsWith(".n3") || transformFile.endsWith(".N3")) {
476 tryXml = false;
477 }
478
479 QString rdfError, xmlError;
480
442 if (tryRdf) { 481 if (tryRdf) {
482
443 RDFTransformFactory factory 483 RDFTransformFactory factory
444 (QUrl::fromLocalFile(QFileInfo(transformXmlFile).absoluteFilePath()) 484 (QUrl::fromLocalFile(QFileInfo(transformFile).absoluteFilePath())
445 .toString()); 485 .toString());
446 ProgressPrinter printer("Parsing transforms RDF file"); 486 ProgressPrinter printer("Parsing transforms RDF file");
447 std::vector<Transform> transforms = factory.getTransforms(&printer); 487 std::vector<Transform> transforms = factory.getTransforms(&printer);
448 if (!factory.isOK()) { 488
449 cerr << "WARNING: FeatureExtractionManager::addFeatureExtractorFromFile: Failed to parse transforms file: " << factory.getErrorString().toStdString() << endl; 489 if (factory.isOK()) {
490 if (transforms.empty()) {
491 cerr << "ERROR: Transform file \"" << transformFile
492 << "\" is valid RDF but defines no transforms" << endl;
493 return false;
494 } else {
495 bool success = true;
496 for (int i = 0; i < (int)transforms.size(); ++i) {
497 if (!addFeatureExtractor(transforms[i], writers)) {
498 success = false;
499 }
500 }
501 return success;
502 }
503 } else { // !factory.isOK()
450 if (factory.isRDF()) { 504 if (factory.isRDF()) {
451 return false; // no point trying it as XML 505 cerr << "ERROR: Invalid transform RDF file \"" << transformFile
452 } 506 << "\": " << factory.getErrorString() << endl;
453 } 507 return false;
454 if (!transforms.empty()) { 508 }
455 bool success = true; 509
456 for (int i = 0; i < (int)transforms.size(); ++i) { 510 // the not-RDF case: fall through without reporting an
457 if (!addFeatureExtractor(transforms[i], writers)) { 511 // error, so we try the file as XML, and if that fails, we
458 success = false; 512 // print a general unparseable-file error
459 } 513 rdfError = factory.getErrorString();
460 } 514 }
461 return success; 515 }
462 } 516
463 } 517 if (tryXml) {
464 518
465 QFile file(transformXmlFile); 519 QFile file(transformFile);
466 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { 520 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
467 cerr << "ERROR: Failed to open transform XML file \"" 521 cerr << "ERROR: Failed to open transform file \""
468 << transformXmlFile.toStdString() << "\" for reading" << endl; 522 << transformFile.toStdString() << "\" for reading" << endl;
469 return false; 523 return false;
470 } 524 }
471 525
472 QTextStream *qts = new QTextStream(&file); 526 QTextStream *qts = new QTextStream(&file);
473 QString qs = qts->readAll(); 527 QString qs = qts->readAll();
474 delete qts; 528 delete qts;
475 file.close(); 529 file.close();
476 530
477 Transform transform(qs); 531 Transform transform(qs);
478 532 xmlError = transform.getErrorString();
479 return addFeatureExtractor(transform, writers); 533
534 if (xmlError == "") {
535
536 if (transform.getIdentifier() == "") {
537 cerr << "ERROR: Transform file \"" << transformFile
538 << "\" is valid XML but defines no transform" << endl;
539 return false;
540 }
541
542 return addFeatureExtractor(transform, writers);
543 }
544 }
545
546 cerr << "ERROR: Transform file \"" << transformFile
547 << "\" could not be parsed" << endl;
548 if (rdfError != "") {
549 cerr << "ERROR: RDF parser reported: " << rdfError << endl;
550 }
551 if (xmlError != "") {
552 cerr << "ERROR: XML parser reported: " << xmlError << endl;
553 }
554
555 return false;
480 } 556 }
481 557
482 void FeatureExtractionManager::addSource(QString audioSource, bool willMultiplex) 558 void FeatureExtractionManager::addSource(QString audioSource, bool willMultiplex)
483 { 559 {
484 std::cerr << "Have audio source: \"" << audioSource.toStdString() << "\"" << std::endl; 560 std::cerr << "Have audio source: \"" << audioSource.toStdString() << "\"" << std::endl;
487 // first audio source and we need it to establish default channel 563 // first audio source and we need it to establish default channel
488 // count and sample rate 564 // count and sample rate
489 565
490 if (m_channels == 0 || m_defaultSampleRate == 0) { 566 if (m_channels == 0 || m_defaultSampleRate == 0) {
491 567
492 ProgressPrinter retrievalProgress("Determining default rate and channel count from first input file..."); 568 ProgressPrinter retrievalProgress("Retrieving first input file to determine default rate and channel count...");
493 569
494 FileSource source(audioSource, &retrievalProgress); 570 FileSource source(audioSource, &retrievalProgress);
495 if (!source.isAvailable()) { 571 if (!source.isAvailable()) {
496 cerr << "ERROR: File or URL \"" << audioSource.toStdString() 572 cerr << "ERROR: File or URL \"" << audioSource.toStdString()
497 << "\" could not be located"; 573 << "\" could not be located";
522 598
523 if (!willMultiplex) { 599 if (!willMultiplex) {
524 if (m_channels == 0) { 600 if (m_channels == 0) {
525 m_channels = reader->getChannelCount(); 601 m_channels = reader->getChannelCount();
526 cerr << "Taking default channel count of " 602 cerr << "Taking default channel count of "
527 << reader->getChannelCount() << " from file" << endl; 603 << reader->getChannelCount() << " from audio file" << endl;
528 } 604 }
529 } 605 }
530 606
531 if (m_defaultSampleRate == 0) { 607 if (m_defaultSampleRate == 0) {
532 m_defaultSampleRate = reader->getNativeRate(); 608 m_defaultSampleRate = reader->getNativeRate();
533 cerr << "Taking default sample rate of " 609 cerr << "Taking default sample rate of "
534 << reader->getNativeRate() << "Hz from file" << endl; 610 << reader->getNativeRate() << "Hz from audio file" << endl;
535 cerr << "(Note: Default may be overridden by transforms)" << endl; 611 cerr << "(Note: Default may be overridden by transforms)" << endl;
536 } 612 }
537 613
538 m_readyReaders[audioSource] = reader; 614 m_readyReaders[audioSource] = reader;
539 } 615 }
614 retrievalProgress.done(); 690 retrievalProgress.done();
615 } 691 }
616 if (!reader) { 692 if (!reader) {
617 throw FailedToOpenFile(source); 693 throw FailedToOpenFile(source);
618 } 694 }
619 return reader;
620 }
621
622 void
623 FeatureExtractionManager::extractFeaturesFor(AudioFileReader *reader,
624 QString audioSource)
625 {
626 // Note: This also deletes reader
627
628 cerr << "Audio file \"" << audioSource.toStdString() << "\": "
629 << reader->getChannelCount() << "ch at "
630 << reader->getNativeRate() << "Hz" << endl;
631 if (reader->getChannelCount() != m_channels || 695 if (reader->getChannelCount() != m_channels ||
632 reader->getNativeRate() != m_sampleRate) { 696 reader->getNativeRate() != m_sampleRate) {
633 cerr << "NOTE: File will be mixed or resampled for processing, to: " 697 cerr << "NOTE: File will be mixed or resampled for processing, to: "
634 << m_channels << "ch at " 698 << m_channels << "ch at "
635 << m_sampleRate << "Hz" << endl; 699 << m_sampleRate << "Hz" << endl;
636 } 700 }
701 return reader;
702 }
703
704 void
705 FeatureExtractionManager::extractFeaturesFor(AudioFileReader *reader,
706 QString audioSource)
707 {
708 // Note: This also deletes reader
709
710 cerr << "Audio file \"" << audioSource.toStdString() << "\": "
711 << reader->getChannelCount() << "ch at "
712 << reader->getNativeRate() << "Hz" << endl;
637 713
638 // allocate audio buffers 714 // allocate audio buffers
639 float **data = new float *[m_channels]; 715 float **data = new float *[m_channels];
640 for (int c = 0; c < m_channels; ++c) { 716 for (int c = 0; c < m_channels; ++c) {
641 data[c] = new float[m_blockSize]; 717 data[c] = new float[m_blockSize];
669 745
670 foreach (Plugin *plugin, m_orderedPlugins) { 746 foreach (Plugin *plugin, m_orderedPlugins) {
671 747
672 PluginMap::iterator pi = m_plugins.find(plugin); 748 PluginMap::iterator pi = m_plugins.find(plugin);
673 749
674 std::cerr << "Calling reset on " << plugin << std::endl; 750 // std::cerr << "Calling reset on " << plugin << std::endl;
675 plugin->reset(); 751 plugin->reset();
676 752
677 for (TransformWriterMap::iterator ti = pi->second.begin(); 753 for (TransformWriterMap::iterator ti = pi->second.begin();
678 ti != pi->second.end(); ++ti) { 754 ti != pi->second.end(); ++ti) {
679 755
850 if (!m_summariesOnly) { 926 if (!m_summariesOnly) {
851 writeFeatures(audioSource, plugin, featureSet); 927 writeFeatures(audioSource, plugin, featureSet);
852 } 928 }
853 929
854 if (!m_summaries.empty()) { 930 if (!m_summaries.empty()) {
931 // Summaries requested on the command line, for all transforms
855 PluginSummarisingAdapter *adapter = 932 PluginSummarisingAdapter *adapter =
856 dynamic_cast<PluginSummarisingAdapter *>(plugin); 933 dynamic_cast<PluginSummarisingAdapter *>(plugin);
857 if (!adapter) { 934 if (!adapter) {
858 cerr << "WARNING: Summaries requested, but plugin is not a summarising adapter" << endl; 935 cerr << "WARNING: Summaries requested, but plugin is not a summarising adapter" << endl;
859 } else { 936 } else {
866 //!!! on whether their features have duration or 943 //!!! on whether their features have duration or
867 //!!! not 944 //!!! not
868 featureSet = adapter->getSummaryForAllOutputs 945 featureSet = adapter->getSummaryForAllOutputs
869 (getSummaryType(*sni), 946 (getSummaryType(*sni),
870 PluginSummarisingAdapter::ContinuousTimeAverage); 947 PluginSummarisingAdapter::ContinuousTimeAverage);
871 writeFeatures(audioSource, plugin, featureSet,//!!! *sni); 948 writeFeatures(audioSource, plugin, featureSet,
872 Transform::stringToSummaryType(sni->c_str())); 949 Transform::stringToSummaryType(sni->c_str()));
873 } 950 }
874 } 951 }
875 } 952 }
876 953
954 // Summaries specified in transform definitions themselves
877 writeSummaries(audioSource, plugin); 955 writeSummaries(audioSource, plugin);
878 } 956 }
879 957
880 extractionProgress.done(); 958 extractionProgress.done();
881 959
893 for (TransformWriterMap::const_iterator ti = pi->second.begin(); 971 for (TransformWriterMap::const_iterator ti = pi->second.begin();
894 ti != pi->second.end(); ++ti) { 972 ti != pi->second.end(); ++ti) {
895 973
896 const Transform &transform = ti->first; 974 const Transform &transform = ti->first;
897 975
976 // cerr << "FeatureExtractionManager::writeSummaries: plugin is " << plugin
977 // << ", found transform: " << transform.toXmlString() << endl;
978
898 Transform::SummaryType summaryType = transform.getSummaryType(); 979 Transform::SummaryType summaryType = transform.getSummaryType();
899 PluginSummarisingAdapter::SummaryType pType = 980 PluginSummarisingAdapter::SummaryType pType =
900 (PluginSummarisingAdapter::SummaryType)summaryType; 981 (PluginSummarisingAdapter::SummaryType)summaryType;
901 982
902 if (transform.getSummaryType() == Transform::NoSummary) { 983 if (transform.getSummaryType() == Transform::NoSummary) {
984 // cerr << "(no summary, continuing)" << endl;
903 continue; 985 continue;
904 } 986 }
905 987
906 PluginSummarisingAdapter *adapter = 988 PluginSummarisingAdapter *adapter =
907 dynamic_cast<PluginSummarisingAdapter *>(plugin); 989 dynamic_cast<PluginSummarisingAdapter *>(plugin);
911 } 993 }
912 994
913 Plugin::FeatureSet featureSet = adapter->getSummaryForAllOutputs 995 Plugin::FeatureSet featureSet = adapter->getSummaryForAllOutputs
914 (pType, PluginSummarisingAdapter::ContinuousTimeAverage); 996 (pType, PluginSummarisingAdapter::ContinuousTimeAverage);
915 997
916 // cout << "summary type " << int(pType) << " for transform:" << endl << transform.toXmlString().toStdString()<< endl << "... feature set with " << featureSet.size() << " elts" << endl; 998 // cerr << "summary type " << int(pType) << " for transform:" << endl << transform.toXmlString().toStdString()<< endl << "... feature set with " << featureSet.size() << " elts" << endl;
917 999
918 writeFeatures(audioSource, plugin, featureSet, summaryType); 1000 writeFeatures(audioSource, plugin, featureSet, summaryType);
919 } 1001 }
920 } 1002 }
921 1003
925 Transform::SummaryType summaryType) 1007 Transform::SummaryType summaryType)
926 { 1008 {
927 // caller should have ensured plugin is in m_plugins 1009 // caller should have ensured plugin is in m_plugins
928 PluginMap::iterator pi = m_plugins.find(plugin); 1010 PluginMap::iterator pi = m_plugins.find(plugin);
929 1011
1012 // Write features from the feature set passed in, according to the
1013 // transforms listed for the given plugin with the given summary type
1014
930 for (TransformWriterMap::const_iterator ti = pi->second.begin(); 1015 for (TransformWriterMap::const_iterator ti = pi->second.begin();
931 ti != pi->second.end(); ++ti) { 1016 ti != pi->second.end(); ++ti) {
932 1017
933 const Transform &transform = ti->first; 1018 const Transform &transform = ti->first;
934 const vector<FeatureWriter *> &writers = ti->second; 1019 const vector<FeatureWriter *> &writers = ti->second;
935 1020
936 if (transform.getSummaryType() != Transform::NoSummary && 1021 // cerr << "writeFeatures: plugin " << plugin << " has transform: " << transform.toXmlString() << endl;
937 m_summaries.empty() && 1022
938 summaryType == Transform::NoSummary) { 1023 if (transform.getSummaryType() == Transform::NoSummary &&
939 continue; 1024 !m_summaries.empty()) {
940 } 1025 // cerr << "transform has no summary, but summaries requested on command line, so going for it anyway" << endl;
941 1026 } else if (transform.getSummaryType() != summaryType) {
942 if (transform.getSummaryType() != Transform::NoSummary && 1027 // Either we're not writing a summary and the transform
943 summaryType != Transform::NoSummary && 1028 // has one, or we're writing a summary but the transform
944 transform.getSummaryType() != summaryType) { 1029 // has none or a different one; either way, skip it
1030 // cerr << "summary type differs from passed-in one " << summaryType << endl;
945 continue; 1031 continue;
946 } 1032 }
947 1033
948 string outputId = transform.getOutput().toStdString(); 1034 string outputId = transform.getOutput().toStdString();
949 1035
957 1043
958 int outputIndex = m_pluginOutputIndices[outputId]; 1044 int outputIndex = m_pluginOutputIndices[outputId];
959 Plugin::FeatureSet::const_iterator fsi = features.find(outputIndex); 1045 Plugin::FeatureSet::const_iterator fsi = features.find(outputIndex);
960 if (fsi == features.end()) continue; 1046 if (fsi == features.end()) continue;
961 1047
1048 // cerr << "this transform has " << writers.size() << " writer(s)" << endl;
1049
962 for (int j = 0; j < (int)writers.size(); ++j) { 1050 for (int j = 0; j < (int)writers.size(); ++j) {
963 writers[j]->write 1051 writers[j]->write
964 (audioSource, transform, desc, fsi->second, 1052 (audioSource, transform, desc, fsi->second,
965 Transform::summaryTypeToString(summaryType).toStdString()); 1053 Transform::summaryTypeToString(summaryType).toStdString());
966 } 1054 }