CSVFeatureWriter.cpp
Go to the documentation of this file.
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2 
3 /*
4  Sonic Visualiser
5  An audio file viewer and annotation editor.
6 
7  Sonic Annotator
8  A utility for batch feature extraction from audio files.
9 
10  Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
11  Copyright 2007-2008 QMUL.
12 
13  This program is free software; you can redistribute it and/or
14  modify it under the terms of the GNU General Public License as
15  published by the Free Software Foundation; either version 2 of the
16  License, or (at your option) any later version. See the file
17  COPYING included with this distribution for more information.
18 */
19 
20 #include "CSVFeatureWriter.h"
21 
22 #include <iostream>
23 
24 #include <QRegExp>
25 #include <QTextStream>
26 #include <QTextCodec>
27 
28 using namespace std;
29 using namespace Vamp;
30 
32  FileFeatureWriter(SupportOneFilePerTrackTransform |
33  SupportOneFileTotal |
34  SupportStdOut,
35  "csv"),
36  m_separator(","),
37  m_sampleTiming(false),
38  m_endTimes(false),
39  m_forceEnd(false),
40  m_omitFilename(false),
41  m_digits(6)
42 {
43 }
44 
46 {
47 }
48 
49 string
51 {
52  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.";
53 }
54 
57 {
59  Parameter p;
60 
61  p.name = "separator";
62  p.description = "Column separator for output. Default is \",\" (comma).";
63  p.hasArg = true;
64  pl.push_back(p);
65 
66  p.name = "omit-filename";
67  p.description = "Omit the filename column. May result in confusion if sending more than one audio file's features to the same CSV output.";
68  p.hasArg = false;
69  pl.push_back(p);
70 
71  p.name = "sample-timing";
72  p.description = "Show timings as sample frame counts instead of in seconds.";
73  p.hasArg = false;
74  pl.push_back(p);
75 
76  p.name = "end-times";
77  p.description = "Show start and end time instead of start and duration, for features with duration.";
78  p.hasArg = false;
79  pl.push_back(p);
80 
81  p.name = "fill-ends";
82  p.description = "Include durations (or end times) even for features without duration, by using the gap to the next feature instead.";
83  p.hasArg = false;
84  pl.push_back(p);
85 
86  p.name = "digits";
87  p.description = "Specify the number of significant digits to use when printing transform outputs. Outputs are represented internally using single-precision floating-point, so digits beyond the 8th or 9th place are usually meaningless. The default is 6.";
88  p.hasArg = true;
89  pl.push_back(p);
90 
91  return pl;
92 }
93 
94 void
95 CSVFeatureWriter::setParameters(map<string, string> &params)
96 {
98 
99  SVDEBUG << "CSVFeatureWriter::setParameters" << endl;
100  for (map<string, string>::iterator i = params.begin();
101  i != params.end(); ++i) {
102  SVDEBUG << i->first << " -> " << i->second << endl;
103  if (i->first == "separator") {
104  m_separator = i->second.c_str();
105  SVDEBUG << "m_separator = " << m_separator << endl;
106  if (m_separator == "\\t") {
107  m_separator = QChar::Tabulation;
108  }
109  } else if (i->first == "sample-timing") {
110  m_sampleTiming = true;
111  } else if (i->first == "end-times") {
112  m_endTimes = true;
113  } else if (i->first == "fill-ends") {
114  m_forceEnd = true;
115  } else if (i->first == "omit-filename") {
116  m_omitFilename = true;
117  } else if (i->first == "digits") {
118  int digits = atoi(i->second.c_str());
119  if (digits <= 0 || digits > 100) {
120  SVCERR << "CSVFeatureWriter: ERROR: Invalid or out-of-range value for number of significant digits: " << i->second << endl;
121  SVCERR << "CSVFeatureWriter: NOTE: Continuing with default settings" << endl;
122  } else {
123  m_digits = digits;
124  }
125  }
126  }
127 }
128 
129 void
130 CSVFeatureWriter::write(QString trackId,
131  const Transform &transform,
132  const Plugin::OutputDescriptor& ,
133  const Plugin::FeatureList& features,
134  std::string summaryType)
135 {
136  TransformId transformId = transform.getIdentifier();
137 
138  // Select appropriate output file for our track/transform
139  // combination
140 
141  QTextStream *sptr = getOutputStream(trackId,
142  transformId,
143  QTextCodec::codecForName("UTF-8"));
144  if (!sptr) {
145  throw FailedToOpenOutputStream(trackId, transformId);
146  }
147 
148  QTextStream &stream = *sptr;
149 
150  int n = (int)features.size();
151 
152  if (n == 0) return;
153 
154  DataId tt(trackId, transform);
155 
156  if (m_pending.find(tt) != m_pending.end()) {
157  writeFeature(tt,
158  stream,
159  m_pending[tt],
160  &features[0],
162  m_pending.erase(tt);
163  m_pendingSummaryTypes.erase(tt);
164  }
165 
166  if (m_forceEnd) {
167  // can't write final feature until we know its end time
168  --n;
169  m_pending[tt] = features[n];
170  m_pendingSummaryTypes[tt] = summaryType;
171  }
172 
173  for (int i = 0; i < n; ++i) {
174  writeFeature(tt,
175  stream,
176  features[i],
177  m_forceEnd ? &features[i+1] : nullptr,
178  summaryType);
179  }
180 }
181 
182 void
184 {
185  for (PendingFeatures::const_iterator i = m_pending.begin();
186  i != m_pending.end(); ++i) {
187  DataId tt = i->first;
188  Plugin::Feature f = i->second;
189  QTextStream *sptr = getOutputStream(tt.first,
190  tt.second.getIdentifier(),
191  QTextCodec::codecForName("UTF-8"));
192  if (!sptr) {
193  throw FailedToOpenOutputStream(tt.first, tt.second.getIdentifier());
194  }
195  QTextStream &stream = *sptr;
196  // final feature has its own time as end time (we can't
197  // reliably determine the end of audio file, and because of
198  // the nature of block processing, the feature could even
199  // start beyond that anyway)
200  writeFeature(tt, stream, f, &f, m_pendingSummaryTypes[tt]);
201  }
202 
203  m_pending.clear();
204 }
205 
206 void
208  QTextStream &stream,
209  const Plugin::Feature &f,
210  const Plugin::Feature *optionalNextFeature,
211  std::string summaryType)
212 {
213  QString trackId = tt.first;
214  Transform transform = tt.second;
215 
216  if (!m_omitFilename) {
217  if (m_stdout || m_singleFileName != "") {
218  if (trackId != m_prevPrintedTrackId) {
219  stream << "\"" << trackId << "\"" << m_separator;
220  m_prevPrintedTrackId = trackId;
221  } else {
222  stream << m_separator;
223  }
224  }
225  }
226 
227  ::RealTime duration;
228  bool haveDuration = true;
229 
230  if (f.hasDuration) {
231  duration = f.duration;
232  } else if (optionalNextFeature) {
233  duration = optionalNextFeature->timestamp - f.timestamp;
234  } else {
235  haveDuration = false;
236  }
237 
238  if (m_sampleTiming) {
239 
240  sv_samplerate_t rate = transform.getSampleRate();
241 
242  stream << ::RealTime::realTime2Frame(f.timestamp, rate);
243 
244  if (haveDuration) {
245  stream << m_separator;
246  if (m_endTimes) {
248  (::RealTime(f.timestamp) + duration, rate);
249  } else {
250  stream << ::RealTime::realTime2Frame(duration, rate);
251  }
252  }
253 
254  } else {
255 
256  QString timestamp = f.timestamp.toString().c_str();
257  timestamp.replace(QRegExp("^ +"), "");
258  stream << timestamp;
259 
260  if (haveDuration) {
261  if (m_endTimes) {
262  QString endtime =
263  (::RealTime(f.timestamp) + duration).toString().c_str();
264  endtime.replace(QRegExp("^ +"), "");
265  stream << m_separator << endtime;
266  } else {
267  QString d = ::RealTime(duration).toString().c_str();
268  d.replace(QRegExp("^ +"), "");
269  stream << m_separator << d;
270  }
271  }
272  }
273 
274  if (summaryType != "") {
275  stream << m_separator << summaryType.c_str();
276  }
277 
278  for (unsigned int j = 0; j < f.values.size(); ++j) {
279 
280  QString number = QString("%1").arg(f.values[j], 0, 'g', m_digits);
281 
282  // Qt pre-5.6 zero pads single-digit exponents to two digits;
283  // Qt 5.7+ doesn't by default. But we want both to produce the
284  // same output. Getting the new behaviour from standard APIs
285  // in Qt 5.6 isn't possible I think; getting the old behaviour
286  // from Qt 5.7 is possible but fiddly, involving setting up an
287  // appropriate locale and using the %L specifier. We could
288  // doubtless do it with sprintf but Qt is a known quantity at
289  // this point. Let's just convert the old format to the new.
290  number.replace("e-0", "e-");
291 
292  stream << m_separator << number;
293  }
294 
295  if (f.label != "") {
296  stream << m_separator << "\"" << f.label.c_str() << "\"";
297  }
298 
299  stream << "\n";
300 }
301 
302 
double sv_samplerate_t
Sample rate.
Definition: BaseTypes.h:51
sv_samplerate_t getSampleRate() const
Definition: Transform.cpp:378
QTextStream * getOutputStream(QString, TransformId, QTextCodec *)
void writeFeature(DataId, QTextStream &, const Vamp::Plugin::Feature &f, const Vamp::Plugin::Feature *optionalNextFeature, std::string summaryType)
vector< Parameter > ParameterList
Definition: FeatureWriter.h:51
ParameterList getSupportedParameters() const override
std::string toString(bool align=false) const
Return a human-readable debug-type string to full precision (probably not a format to show to a user ...
Definition: RealTimeSV.cpp:213
string getDescription() const override
void setParameters(map< string, string > &params) override
pair< QString, Transform > DataId
virtual ~CSVFeatureWriter()
static sv_frame_t realTime2Frame(const RealTime &r, sv_samplerate_t sampleRate)
Convert a RealTime into a sample frame at the given sample rate.
Definition: RealTimeSV.cpp:490
void setParameters(map< string, string > &params) override
#define SVDEBUG
Definition: Debug.h:106
void finish() override
TransformId getIdentifier() const
Definition: Transform.cpp:179
#define SVCERR
Definition: Debug.h:109
PendingSummaryTypes m_pendingSummaryTypes
PendingFeatures m_pending
QString m_prevPrintedTrackId
ParameterList getSupportedParameters() const override
QString TransformId
Definition: Transform.h:30
void write(QString trackid, const Transform &transform, const Vamp::Plugin::OutputDescriptor &output, const Vamp::Plugin::FeatureList &features, std::string summaryType="") override
RealTime represents time values to nanosecond precision with accurate arithmetic and frame-rate conve...
Definition: RealTime.h:42