annotate transform/CSVFeatureWriter.cpp @ 1002:c2316a3bbb81

Add omit-filename option to CSV writer (and test it while testing lab writer)
author Chris Cannam
date Wed, 15 Oct 2014 10:38:17 +0100
parents 51bf067de517
children d954e03274e8
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@1002 38 m_forceEnd(false),
Chris@1002 39 m_omitFilename(false)
Chris@498 40 {
Chris@498 41 }
Chris@498 42
Chris@498 43 CSVFeatureWriter::~CSVFeatureWriter()
Chris@498 44 {
Chris@498 45 }
Chris@498 46
Chris@998 47 string
Chris@998 48 CSVFeatureWriter::getDescription() const
Chris@998 49 {
Chris@998 50 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 51 }
Chris@998 52
Chris@498 53 CSVFeatureWriter::ParameterList
Chris@498 54 CSVFeatureWriter::getSupportedParameters() const
Chris@498 55 {
Chris@498 56 ParameterList pl = FileFeatureWriter::getSupportedParameters();
Chris@498 57 Parameter p;
Chris@498 58
Chris@498 59 p.name = "separator";
Chris@498 60 p.description = "Column separator for output. Default is \",\" (comma).";
Chris@498 61 p.hasArg = true;
Chris@498 62 pl.push_back(p);
Chris@669 63
Chris@1002 64 p.name = "omit-filename";
Chris@1002 65 p.description = "Omit the filename column. May result in confusion if sending more than one audio file's features to the same CSV output.";
Chris@1002 66 p.hasArg = false;
Chris@1002 67 pl.push_back(p);
Chris@1002 68
Chris@669 69 p.name = "sample-timing";
Chris@669 70 p.description = "Show timings as sample frame counts instead of in seconds.";
Chris@669 71 p.hasArg = false;
Chris@669 72 pl.push_back(p);
Chris@1000 73
Chris@1000 74 p.name = "end-times";
Chris@1000 75 p.description = "Show start and end time instead of start and duration, for features with duration.";
Chris@1000 76 p.hasArg = false;
Chris@1000 77 pl.push_back(p);
Chris@498 78
Chris@1001 79 p.name = "fill-ends";
Chris@1001 80 p.description = "Include durations (or end times) even for features without duration, by using the gap to the next feature instead.";
Chris@1001 81 p.hasArg = false;
Chris@1001 82 pl.push_back(p);
Chris@1001 83
Chris@498 84 return pl;
Chris@498 85 }
Chris@498 86
Chris@498 87 void
Chris@498 88 CSVFeatureWriter::setParameters(map<string, string> &params)
Chris@498 89 {
Chris@498 90 FileFeatureWriter::setParameters(params);
Chris@498 91
Chris@690 92 SVDEBUG << "CSVFeatureWriter::setParameters" << endl;
Chris@498 93 for (map<string, string>::iterator i = params.begin();
Chris@498 94 i != params.end(); ++i) {
Chris@498 95 cerr << i->first << " -> " << i->second << endl;
Chris@498 96 if (i->first == "separator") {
Chris@498 97 m_separator = i->second.c_str();
Chris@1002 98 cerr << "m_separator = " << m_separator << endl;
Chris@1002 99 if (m_separator == "\\t") {
Chris@1002 100 m_separator = QChar::Tabulation;
Chris@1002 101 }
Chris@669 102 } else if (i->first == "sample-timing") {
Chris@669 103 m_sampleTiming = true;
Chris@1000 104 } else if (i->first == "end-times") {
Chris@1000 105 m_endTimes = true;
Chris@1001 106 } else if (i->first == "fill-ends") {
Chris@1001 107 m_forceEnd = true;
Chris@1002 108 } else if (i->first == "omit-filename") {
Chris@1002 109 m_omitFilename = true;
Chris@498 110 }
Chris@498 111 }
Chris@498 112 }
Chris@498 113
Chris@498 114 void
Chris@498 115 CSVFeatureWriter::write(QString trackId,
Chris@498 116 const Transform &transform,
Chris@930 117 const Plugin::OutputDescriptor& ,
Chris@498 118 const Plugin::FeatureList& features,
Chris@498 119 std::string summaryType)
Chris@498 120 {
Chris@1001 121 TransformId transformId = transform.getIdentifier();
Chris@1001 122
Chris@498 123 // Select appropriate output file for our track/transform
Chris@498 124 // combination
Chris@498 125
Chris@1001 126 QTextStream *sptr = getOutputStream(trackId, transformId);
Chris@604 127 if (!sptr) {
Chris@1001 128 throw FailedToOpenOutputStream(trackId, transformId);
Chris@604 129 }
Chris@498 130
Chris@498 131 QTextStream &stream = *sptr;
Chris@498 132
Chris@1001 133 int n = features.size();
Chris@498 134
Chris@1001 135 if (n == 0) return;
Chris@1001 136
Chris@1001 137 TrackTransformPair tt(trackId, transformId);
Chris@1001 138
Chris@1001 139 if (m_rates.find(transformId) == m_rates.end()) {
Chris@1001 140 m_rates[transformId] = transform.getSampleRate();
Chris@1001 141 }
Chris@1001 142
Chris@1001 143 if (m_pending.find(tt) != m_pending.end()) {
Chris@1001 144 writeFeature(tt,
Chris@1001 145 stream,
Chris@1001 146 m_pending[tt],
Chris@1001 147 &features[0],
Chris@1001 148 m_pendingSummaryTypes[tt]);
Chris@1001 149 m_pending.erase(tt);
Chris@1001 150 m_pendingSummaryTypes.erase(tt);
Chris@1001 151 }
Chris@1001 152
Chris@1001 153 if (m_forceEnd) {
Chris@1001 154 // can't write final feature until we know its end time
Chris@1001 155 --n;
Chris@1001 156 m_pending[tt] = features[n];
Chris@1001 157 m_pendingSummaryTypes[tt] = summaryType;
Chris@1001 158 }
Chris@1001 159
Chris@1001 160 for (int i = 0; i < n; ++i) {
Chris@1001 161 writeFeature(tt,
Chris@1001 162 stream,
Chris@1001 163 features[i],
Chris@1001 164 m_forceEnd ? &features[i+1] : 0,
Chris@1001 165 summaryType);
Chris@1001 166 }
Chris@1001 167 }
Chris@1001 168
Chris@1001 169 void
Chris@1001 170 CSVFeatureWriter::finish()
Chris@1001 171 {
Chris@1001 172 for (PendingFeatures::const_iterator i = m_pending.begin();
Chris@1001 173 i != m_pending.end(); ++i) {
Chris@1001 174 TrackTransformPair tt = i->first;
Chris@1001 175 Plugin::Feature f = i->second;
Chris@1001 176 QTextStream *sptr = getOutputStream(tt.first, tt.second);
Chris@1001 177 if (!sptr) {
Chris@1001 178 throw FailedToOpenOutputStream(tt.first, tt.second);
Chris@1001 179 }
Chris@1001 180 QTextStream &stream = *sptr;
Chris@1001 181 // final feature has its own time as end time (we can't
Chris@1001 182 // reliably determine the end of audio file, and because of
Chris@1001 183 // the nature of block processing, the feature could even
Chris@1001 184 // start beyond that anyway)
Chris@1001 185 writeFeature(tt, stream, f, &f, m_pendingSummaryTypes[tt]);
Chris@1001 186 }
Chris@1001 187
Chris@1001 188 m_pending.clear();
Chris@1001 189 }
Chris@1001 190
Chris@1001 191 void
Chris@1001 192 CSVFeatureWriter::writeFeature(TrackTransformPair tt,
Chris@1001 193 QTextStream &stream,
Chris@1001 194 const Plugin::Feature &f,
Chris@1001 195 const Plugin::Feature *optionalNextFeature,
Chris@1001 196 std::string summaryType)
Chris@1001 197 {
Chris@1001 198 QString trackId = tt.first;
Chris@1001 199 TransformId transformId = tt.second;
Chris@1001 200
Chris@1002 201 if (!m_omitFilename) {
Chris@1002 202 if (m_stdout || m_singleFileName != "") {
Chris@1002 203 if (trackId != m_prevPrintedTrackId) {
Chris@1002 204 stream << "\"" << trackId << "\"" << m_separator;
Chris@1002 205 m_prevPrintedTrackId = trackId;
Chris@1002 206 } else {
Chris@1002 207 stream << m_separator;
Chris@1002 208 }
Chris@1001 209 }
Chris@1001 210 }
Chris@1001 211
Chris@1001 212 Vamp::RealTime duration;
Chris@1001 213 bool haveDuration = true;
Chris@1001 214
Chris@1001 215 if (f.hasDuration) {
Chris@1001 216 duration = f.duration;
Chris@1001 217 } else if (optionalNextFeature) {
Chris@1001 218 duration = optionalNextFeature->timestamp - f.timestamp;
Chris@1001 219 } else {
Chris@1001 220 haveDuration = false;
Chris@1001 221 }
Chris@1001 222
Chris@1001 223 if (m_sampleTiming) {
Chris@1001 224
Chris@1001 225 float rate = m_rates[transformId];
Chris@1001 226
Chris@1001 227 stream << Vamp::RealTime::realTime2Frame(f.timestamp, rate);
Chris@1001 228
Chris@1001 229 if (haveDuration) {
Chris@1001 230 stream << m_separator;
Chris@1001 231 if (m_endTimes) {
Chris@1001 232 stream << Vamp::RealTime::realTime2Frame
Chris@1001 233 (f.timestamp + duration, rate);
Chris@514 234 } else {
Chris@1001 235 stream << Vamp::RealTime::realTime2Frame(duration, rate);
Chris@514 236 }
Chris@514 237 }
Chris@514 238
Chris@1001 239 } else {
Chris@498 240
Chris@1001 241 QString timestamp = f.timestamp.toString().c_str();
Chris@1001 242 timestamp.replace(QRegExp("^ +"), "");
Chris@1001 243 stream << timestamp;
Chris@669 244
Chris@1001 245 if (haveDuration) {
Chris@1001 246 if (m_endTimes) {
Chris@1001 247 QString endtime =
Chris@1001 248 (f.timestamp + duration).toString().c_str();
Chris@1001 249 endtime.replace(QRegExp("^ +"), "");
Chris@1001 250 stream << m_separator << endtime;
Chris@1001 251 } else {
Chris@1001 252 QString d = duration.toString().c_str();
Chris@1001 253 d.replace(QRegExp("^ +"), "");
Chris@1001 254 stream << m_separator << d;
Chris@669 255 }
Chris@1001 256 }
Chris@1001 257 }
Chris@669 258
Chris@1001 259 if (summaryType != "") {
Chris@1001 260 stream << m_separator << summaryType.c_str();
Chris@498 261 }
Chris@1001 262
Chris@1001 263 for (unsigned int j = 0; j < f.values.size(); ++j) {
Chris@1001 264 stream << m_separator << f.values[j];
Chris@1001 265 }
Chris@1001 266
Chris@1001 267 if (f.label != "") {
Chris@1001 268 stream << m_separator << "\"" << f.label.c_str() << "\"";
Chris@1001 269 }
Chris@1001 270
Chris@1001 271 stream << "\n";
Chris@498 272 }
Chris@498 273
Chris@498 274