changeset 154:6ff4da31db8b labfile

Implement .lab file writer
author Chris Cannam
date Tue, 14 Oct 2014 17:30:44 +0100
parents 04945e74d314
children 6dc824c3f5e1
files .hgsubstate runner.pro runner/FeatureWriterFactory.cpp runner/LabFeatureWriter.cpp runner/LabFeatureWriter.h tests/test-lab-destinations/test-lab-destinations.sh tests/test-lab-destinations/transforms/detectionfunction.n3 tests/test-lab-destinations/transforms/onsets.n3 tests/test.sh
diffstat 9 files changed, 342 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/.hgsubstate	Tue Oct 14 11:13:31 2014 +0100
+++ b/.hgsubstate	Tue Oct 14 17:30:44 2014 +0100
@@ -1,3 +1,3 @@
 d16f0fd6db6104d87882bc43788a3bb1b0f8c528 dataquay
 879bdc878826bebec67130326f99397c430419b1 sv-dependency-builds
-694301cc71ccb10c8cc5a729c9ffb861891589c2 svcore
+ec6e69373997482c65b68a10dac1b8a0b5d13dac svcore
--- a/runner.pro	Tue Oct 14 11:13:31 2014 +0100
+++ b/runner.pro	Tue Oct 14 17:30:44 2014 +0100
@@ -84,6 +84,7 @@
         runner/FeatureWriterFactory.h  \
         runner/DefaultFeatureWriter.h \
         runner/FeatureExtractionManager.h \
+        runner/LabFeatureWriter.h \
         runner/MIDIFeatureWriter.h \
         runner/MultiplexedReader.h
 
@@ -93,6 +94,7 @@
 	runner/FeatureExtractionManager.cpp \
         runner/AudioDBFeatureWriter.cpp \
         runner/FeatureWriterFactory.cpp \
+        runner/LabFeatureWriter.cpp \
         runner/MIDIFeatureWriter.cpp \
         runner/MultiplexedReader.cpp
 
--- a/runner/FeatureWriterFactory.cpp	Tue Oct 14 11:13:31 2014 +0100
+++ b/runner/FeatureWriterFactory.cpp	Tue Oct 14 17:30:44 2014 +0100
@@ -17,10 +17,13 @@
 #include "FeatureWriterFactory.h"
 
 #include "DefaultFeatureWriter.h"
+
 #include "rdf/RDFFeatureWriter.h"
+#include "transform/CSVFeatureWriter.h"
+
 #include "AudioDBFeatureWriter.h"
 #include "MIDIFeatureWriter.h"
-#include "transform/CSVFeatureWriter.h"
+#include "LabFeatureWriter.h"
 
 set<string>
 FeatureWriterFactory::getWriterTags()
@@ -30,6 +33,7 @@
     tags.insert("rdf");
     tags.insert("audiodb");
     tags.insert("csv");
+    tags.insert("lab");
     tags.insert("midi");
     return tags;
 }
