annotate transform/CSVFeatureWriter.cpp @ 1006:d954e03274e8

TransformId is not sufficient -- it could be ambiguous (same transform id, different parameters etc). Use the whole transform
author Chris Cannam
date Wed, 15 Oct 2014 17:02:48 +0100
parents c2316a3bbb81
children d74ebd2d2c49
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@1006 137 DataId tt(trackId, transform);
Chris@1001 138
Chris@1001 139 if (m_pending.find(tt) != m_pending.end()) {
Chris@1001 140 writeFeature(tt,
Chris@1001 141 stream,
Chris@1001 142 m_pending[tt],
Chris@1001 143 &features[0],
Chris@1001 144 m_pendingSummaryTypes[tt]);
Chris@1001 145 m_pending.erase(tt);
Chris@1001 146 m_pendingSummaryTypes.erase(tt);
Chris@1001 147 }
Chris@1001 148
Chris@1001 149 if (m_forceEnd) {
Chris@1001 150 // can't write final feature until we know its end time
Chris@1001 151 --n;
Chris@1001 152 m_pending[tt] = features[n];
Chris@1001 153 m_pendingSummaryTypes[tt] = summaryType;
Chris@1001 154 }
Chris@1001 155
Chris@1001 156 for (int i = 0; i < n; ++i) {
Chris@1001 157 writeFeature(tt,
Chris@1001 158 stream,
Chris@1001 159 features[i],
Chris@1001 160 m_forceEnd ? &features[i+1] : 0,
Chris@1001 161 summaryType);
Chris@1001 162 }
Chris@1001 163 }
Chris@1001 164
Chris@1001 165 void
Chris@1001 166 CSVFeatureWriter::finish()
Chris@1001 167 {
Chris@1001 168 for (PendingFeatures::const_iterator i = m_pending.begin();
Chris@1001 169 i != m_pending.end(); ++i) {
Chris@1006 170 DataId tt = i->first;
Chris@1001 171 Plugin::Feature f = i->second;
Chris@1006 172 QTextStream *sptr = getOutputStream(tt.first, tt.second.getIdentifier());
Chris@1001 173 if (!sptr) {
Chris@1006 174 throw FailedToOpenOutputStream(tt.first, tt.second.getIdentifier());
Chris@1001 175 }
Chris@1001 176 QTextStream &stream = *sptr;
Chris@1001 177 // final feature has its own time as end time (we can't
Chris@1001 178 // reliably determine the end of audio file, and because of
Chris@1001 179 // the nature of block processing, the feature could even
Chris@1001 180 // start beyond that anyway)
Chris@1001 181 writeFeature(tt, stream, f, &f, m_pendingSummaryTypes[tt]);
Chris@1001 182 }
Chris@1001 183
Chris@1001 184 m_pending.clear();
Chris@1001 185 }
Chris@1001 186
Chris@1001 187 void
Chris@1006 188 CSVFeatureWriter::writeFeature(DataId tt,
Chris@1001 189 QTextStream &stream,
Chris@1001 190 const Plugin::Feature &f,
Chris@1001 191 const Plugin::Feature *optionalNextFeature,
Chris@1001 192 std::string summaryType)
Chris@1001 193 {
Chris@1001 194 QString trackId = tt.first;
Chris@1006 195 Transform transform = tt.second;
Chris@1001 196
Chris@1002 197 if (!m_omitFilename) {
Chris@1002 198 if (m_stdout || m_singleFileName != "") {
Chris@1002 199 if (trackId != m_prevPrintedTrackId) {
Chris@1002 200 stream << "\"" << trackId << "\"" << m_separator;
Chris@1002 201 m_prevPrintedTrackId = trackId;
Chris@1002 202 } else {
Chris@1002 203 stream << m_separator;
Chris@1002 204 }
Chris@1001 205 }
Chris@1001 206 }
Chris@1001 207
Chris@1001 208 Vamp::RealTime duration;
Chris@1001 209 bool haveDuration = true;
Chris@1001 210
Chris@1001 211 if (f.hasDuration) {
Chris@1001 212 duration = f.duration;
Chris@1001 213 } else if (optionalNextFeature) {
Chris@1001 214 duration = optionalNextFeature->timestamp - f.timestamp;
Chris@1001 215 } else {
Chris@1001 216 haveDuration = false;
Chris@1001 217 }
Chris@1001 218
Chris@1001 219 if (m_sampleTiming) {
Chris@1001 220
Chris@1006 221 float rate = transform.getSampleRate();
Chris@1001 222
Chris@1001 223 stream << Vamp::RealTime::realTime2Frame(f.timestamp, rate);
Chris@1001 224
Chris@1001 225 if (haveDuration) {
Chris@1001 226 stream << m_separator;
Chris@1001 227 if (m_endTimes) {
Chris@1001 228 stream << Vamp::RealTime::realTime2Frame
Chris@1001 229 (f.timestamp + duration, rate);
Chris@514 230 } else {
Chris@1001 231 stream << Vamp::RealTime::realTime2Frame(duration, rate);
Chris@514 232 }
Chris@514 233 }
Chris@514 234
Chris@1001 235 } else {
Chris@498 236
Chris@1001 237 QString timestamp = f.timestamp.toString().c_str();
Chris@1001 238 timestamp.replace(QRegExp("^ +"), "");
Chris@1001 239 stream << timestamp;
Chris@669 240
Chris@1001 241 if (haveDuration) {
Chris@1001 242 if (m_endTimes) {
Chris@1001 243 QString endtime =
Chris@1001 244 (f.timestamp + duration).toString().c_str();
Chris@1001 245 endtime.replace(QRegExp("^ +"), "");
Chris@1001 246 stream << m_separator << endtime;
Chris@1001 247 } else {
Chris@1001 248 QString d = duration.toString().c_str();
Chris@1001 249 d.replace(QRegExp("^ +"), "");
Chris@1001 250 stream << m_separator << d;
Chris@669 251 }
Chris@1001 252 }
Chris@1001 253 }
Chris@669 254
Chris@1001 255 if (summaryType != "") {
Chris@1001 256 stream << m_separator << summaryType.c_str();
Chris@498 257 }
Chris@1001 258
Chris@1001 259 for (unsigned int j = 0; j < f.values.size(); ++j) {
Chris@1001 260 stream << m_separator << f.values[j];
Chris@1001 261 }
Chris@1001 262
Chris@1001 263 if (f.label != "") {
Chris@1001 264 stream << m_separator << "\"" << f.label.c_str() << "\"";
Chris@1001 265 }
Chris@1001 266
Chris@1001 267 stream << "\n";
Chris@498 268 }
Chris@498 269
Chris@498 270