Mercurial > hg > m4-sa
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> ¶ms) | |
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 } |