@@ -45,6 +49,8 @@
         return new AudioDBFeatureWriter();
     } else if (tag == "csv") {
         return new CSVFeatureWriter();
+    } else if (tag == "lab") {
+        return new LabFeatureWriter();
     } else if (tag == "midi") {
         return new MIDIFeatureWriter();
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/runner/LabFeatureWriter.cpp	Tue Oct 14 17:30:44 2014 +0100
@@ -0,0 +1,135 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+
+    Sonic Annotator
+    A utility for batch feature extraction from audio files.
+
+    Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
+    Copyright 2007-2008 QMUL.
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "LabFeatureWriter.h"
+
+#include <iostream>
+
+#include <QRegExp>
+#include <QTextStream>
+
+using namespace std;
+using namespace Vamp;
+
+LabFeatureWriter::LabFeatureWriter() :
+    FileFeatureWriter(SupportOneFilePerTrackTransform |
+                      SupportStdOut,
+                      "lab"),
+    m_forceEnd(false)
+{
+}
+
+LabFeatureWriter::~LabFeatureWriter()
+{
+}
+
+string
+LabFeatureWriter::getDescription() const
+{
+    return "Write features in .lab, a tab-separated columnar format. The first column is always the feature start time in seconds. If the features have duration, the second column will be the feature end time in seconds. Remaining columns are the feature values (if any) and finally the feature label (if any). There is no identification of the audio file or the transform, so confusion will result if features from different audio or transforms are mixed. For more control over the output, consider using the CSV writer.";
+}
+
+LabFeatureWriter::ParameterList
+LabFeatureWriter::getSupportedParameters() const
+{
+    ParameterList pl = FileFeatureWriter::getSupportedParameters();
+
+    Parameter p;
+
+    p.name = "fill-ends";
+    p.description = "Include end times even for features without duration, by using the gap to the next feature instead.";
+    p.hasArg = false;
+    pl.push_back(p);
+
+    return pl;
+}
+
+void
+LabFeatureWriter::setParameters(map<string, string> &params)
+{
+    FileFeatureWriter::setParameters(params);
+
+    for (map<string, string>::iterator i = params.begin();
+         i != params.end(); ++i) {
+        if (i->first == "fill-ends") {
+            m_forceEnd = true;
+        }
+    }
+}
+
+void
+LabFeatureWriter::write(QString trackId,
+                        const Transform &transform,
+                        const Plugin::OutputDescriptor& ,
+                        const Plugin::FeatureList& features,
+                        std::string summaryType)
+{
+    // Select appropriate output file for our track/transform
+    // combination
+
+    QTextStream *sptr = getOutputStream(trackId, transform.getIdentifier());
+    if (!sptr) {
+        throw FailedToOpenOutputStream(trackId, transform.getIdentifier());
+    }
+
+    QTextStream &stream = *sptr;
+
+    QString sep = "\t";
+
+    for (unsigned int i = 0; i < features.size(); ++i) {
+
+        QString timestamp = features[i].timestamp.toString().c_str();
+        timestamp.replace(QRegExp("^ +"), "");
+        stream << timestamp;
+
+        Vamp::RealTime endTime;
+        bool haveEndTime = true;
+
+        if (features[i].hasDuration) {
+            endTime = features[i].timestamp + features[i].duration;
+        } else if (m_forceEnd) {
+            if (i+1 < features.size()) {
+                endTime = features[i+1].timestamp;
+            } else {
+                //!!! what to do??? can we get the end time of the input file?
+                endTime = features[i].timestamp;
+            }
+        } else {
+            haveEndTime = false;
+        }
+
+        if (haveEndTime) {
+            QString e = endTime.toString().c_str();
+            e.replace(QRegExp("^ +"), "");
+            stream << sep << e;
+        }
+
+        for (unsigned int j = 0; j < features[i].values.size(); ++j) {
+            stream << sep << features[i].values[j];
+        }
+
+        if (features[i].label != "") {
+            stream << sep << "\"" << features[i].label.c_str() << "\"";
+        }
+
+        stream << "\n";
+    }
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/runner/LabFeatureWriter.h	Tue Oct 14 17:30:44 2014 +0100
@@ -0,0 +1,60 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+
+    Sonic Annotator
+    A utility for batch feature extraction from audio files.
+
+    Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
+    Copyright 2007-2008 QMUL.
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef _LAB_FEATURE_WRITER_H_
+#define _LAB_FEATURE_WRITER_H_
+
+#include <string>
+#include <map>
+#include <set>
+
+#include <QString>
+
+#include "transform/FileFeatureWriter.h"
+
+using std::string;
+using std::map;
+
+class QTextStream;
+class QFile;
+
+class LabFeatureWriter : public FileFeatureWriter
+{
+public:
+    LabFeatureWriter();
+    virtual ~LabFeatureWriter();
+
+    virtual string getDescription() const;
+
+    virtual ParameterList getSupportedParameters() const;
+    virtual void setParameters(map<string, string> &params);
+
+    virtual void write(QString trackid,
+                       const Transform &transform,
+                       const Vamp::Plugin::OutputDescriptor &output,
+                       const Vamp::Plugin::FeatureList &features,
+                       std::string summaryType = "");
+
+    virtual QString getWriterTag() const { return "lab"; }
+
+private:
+    bool m_forceEnd;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-lab-destinations/test-lab-destinations.sh	Tue Oct 14 17:30:44 2014 +0100
@@ -0,0 +1,115 @@
+#!/bin/bash
+
+. ../include.sh
+
+infile1=$audiopath/3clicks8.wav
+infile2=$audiopath/6clicks8.wav
+
+outfile1=$audiopath/3clicks8_vamp_vamp-example-plugins_percussiononsets_onsets.lab
+outfile2=$audiopath/6clicks8_vamp_vamp-example-plugins_percussiononsets_onsets.lab
+
+infile1dot=$audiopath/3.clicks.8.wav
+outfile1dot=$audiopath/3.clicks.8_vamp_vamp-example-plugins_percussiononsets_onsets.lab
+
+outfile3=$audiopath/3clicks8_vamp_vamp-example-plugins_percussiononsets_onsets.lab
+outfile4=$audiopath/3clicks8_vamp_vamp-example-plugins_percussiononsets_detectionfunction.lab
+
+tmplab=$mypath/tmp_1_$$.lab
+
+trap "rm -f $tmplab $outfile1 $outfile2 $outfile3 $outfile4 $infile1dot $outfile1dot" 0
+
+transformdir=$mypath/transforms
+
+check_lab() {
+    test -f $1 || \
+	fail "Fails to write output to expected location $1 for $2"
+    # every line must contain the same number of tabs
+    formats=`awk -F'\t' '{ print NF; }' $1 | sort | uniq | wc | awk '{ print $1 }'`
+    if [ "$formats" != "1" ]; then
+	fail "Output is not consistently formatted tab-separated file for $2"
+    fi
+    rm -f $1
+}    
+
+
+ctx="onsets transform, one audio file, default LAB writer destination"
+
+rm -f $outfile1
+
+$r -t $transformdir/onsets.n3 -w lab $infile1 2>/dev/null || \
+    fail "Fails to run with $ctx"
+
+check_lab $outfile1 "$ctx"
+
+
+ctx="onsets transform, one audio file with dots in filename, default LAB writer destination"
+
+rm -f $outfile1
+
+cp $infile1 $infile1dot
+
+$r -t $transformdir/onsets.n3 -w lab $infile1dot 2>/dev/null || \
+    fail "Fails to run with $ctx"
+
+check_lab $outfile1dot "$ctx"
+
+rm -f $infile1dot $outfile1dot
+
+
+ctx="onsets and df transforms, one audio file, default LAB writer destination"
+
+rm -f $outfile1
+
+$r -t $transformdir/onsets.n3 -t $transformdir/detectionfunction.n3 -w lab $infile1 2>/dev/null || \
+    fail "Fails to run with $ctx"
+
+check_lab $outfile1 "$ctx"
+
+
+ctx="onsets transform, two audio files, default LAB writer destination"
+
+rm -f $outfile1
+rm -f $outfile2
+
+$r -t $transformdir/onsets.n3 -w lab $infile1 $infile2 2>/dev/null || \
+    fail "Fails to run with $ctx"
+
+check_lab $outfile1 "$ctx"
+check_lab $outfile2 "$ctx"
+
+
+ctx="onsets transform, two audio files, one-file LAB writer"
+
+# Writer should refuse to write results from more than one audio file
+# together (as the audio file is not identified)
+
+$r -t $transformdir/onsets.n3 -w lab --lab-one-file $tmplab $infile1 $infile2 2>/dev/null && \
+    fail "Fails by completing successfully with $ctx"
+
+
+ctx="onsets transform, two audio files, stdout LAB writer"
+
+$r -t $transformdir/onsets.n3 -w lab --lab-stdout $infile1 $infile2 2>/dev/null >$tmplab || \
+    fail "Fails to run with $ctx"
+
+check_lab $tmplab "$ctx"
+
+
+ctx="existing output file and no --lab-force"
+
+touch $outfile1
+
+$r -t $transformdir/onsets.n3 -w lab $infile1 2>/dev/null && \
+    fail "Fails by completing successfully when output file already exists (should refuse and bail out)"
+
+
+ctx="existing output file and --lab-force"
+
+touch $outfile1
+
+$r -t $transformdir/onsets.n3 -w lab --lab-force $infile1 2>/dev/null || \
+    fail "Fails to run with $ctx"
+
+check_lab $outfile1 "$ctx"
+
+exit 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-lab-destinations/transforms/detectionfunction.n3	Tue Oct 14 17:30:44 2014 +0100
@@ -0,0 +1,11 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
+@prefix vamp: <http://purl.org/ontology/vamp/>.
+@prefix examples: <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#>.
+@prefix : <#>.
+
+:transform0 a vamp:Transform;
+	vamp:plugin examples:percussiononsets ;
+	vamp:output examples:percussiononsets_output_detectionfunction .
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-lab-destinations/transforms/onsets.n3	Tue Oct 14 17:30:44 2014 +0100
@@ -0,0 +1,10 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
+@prefix vamp: <http://purl.org/ontology/vamp/>.
+@prefix examples: <http://vamp-plugins.org/rdf/plugins/vamp-example-plugins#>.
+@prefix : <#>.
+
+:transform0 a vamp:Transform;
+	vamp:plugin examples:percussiononsets.
+
+
+
--- a/tests/test.sh	Tue Oct 14 11:13:31 2014 +0100
+++ b/tests/test.sh	Tue Oct 14 17:30:44 2014 +0100
@@ -12,6 +12,7 @@
     rdf-writer \
     rdf-destinations \
     csv-destinations \
+    lab-destinations \
     midi-destinations \
     summaries \
     multiple-audio \