comparison runner/BinaryFeatureWriter.cpp @ 60:da84d2efd7a3

Added NumPy compatible binary feature writer for track-level outputs
author gyorgyf
date Sun, 12 Feb 2012 19:55:06 +0000
parents
children 82248965fc74
comparison
equal deleted inserted replaced
59:52b9d58edb78 60:da84d2efd7a3
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2
3 /*
4 Sonic Annotator
5 A utility for batch feature extraction from audio files.
6 Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
7 Copyright 2007-2008 QMUL.
8
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License as
11 published by the Free Software Foundation; either version 2 of the
12 License, or (at your option) any later version. See the file
13 COPYING included with this distribution for more information.
14 */
15
16 #include <fstream>
17
18 #include <QFileInfo>
19
20 #include <ctime>
21
22 #include <stdint.h>
23
24 #include "BinaryFeatureWriter.h"
25
26 #include "base/RealTime.h"
27
28 #include "../version.h"
29
30 #ifdef _WIN32
31 #define platform "Windows"
32 #elif __APPLE__
33 #define platform "MacOS"
34 #elif __linux__
35 #define platform "Linux"
36 #else
37 #define platform "Unix"
38 #endif
39 #ifdef __LP64__ //__x86_64__
40 #define arch "64"
41 #elif _WIN64
42 #define arch "64"
43 #else
44 #define arch "32"
45 #endif
46
47 # define MAJOR_VERSION_PY 0
48
49 using namespace std;
50 using namespace Vamp;
51
52 // Parameter names
53 string
54 BinaryFeatureWriter::outputFileParam = "output";
55
56 struct BinaryFeatureWriter::OutputStream
57 {
58 ofstream* stream;
59 bool newtransform;
60 const Transform *transform;
61 OutputStream() : newtransform(true),stream(NULL),transform(NULL) { }
62 ~OutputStream() { if (stream != NULL) {stream->close(); delete stream;} }
63
64 struct header_t {
65 int16_t BOM16; // 16-bit BOM (FEFF as in UTF-16)
66 int32_t BOM32; // 32-bit BOM (human readable: e.g. ABCD)
67 char major_version; // check for binary compatibility
68 char minor_version; // changes in txt parts only
69 char compression; // use of stream compression (e.g. gzip)
70 char reserved1; // reserved byte
71 char reserved2; // reserved byte
72 char float_size; // size of float
73 char int_size; // size of int
74 char info[160]; // 160 byte text field
75 char null; }; // NULL
76
77 bool open(string filename, bool append = true) {
78
79 if (stream) return true;
80
81 header_t header = {(int16_t) 0xFEFF,0x41424344,MAJOR_VERSION_PY,1,0,NULL,NULL,(char)sizeof(float),(char)sizeof(int),{NULL},NULL};
82 char* p_header = reinterpret_cast<char*>(&header);
83
84 if (append)
85 stream = new ofstream(filename.c_str(), fstream::binary | ios_base::out | ios_base::in | ofstream::ate);
86 else
87 stream = new ofstream(filename.c_str(), fstream::binary);
88
89 if (!stream)
90 {
91 cerr << "ERROR: BinaryFeatureWriter::OutputStream::open(): can't open file " << filename << endl;
92 return false;
93 }
94
95 if (append && !stream->is_open()) {
96 cerr << "NOTE: Writing new binary output file: " << filename << endl;
97 delete stream;
98 stream = NULL;
99 return open(filename,false);
100 }
101
102 // verify input file format
103 if (append) {
104 ifstream istream;
105 istream.open(filename.c_str(), fstream::binary | ios::in);
106 if (!istream.is_open()) {
107 cerr << "ERROR: BinaryFeatureWriter::OutputStream::open(): Can not verify supplied output stream." << endl;
108 return false;
109 }
110 header_t iheader;
111 istream.read(reinterpret_cast<char*>(&iheader),sizeof(header_t));
112 istream.close();
113 int16_t FEFF = 0xFEFF;
114 if (iheader.BOM16 != FEFF) {
115 if (iheader.BOM16 == (int16_t) 0xFFFE) {
116 cerr << "ERROR: BinaryFeatureWriter::OutputStream::open(): This file apperas to have created on a different platform. Can not be appended. "
117 << "Byte order mark: " << iheader.BOM32 << endl;
118 return false;
119 } else {
120 cerr << "ERROR: BinaryFeatureWriter::OutputStream::open(): Invalid target file for this writer." << endl;
121 return false;
122 }
123 }
124 if (iheader.major_version != (char) MAJOR_VERSION_PY || iheader.BOM32 != (int32_t) 0x41424344) {
125 cerr << "ERROR: BinaryFeatureWriter::OutputStream::open(): This file is not binary compatible with this version of the writer."
126 << "file version: " << iheader.major_version << " required: " << MAJOR_VERSION_PY << endl;
127 return false;
128 }
129 }
130
131 if (append) stream->seekp(0,ios_base::end);
132
133 if (!append) {
134 time_t now = time(0);
135 tm* gmtm = gmtime(&now);
136 QString timestamp;
137 if (gmtm != NULL)
138 timestamp = QString("%1 %2").arg(", Created: ").arg(asctime(gmtm)).trimmed();
139 string info = QString(" SONIC ANNOTATOR v%1 PYTHON BINARY V0.1, Platform: %2-%3bit%4")
140 .arg(RUNNER_VERSION)
141 .arg(platform)
142 .arg(arch)
143 .arg(timestamp).toStdString();
144 strncpy(reinterpret_cast<char*>(&header.info),info.c_str(), info.length() <= 160 ? info.length() : 160);
145 stream->write(p_header,sizeof(header_t));
146 }
147 return true;
148 }
149 };
150
151
152 BinaryFeatureWriter::BinaryFeatureWriter() :
153 outputFile("features")
154 {
155 binary = new OutputStream();
156 }
157
158 BinaryFeatureWriter::~BinaryFeatureWriter()
159 {
160 if (binary) delete binary;
161 }
162
163 BinaryFeatureWriter::ParameterList
164 BinaryFeatureWriter::getSupportedParameters() const
165 {
166 ParameterList pl;
167 Parameter p;
168
169 p.name = outputFileParam;
170 p.description = "Binary output file path";
171 p.hasArg = true;
172 pl.push_back(p);
173
174 return pl;
175 }
176
177 void
178 BinaryFeatureWriter::setParameters(map<string, string> &params)
179 {
180 if (params.find(outputFileParam) != params.end()) {
181 setOutputFile(params[outputFileParam]);
182 params.erase(outputFileParam);
183 }
184 }
185
186 void
187 BinaryFeatureWriter::setOutputFile(const string &file)
188 {
189 outputFile = file;
190 }
191
192 void BinaryFeatureWriter::write(QString trackid,
193 const Transform &transform,
194 const Vamp::Plugin::OutputDescriptor& output,
195 const Vamp::Plugin::FeatureList& featureList,
196 std::string summaryType)
197 {
198 //!!! use summaryType
199 if (summaryType != "") {
200 //!!! IMPLEMENT
201 cerr << "ERROR: BinaryFeatureWriter::write: Writing summaries is not yet implemented!" << endl;
202 exit(1);
203 }
204
205 // TODO: Consider writing out NumPy arrays directly following this documentation:
206 // https://github.com/numpy/numpy/blob/master/doc/neps/npy-format.txt
207 // and using the .npy format
208
209 // return if file could not be opened
210 if(!openBinaryFile()) {
211 cerr << "ERROR: BinaryFeatureWriter::write: Error opening binary output file!" << endl;
212 exit(1);
213 }
214
215 ofstream &ofs = *(binary->stream);
216
217 // The manager does not call finish() after writing different outputs from the same plugin, but we need this behaviour here:
218 if (!binary->newtransform && binary->transform != NULL && binary->transform != &transform) finish();
219
220 // write a python dictionary string containing (some) metadata needed to interpret the results
221 // this can be evaluated in python using the expression : d = eval(f.readline())
222 // given f is an open file, which should yield a valid dictionary.
223 /*
224 enum SampleType {
225
226 /// Results from each process() align with that call's block start
227 0: OneSamplePerStep,
228
229 /// Results are evenly spaced in time
230 1: FixedSampleRate,
231
232 /// Results are unevenly spaced and have individual timestamps
233 2: VariableSampleRate
234 };
235 */
236
237 if (binary->newtransform) {
238 binary->newtransform = false;
239 output_binCount = output.binCount;
240 feature_count = 0;
241 binary->transform = &transform;
242
243 ofs << endl << "{"
244 << "\"track_id\":\"" << trackid << "\","
245 << "\"transform_id\":\"" << transform.getIdentifier() << "\","
246 << "\"sample_rate\":" << transform.getSampleRate() << ","
247 << "\"step_size\":" << transform.getStepSize() << ","
248 << "\"block_size\":" << transform.getBlockSize() << ","
249 << "\"window_type\":" << transform.getWindowType() << ","
250
251 << "\"features_list\":" << featureList.size() << ","
252 << "\"bin_count\":" << output.binCount << ","
253 // << "\"output_description\":\"" << output.description << "\","
254 << "\"output_sample_type\":" << output.sampleType << ","
255 << "\"output_sample_rate\":" << output.sampleRate << ",";
256
257 // Write start time and duration if the transform is not for the whole file
258 if (transform.getDuration().toString() != "0.000000000") {
259 ofs << "\"start_time\":\"" << transform.getStartTime().toString() << "\",";
260 ofs << "\"duration\":\"" << transform.getDuration().toString() << "\",";
261 }
262 // Write plugin version if known. (NOTE: using RDF transforms, it remains empty for some reason)
263 if (!transform.getPluginVersion().isEmpty())
264 ofs << "\"plugin_version\":\"" << transform.getPluginVersion() << "\",";
265
266 // write transform parameters into a dict: parameters:{"parameter_name":value,...} where value is float
267 ofs << "\"parameters\":{" ;
268 ParameterMap m = transform.getParameters();
269
270 for (ParameterMap::const_iterator i = m.begin(); i != m.end(); ++i)
271 // note last comma is ignored by python
272 if (i == m.begin())
273 ofs << QString("\"%1\":%2").arg(i->first).arg(i->second);
274 else
275 ofs << QString(",\"%1\":%2").arg(i->first).arg(i->second);
276 ofs << "}";
277
278 // write the data size last, and close the line.
279 data_size_pos = ofs.tellp();
280 ofs << " }" << endl;
281 }
282
283 // write the feature data
284 feature_count += featureList.size();
285 for (size_t i = 0; i < featureList.size(); ++i) {
286 for (size_t j = 0; j < featureList[i].values.size(); ++j)
287 ofs.write( (const char*) &featureList[i].values[j], sizeof(featureList[i].values[j]) );
288 }
289 // ofs << endl;
290
291 // // write time stamp data
292 // for (int i = 0; i < featureList.size(); ++i) {
293 // for (int j = 0; j < featureList[i].values.size(); ++j) {
294 // // float sec = (int) featureList[i].timestamp.sec;
295 // // float nsec = (int) featureList[i].timestamp.nsec;
296 // // (*dbfiles[id].ofs).write( (const char*) &sec, sizeof(int));
297 // // (*dbfiles[id].ofs).write( (const char*) &nsec, sizeof(int));
298 // ofs.write( (const char*) &featureList[i].timestamp.sec, sizeof(int));
299 // ofs.write( (const char*) &featureList[i].timestamp.nsec, sizeof(int));
300 //
301 // }
302 //
303 // }
304
305 // -- UNCOMMENT - TO - HERE --
306
307
308
309 }
310
311 bool BinaryFeatureWriter::openBinaryFile()
312 {
313 return binary->open(outputFile + ".bin");
314 }
315
316 void BinaryFeatureWriter::finish()
317 {
318 ofstream &ofs = *(binary->stream);
319 binary->newtransform = true;
320 // ofs << endl;
321 long t = ofs.tellp();
322 ofs.seekp(data_size_pos);
323 // fill in the missing information in the transform python dict that is required to read the output into an array
324 ofs << ",\"feature_count\":"
325 << feature_count
326 << ",\"data_size\":"
327 << feature_count * output_binCount * sizeof(float);
328 ofs.seekp(t);
329 // FileFeatureWriter::finish();
330 }