annotate transform/CSVFeatureWriter.cpp @ 1001:51bf067de517

Add fill-ends option to CSV writer (and test it)
author Chris Cannam
date Wed, 15 Oct 2014 10:18:13 +0100
parents ec6e69373997
children c2316a3bbb81
rev   line source
Chris@498 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@498 2
Chris@498 3 /*
Chris@498 4 Sonic Visualiser
Chris@498 5 An audio file viewer and annotation editor.
Chris@498 6
Chris@498 7 Sonic Annotator
Chris@498 8 A utility for batch feature extraction from audio files.
Chris@498 9
Chris@498 10 Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
Chris@498 11 Copyright 2007-2008 QMUL.
Chris@498 12
Chris@498 13 This program is free software; you can redistribute it and/or
Chris@498 14 modify it under the terms of the GNU General Public License as
Chris@498 15 published by the Free Software Foundation; either version 2 of the
Chris@498 16 License, or (at your option) any later version. See the file
Chris@498 17 COPYING included with this distribution for more information.
Chris@498 18 */
Chris@498 19
Chris@498 20 #include "CSVFeatureWriter.h"
Chris@498 21
Chris@498 22 #include <iostream>
Chris@498 23
Chris@498 24 #include <QRegExp>
Chris@498 25 #include <QTextStream>
Chris@498 26
Chris@498 27 using namespace std;
Chris@498 28 using namespace Vamp;
Chris@498 29
Chris@498 30 CSVFeatureWriter::CSVFeatureWriter() :
Chris@498 31 FileFeatureWriter(SupportOneFilePerTrackTransform |
Chris@997 32 SupportOneFileTotal |
Chris@997 33 SupportStdOut,
Chris@498 34 "csv"),
Chris@669 35 m_separator(","),
Chris@1000 36 m_sampleTiming(false),
Chris@1001 37 m_endTimes(false),
Chris@1001 38 m_forceEnd(false)
Chris@498 39 {
Chris@498 40 }
Chris@498 41
Chris@498 42 CSVFeatureWriter::~CSVFeatureWriter()
Chris@498 43 {
Chris@498 44 }
Chris@498 45
Chris@998 46 string
Chris@998 47 CSVFeatureWriter::getDescription() const
Chris@998 48 {
Chris@998 49 return "Write features in comma-separated (CSV) format. If transforms are being written to a single file or to stdout, the first column in the output will contain the input audio filename, or an empty string if the feature hails from the same audio file as its predecessor. If transforms are being written to multiple files, the audio filename column will be omitted. Subsequent columns will contain the feature timestamp, then any or all of duration, values, and label.";
Chris@998 50 }
Chris@998 51
Chris@498 52 CSVFeatureWriter::ParameterList
Chris@498 53 CSVFeatureWriter::getSupportedParameters() const
Chris@498 54 {
Chris@498 55 ParameterList pl = FileFeatureWriter::getSupportedParameters();
Chris@498 56 Parameter p;
Chris@498 57
Chris@498 58 p.name = "separator";
Chris@498 59 p.description = "Column separator for output. Default is \",\" (comma).";
Chris@498 60 p.hasArg = true;
Chris@498 61 pl.push_back(p);
Chris@669 62
Chris@669 63 p.name = "sample-timing";
Chris@669 64 p.description = "Show timings as sample frame counts instead of in seconds.";
Chris@669 65 p.hasArg = false;
Chris@669 66 pl.push_back(p);
Chris@1000 67
Chris@1000 68 p.name = "end-times";
Chris@1000 69 p.description = "Show start and end time instead of start and duration, for features with duration.";
Chris@1000 70 p.hasArg = false;
Chris@1000 71 pl.push_back(p);
Chris@498 72
Chris@1001 73 p.name = "fill-ends";
Chris@1001 74 p.description = "Include durations (or end times) even for features without duration, by using the gap to the next feature instead.";
Chris@1001 75 p.hasArg = false;
Chris@1001 76 pl.push_back(p);
Chris@1001 77
Chris@498 78 return pl;
Chris@498 79 }
Chris@498 80
Chris@498 81 void
Chris@498 82 CSVFeatureWriter::setParameters(map<string, string> &params)
Chris@498 83 {
Chris@498 84 FileFeatureWriter::setParameters(params);
Chris@498 85
Chris@690 86 SVDEBUG << "CSVFeatureWriter::setParameters" << endl;
Chris@498 87 for (map<string, string>::iterator i = params.begin();
Chris@498 88 i != params.end(); ++i) {
Chris@498 89 cerr << i->first << " -> " << i->second << endl;
Chris@498 90 if (i->first == "separator") {
Chris@498 91 m_separator = i->second.c_str();
Chris@669 92 } else if (i->first == "sample-timing") {
Chris@669 93 m_sampleTiming = true;
Chris@1000 94 } else if (i->first == "end-times") {
Chris@1000 95 m_endTimes = true;
Chris@1001 96 } else if (i->first == "fill-ends") {
Chris@1001 97 m_forceEnd = true;
Chris@498 98 }
Chris@498 99 }
Chris@498 100 }
Chris@498 101
Chris@498 102 void
Chris@498 103 CSVFeatureWriter::write(QString trackId,
Chris@498 104 const Transform &transform,
Chris@930 105 const Plugin::OutputDescriptor& ,
Chris@498 106 const Plugin::FeatureList& features,
Chris@498 107 std::string summaryType)
Chris@498 108 {
Chris@1001 109 TransformId transformId = transform.getIdentifier();
Chris@1001 110
Chris@498 111 // Select appropriate output file for our track/transform
Chris@498 112 // combination
Chris@498 113
Chris@1001 114 QTextStream *sptr = getOutputStream(trackId, transformId);
Chris@604 115 if (!sptr) {
Chris@1001 116 throw FailedToOpenOutputStream(trackId, transformId);
Chris@604 117 }
Chris@498 118
Chris@498 119 QTextStream &stream = *sptr;
Chris@498 120
Chris@1001 121 int n = features.size();
Chris@498 122
Chris@1001 123 if (n == 0) return;
Chris@1001 124
Chris@1001 125 TrackTransformPair tt(trackId, transformId);
Chris@1001 126
Chris@1001 127 if (m_rates.find(transformId) == m_rates.end()) {
Chris@1001 128 m_rates[transformId] = transform.getSampleRate();
Chris@1001 129 }
Chris@1001 130
Chris@1001 131 if (m_pending.find(tt) != m_pending.end()) {
Chris@1001 132 writeFeature(tt,
Chris@1001 133 stream,
Chris@1001 134 m_pending[tt],
Chris@1001 135 &features[0],
Chris@1001 136 m_pendingSummaryTypes[tt]);
Chris@1001 137 m_pending.erase(tt);
Chris@1001 138 m_pendingSummaryTypes.erase(tt);
Chris@1001 139 }
Chris@1001 140
Chris@1001 141 if (m_forceEnd) {
Chris@1001 142 // can't write final feature until we know its end time
Chris@1001 143 --n;
Chris@1001 144 m_pending[tt] = features[n];
Chris@1001 145 m_pendingSummaryTypes[tt] = summaryType;
Chris@1001 146 }
Chris@1001 147
Chris@1001 148 for (int i = 0; i < n; ++i) {
Chris@1001 149 writeFeature(tt,
Chris@1001 150 stream,
Chris@1001 151 features[i],
Chris@1001 152 m_forceEnd ? &features[i+1] : 0,
Chris@1001 153 summaryType);
Chris@1001 154 }
Chris@1001 155 }
Chris@1001 156
Chris@1001 157 void
Chris@1001 158 CSVFeatureWriter::finish()
Chris@1001 159 {
Chris@1001 160 for (PendingFeatures::const_iterator i = m_pending.begin();
Chris@1001 161 i != m_pending.end(); ++i) {
Chris@1001 162 TrackTransformPair tt = i->first;
Chris@1001 163 Plugin::Feature f = i->second;
Chris@1001 164 QTextStream *sptr = getOutputStream(tt.first, tt.second);
Chris@1001 165 if (!sptr) {
Chris@1001 166 throw FailedToOpenOutputStream(tt.first, tt.second);
Chris@1001 167 }
Chris@1001 168 QTextStream &stream = *sptr;
Chris@1001 169 // final feature has its own time as end time (we can't
Chris@1001 170 // reliably determine the end of audio file, and because of
Chris@1001 171 // the nature of block processing, the feature could even
Chris@1001 172 // start beyond that anyway)
Chris@1001 173 writeFeature(tt, stream, f, &f, m_pendingSummaryTypes[tt]);
Chris@1001 174 }
Chris@1001 175
Chris@1001 176 m_pending.clear();
Chris@1001 177 }
Chris@1001 178
Chris@1001 179 void
Chris@1001 180 CSVFeatureWriter::writeFeature(TrackTransformPair tt,
Chris@1001 181 QTextStream &stream,
Chris@1001 182 const Plugin::Feature &f,
Chris@1001 183 const Plugin::Feature *optionalNextFeature,
Chris@1001 184 std::string summaryType)
Chris@1001 185 {
Chris@1001 186 QString trackId = tt.first;
Chris@1001 187 TransformId transformId = tt.second;
Chris@1001 188
Chris@1001 189 if (m_stdout || m_singleFileName != "") {
Chris@1001 190 if (trackId != m_prevPrintedTrackId) {
Chris@1001 191 stream << "\"" << trackId << "\"" << m_separator;
Chris@1001 192 m_prevPrintedTrackId = trackId;
Chris@1001 193 } else {
Chris@1001 194 stream << m_separator;
Chris@1001 195 }
Chris@1001 196 }
Chris@1001 197
Chris@1001 198 Vamp::RealTime duration;
Chris@1001 199 bool haveDuration = true;
Chris@1001 200
Chris@1001 201 if (f.hasDuration) {
Chris@1001 202 duration = f.duration;
Chris@1001 203 } else if (optionalNextFeature) {
Chris@1001 204 duration = optionalNextFeature->timestamp - f.timestamp;
Chris@1001 205 } else {
Chris@1001 206 haveDuration = false;
Chris@1001 207 }
Chris@1001 208
Chris@1001 209 if (m_sampleTiming) {
Chris@1001 210
Chris@1001 211 float rate = m_rates[transformId];
Chris@1001 212
Chris@1001 213 stream << Vamp::RealTime::realTime2Frame(f.timestamp, rate);
Chris@1001 214
Chris@1001 215 if (haveDuration) {
Chris@1001 216 stream << m_separator;
Chris@1001 217 if (m_endTimes) {
Chris@1001 218 stream << Vamp::RealTime::realTime2Frame
Chris@1001 219 (f.timestamp + duration, rate);
Chris@514 220 } else {
Chris@1001 221 stream << Vamp::RealTime::realTime2Frame(duration, rate);
Chris@514 222 }
Chris@514 223 }
Chris@514 224
Chris@1001 225 } else {
Chris@498 226
Chris@1001 227 QString timestamp = f.timestamp.toString().c_str();
Chris@1001 228 timestamp.replace(QRegExp("^ +"), "");
Chris@1001 229 stream << timestamp;
Chris@669 230
Chris@1001 231 if (haveDuration) {
Chris@1001 232 if (m_endTimes) {
Chris@1001 233 QString endtime =
Chris@1001 234 (f.timestamp + duration).toString().c_str();
Chris@1001 235 endtime.replace(QRegExp("^ +"), "");
Chris@1001 236 stream << m_separator << endtime;
Chris@1001 237 } else {
Chris@1001 238 QString d = duration.toString().c_str();
Chris@1001 239 d.replace(QRegExp("^ +"), "");
Chris@1001 240 stream << m_separator << d;
Chris@669 241 }
Chris@1001 242 }
Chris@1001 243 }
Chris@669 244
Chris@1001 245 if (summaryType != "") {
Chris@1001 246 stream << m_separator << summaryType.c_str();
Chris@498 247 }
Chris@1001 248
Chris@1001 249 for (unsigned int j = 0; j < f.values.size(); ++j) {
Chris@1001 250 stream << m_separator << f.values[j];
Chris@1001 251 }
Chris@1001 252
Chris@1001 253 if (f.label != "") {
Chris@1001 254 stream << m_separator << "\"" << f.label.c_str() << "\"";
Chris@1001 255 }
Chris@1001 256
Chris@1001 257 stream << "\n";
Chris@498 258 }
Chris@498 259
Chris@498 260