Mercurial > hg > svcore
comparison rdf/RDFFeatureWriter.cpp @ 498:fdf5930b7ccc
* Bring FeatureWriter and RDFFeatureWriter into the fold (from Runner)
so that we can use them to export features from SV as well
author | Chris Cannam |
---|---|
date | Fri, 28 Nov 2008 13:47:11 +0000 |
parents | |
children | 83eae5239db6 |
comparison
equal
deleted
inserted
replaced
497:b6dc6c7f402c | 498:fdf5930b7ccc |
---|---|
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 "vamp-hostsdk/PluginHostAdapter.h" | |
19 #include "vamp-hostsdk/PluginLoader.h" | |
20 | |
21 #include "RDFFeatureWriter.h" | |
22 #include "RDFTransformFactory.h" | |
23 | |
24 #include <QTextStream> | |
25 #include <QUrl> | |
26 #include <QRegExp> | |
27 | |
28 using namespace std; | |
29 using Vamp::Plugin; | |
30 using Vamp::PluginBase; | |
31 | |
32 RDFFeatureWriter::RDFFeatureWriter() : | |
33 FileFeatureWriter(SupportOneFilePerTrackTransform | | |
34 SupportOneFilePerTrack | | |
35 SupportOneFileTotal, | |
36 "n3"), | |
37 m_plain(false), | |
38 m_count(0) | |
39 { | |
40 } | |
41 | |
42 RDFFeatureWriter::~RDFFeatureWriter() | |
43 { | |
44 } | |
45 | |
46 RDFFeatureWriter::ParameterList | |
47 RDFFeatureWriter::getSupportedParameters() const | |
48 { | |
49 ParameterList pl = FileFeatureWriter::getSupportedParameters(); | |
50 Parameter p; | |
51 | |
52 p.name = "plain"; | |
53 p.description = "Use \"plain\" RDF even if transform metadata is available."; | |
54 p.hasArg = false; | |
55 pl.push_back(p); | |
56 | |
57 p.name = "signal-uri"; | |
58 p.description = "Link the output RDF to the given signal URI."; | |
59 p.hasArg = true; | |
60 pl.push_back(p); | |
61 | |
62 return pl; | |
63 } | |
64 | |
65 void | |
66 RDFFeatureWriter::setParameters(map<string, string> ¶ms) | |
67 { | |
68 FileFeatureWriter::setParameters(params); | |
69 | |
70 for (map<string, string>::iterator i = params.begin(); | |
71 i != params.end(); ++i) { | |
72 if (i->first == "plain") { | |
73 m_plain = true; | |
74 } | |
75 if (i->first == "signal-uri") { | |
76 m_suri = i->second.c_str(); | |
77 } | |
78 } | |
79 } | |
80 | |
81 void RDFFeatureWriter::write(QString trackId, | |
82 const Transform &transform, | |
83 const Plugin::OutputDescriptor& output, | |
84 const Plugin::FeatureList& features, | |
85 std::string summaryType) | |
86 { | |
87 QString pluginId = transform.getPluginIdentifier(); | |
88 | |
89 if (m_rdfDescriptions.find(pluginId) == m_rdfDescriptions.end()) { | |
90 | |
91 m_rdfDescriptions[pluginId] = PluginRDFDescription(pluginId); | |
92 | |
93 if (m_rdfDescriptions[pluginId].haveDescription()) { | |
94 cerr << "NOTE: Have RDF description for plugin ID \"" | |
95 << pluginId.toStdString() << "\"" << endl; | |
96 } else { | |
97 cerr << "NOTE: Do not have RDF description for plugin ID \"" | |
98 << pluginId.toStdString() << "\"" << endl; | |
99 } | |
100 } | |
101 | |
102 // Need to select appropriate output file for our track/transform | |
103 // combination | |
104 | |
105 QTextStream *stream = getOutputStream(trackId, transform.getIdentifier()); | |
106 if (!stream) return; //!!! this is probably better handled with an exception | |
107 | |
108 if (m_startedStreamTransforms.find(stream) == | |
109 m_startedStreamTransforms.end()) { | |
110 cerr << "This stream is new, writing prefixes" << endl; | |
111 writePrefixes(stream); | |
112 if (m_singleFileName == "" && !m_stdout) { | |
113 writeSignalDescription(stream, trackId); | |
114 } | |
115 } | |
116 | |
117 if (m_startedStreamTransforms[stream].find(transform) == | |
118 m_startedStreamTransforms[stream].end()) { | |
119 m_startedStreamTransforms[stream].insert(transform); | |
120 writeLocalFeatureTypes | |
121 (stream, transform, output, m_rdfDescriptions[pluginId]); | |
122 } | |
123 | |
124 if (m_singleFileName != "" || m_stdout) { | |
125 if (m_startedTrackIds.find(trackId) == m_startedTrackIds.end()) { | |
126 writeSignalDescription(stream, trackId); | |
127 m_startedTrackIds.insert(trackId); | |
128 } | |
129 } | |
130 | |
131 QString timelineURI = m_trackTimelineURIs[trackId]; | |
132 | |
133 if (timelineURI == "") { | |
134 cerr << "RDFFeatureWriter: INTERNAL ERROR: writing features without having established a timeline URI!" << endl; | |
135 exit(1); | |
136 } | |
137 | |
138 if (summaryType != "") { | |
139 | |
140 writeSparseRDF(stream, transform, output, features, | |
141 m_rdfDescriptions[pluginId], timelineURI); | |
142 | |
143 } else if (m_rdfDescriptions[pluginId].haveDescription() && | |
144 m_rdfDescriptions[pluginId].getOutputDisposition | |
145 (output.identifier.c_str()) == | |
146 PluginRDFDescription::OutputDense) { | |
147 | |
148 QString signalURI = m_trackSignalURIs[trackId]; | |
149 | |
150 if (signalURI == "") { | |
151 cerr << "RDFFeatureWriter: INTERNAL ERROR: writing dense features without having established a signal URI!" << endl; | |
152 exit(1); | |
153 } | |
154 | |
155 writeDenseRDF(stream, transform, output, features, | |
156 m_rdfDescriptions[pluginId], signalURI, timelineURI); | |
157 | |
158 } else { | |
159 | |
160 writeSparseRDF(stream, transform, output, features, | |
161 m_rdfDescriptions[pluginId], timelineURI); | |
162 } | |
163 } | |
164 | |
165 void | |
166 RDFFeatureWriter::writePrefixes(QTextStream *sptr) | |
167 { | |
168 QTextStream &stream = *sptr; | |
169 | |
170 stream << "@prefix dc: <http://purl.org/dc/elements/1.1/> .\n" | |
171 << "@prefix mo: <http://purl.org/ontology/mo/> .\n" | |
172 << "@prefix af: <http://purl.org/ontology/af/> .\n" | |
173 << "@prefix event: <http://purl.org/NET/c4dm/event.owl#> .\n" | |
174 << "@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n" | |
175 << "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n" | |
176 << "@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n" | |
177 << "@prefix tl: <http://purl.org/NET/c4dm/timeline.owl#> .\n" | |
178 << "@prefix vamp: <http://purl.org/ontology/vamp/> .\n" | |
179 << "@prefix : <#> .\n\n"; | |
180 } | |
181 | |
182 void | |
183 RDFFeatureWriter::writeSignalDescription(QTextStream *sptr, | |
184 QString trackId) | |
185 { | |
186 QTextStream &stream = *sptr; | |
187 | |
188 /* | |
189 * Describe signal we're analysing (AudioFile, Signal, TimeLine, etc.) | |
190 */ | |
191 | |
192 QUrl url(trackId); | |
193 QString scheme = url.scheme().toLower(); | |
194 bool local = (scheme == "" || scheme == "file" || scheme.length() == 1); | |
195 | |
196 if (local) { | |
197 if (scheme == "") { | |
198 url.setScheme("file"); | |
199 } else if (scheme.length() == 1) { // DOS drive letter! | |
200 url.setScheme("file"); | |
201 url.setPath(scheme + ":" + url.path()); | |
202 } | |
203 } | |
204 | |
205 //!!! FIX: If we are appending, we need to start counting after | |
206 //all of the existing counts that are already in the file! | |
207 | |
208 uint64_t signalCount = m_count++; | |
209 | |
210 if (m_trackSignalURIs.find(trackId) == m_trackSignalURIs.end()) { | |
211 m_trackSignalURIs[trackId] = QString(":signal_%1").arg(signalCount); | |
212 } | |
213 | |
214 if (m_suri != NULL) { | |
215 m_trackSignalURIs[trackId] = "<" + m_suri + ">"; | |
216 } | |
217 QString signalURI = m_trackSignalURIs[trackId]; | |
218 | |
219 if (m_trackTimelineURIs.find(trackId) == m_trackTimelineURIs.end()) { | |
220 m_trackTimelineURIs[trackId] = QString(":signal_timeline_%1").arg(signalCount); | |
221 } | |
222 QString timelineURI = m_trackTimelineURIs[trackId]; | |
223 | |
224 stream << "\n<" << url.toEncoded().data() << "> a mo:AudioFile .\n\n" | |
225 << signalURI << " a mo:Signal ;\n" | |
226 << " mo:available_as <" << url.toEncoded().data() | |
227 << "> ;\n" | |
228 << " mo:time [\n" | |
229 << " a tl:Interval ;\n" | |
230 << " tl:onTimeLine " | |
231 << timelineURI << "\n ] .\n\n"; | |
232 } | |
233 | |
234 void | |
235 RDFFeatureWriter::writeLocalFeatureTypes(QTextStream *sptr, | |
236 const Transform &transform, | |
237 const Plugin::OutputDescriptor &od, | |
238 PluginRDFDescription &desc) | |
239 { | |
240 QString outputId = od.identifier.c_str(); | |
241 QTextStream &stream = *sptr; | |
242 | |
243 bool needEventType = false; | |
244 bool needSignalType = false; | |
245 | |
246 //!!! feature attribute type is not yet supported | |
247 | |
248 //!!! bin names, extents and so on can be written out using e.g. vamp:bin_names ( "a" "b" "c" ) | |
249 | |
250 if (desc.getOutputDisposition(outputId) == | |
251 PluginRDFDescription::OutputDense) { | |
252 | |
253 // no feature events, so may need signal type but won't need | |
254 // event type | |
255 | |
256 if (m_plain) { | |
257 | |
258 needSignalType = true; | |
259 | |
260 } else if (desc.getOutputSignalTypeURI(outputId) == "") { | |
261 | |
262 needSignalType = true; | |
263 } | |
264 | |
265 } else { | |
266 | |
267 // may need event type but won't need signal type | |
268 | |
269 if (m_plain) { | |
270 | |
271 needEventType = true; | |
272 | |
273 } else if (desc.getOutputEventTypeURI(outputId) == "") { | |
274 | |
275 needEventType = true; | |
276 } | |
277 } | |
278 | |
279 QString transformUri; | |
280 if (m_transformURIs.find(transform) != m_transformURIs.end()) { | |
281 transformUri = m_transformURIs[transform]; | |
282 } else { | |
283 transformUri = QString(":transform_%1_%2").arg(m_count++).arg(outputId); | |
284 m_transformURIs[transform] = transformUri; | |
285 } | |
286 | |
287 stream << RDFTransformFactory::writeTransformToRDF(transform, transformUri) | |
288 << endl; | |
289 | |
290 if (needEventType) { | |
291 | |
292 QString uri; | |
293 if (m_syntheticEventTypeURIs.find(transform) != | |
294 m_syntheticEventTypeURIs.end()) { | |
295 uri = m_syntheticEventTypeURIs[transform]; | |
296 } else { | |
297 uri = QString(":event_type_%1").arg(m_count++); | |
298 m_syntheticEventTypeURIs[transform] = uri; | |
299 } | |
300 | |
301 stream << uri | |
302 << " rdfs:subClassOf event:Event ;" << endl | |
303 << " dc:title \"" << od.name.c_str() << "\" ;" << endl | |
304 << " dc:format \"" << od.unit.c_str() << "\" ;" << endl | |
305 << " dc:description \"" << od.description.c_str() << "\" ." | |
306 << endl << endl; | |
307 } | |
308 | |
309 if (needSignalType) { | |
310 | |
311 QString uri; | |
312 if (m_syntheticSignalTypeURIs.find(transform) != | |
313 m_syntheticSignalTypeURIs.end()) { | |
314 uri = m_syntheticSignalTypeURIs[transform]; | |
315 } else { | |
316 uri = QString(":signal_type_%1").arg(m_count++); | |
317 m_syntheticSignalTypeURIs[transform] = uri; | |
318 } | |
319 | |
320 stream << uri | |
321 << " rdfs:subClassOf af:Signal ;" << endl | |
322 << " dc:title \"" << od.name.c_str() << "\" ;" << endl | |
323 << " dc:format \"" << od.unit.c_str() << "\" ;" << endl | |
324 << " dc:description \"" << od.description.c_str() << "\" ." | |
325 << endl << endl; | |
326 } | |
327 } | |
328 | |
329 void | |
330 RDFFeatureWriter::writeSparseRDF(QTextStream *sptr, | |
331 const Transform &transform, | |
332 const Plugin::OutputDescriptor& od, | |
333 const Plugin::FeatureList& featureList, | |
334 PluginRDFDescription &desc, | |
335 QString timelineURI) | |
336 { | |
337 if (featureList.empty()) return; | |
338 QTextStream &stream = *sptr; | |
339 | |
340 bool plain = (m_plain || !desc.haveDescription()); | |
341 | |
342 QString outputId = od.identifier.c_str(); | |
343 | |
344 // iterate through FeatureLists | |
345 | |
346 for (int i = 0; i < featureList.size(); ++i) { | |
347 | |
348 const Plugin::Feature &feature = featureList[i]; | |
349 uint64_t featureNumber = m_count++; | |
350 | |
351 stream << ":event_" << featureNumber << " a "; | |
352 | |
353 QString eventTypeURI = desc.getOutputEventTypeURI(outputId); | |
354 if (plain || eventTypeURI == "") { | |
355 if (m_syntheticEventTypeURIs.find(transform) != | |
356 m_syntheticEventTypeURIs.end()) { | |
357 stream << m_syntheticEventTypeURIs[transform] << " ;\n"; | |
358 } else { | |
359 stream << ":event_type_" << outputId << " ;\n"; | |
360 } | |
361 } else { | |
362 stream << "<" << eventTypeURI << "> ;\n"; | |
363 } | |
364 | |
365 QString timestamp = feature.timestamp.toString().c_str(); | |
366 timestamp.replace(QRegExp("^ +"), ""); | |
367 | |
368 if (feature.hasDuration && feature.duration > Vamp::RealTime::zeroTime) { | |
369 | |
370 QString duration = feature.duration.toString().c_str(); | |
371 duration.replace(QRegExp("^ +"), ""); | |
372 | |
373 stream << " event:time [ \n" | |
374 << " a tl:Interval ;\n" | |
375 << " tl:onTimeLine " << timelineURI << " ;\n" | |
376 << " tl:beginsAt \"PT" << timestamp | |
377 << "S\"^^xsd:duration ;\n" | |
378 << " tl:duration \"PT" << duration | |
379 << "S\"^^xsd:duration ;\n" | |
380 << " ] "; | |
381 | |
382 } else { | |
383 | |
384 stream << " event:time [ \n" | |
385 << " a tl:Instant ;\n" //location of the event in time | |
386 << " tl:onTimeLine " << timelineURI << " ;\n" | |
387 << " tl:at \"PT" << timestamp | |
388 << "S\"^^xsd:duration ;\n ] "; | |
389 } | |
390 | |
391 stream << ";\n"; | |
392 stream << " vamp:computed_by " << m_transformURIs[transform] << " "; | |
393 | |
394 if (feature.label.length() > 0) { | |
395 stream << ";\n"; | |
396 stream << " rdfs:label \"" << feature.label.c_str() << "\" "; | |
397 } | |
398 | |
399 if (!feature.values.empty()) { | |
400 stream << ";\n"; | |
401 //!!! named bins? | |
402 stream << " af:feature \"" << feature.values[0]; | |
403 for (int j = 1; j < feature.values.size(); ++j) { | |
404 stream << " " << feature.values[j]; | |
405 } | |
406 stream << "\" "; | |
407 } | |
408 | |
409 stream << ".\n"; | |
410 } | |
411 } | |
412 | |
413 void | |
414 RDFFeatureWriter::writeDenseRDF(QTextStream *sptr, | |
415 const Transform &transform, | |
416 const Plugin::OutputDescriptor& od, | |
417 const Plugin::FeatureList& featureList, | |
418 PluginRDFDescription &desc, | |
419 QString signalURI, | |
420 QString timelineURI) | |
421 { | |
422 if (featureList.empty()) return; | |
423 | |
424 StringTransformPair sp(signalURI, transform); | |
425 | |
426 if (m_openDenseFeatures.find(sp) == m_openDenseFeatures.end()) { | |
427 | |
428 StreamBuffer b(sptr, ""); | |
429 m_openDenseFeatures[sp] = b; | |
430 | |
431 QString &str(m_openDenseFeatures[sp].second); | |
432 QTextStream stream(&str); | |
433 | |
434 bool plain = (m_plain || !desc.haveDescription()); | |
435 QString outputId = od.identifier.c_str(); | |
436 | |
437 uint64_t featureNumber = m_count++; | |
438 | |
439 // need to write out feature timeline map -- for this we need | |
440 // the sample rate, window length and hop size from the | |
441 // transform | |
442 | |
443 stream << "\n:feature_timeline_" << featureNumber << " a tl:DiscreteTimeLine .\n\n"; | |
444 | |
445 size_t stepSize = transform.getStepSize(); | |
446 if (stepSize == 0) { | |
447 cerr << "RDFFeatureWriter: INTERNAL ERROR: writing dense features without having set the step size properly!" << endl; | |
448 return; | |
449 } | |
450 | |
451 size_t blockSize = transform.getBlockSize(); | |
452 if (blockSize == 0) { | |
453 cerr << "RDFFeatureWriter: INTERNAL ERROR: writing dense features without having set the block size properly!" << endl; | |
454 return; | |
455 } | |
456 | |
457 float sampleRate = transform.getSampleRate(); | |
458 if (sampleRate == 0.f) { | |
459 cerr << "RDFFeatureWriter: INTERNAL ERROR: writing dense features without having set the sample rate properly!" << endl; | |
460 return; | |
461 } | |
462 | |
463 stream << ":feature_timeline_map_" << featureNumber | |
464 << " a tl:UniformSamplingWindowingMap ;\n" | |
465 << " tl:rangeTimeLine :feature_timeline_" << featureNumber << " ;\n" | |
466 << " tl:domainTimeLine " << timelineURI << " ;\n" | |
467 << " tl:sampleRate \"" << int(sampleRate) << "\"^^xsd:int ;\n" | |
468 << " tl:windowLength \"" << blockSize << "\"^^xsd:int ;\n" | |
469 << " tl:hopSize \"" << stepSize << "\"^^xsd:int .\n\n"; | |
470 | |
471 stream << signalURI << " af:signal_feature :feature_" | |
472 << featureNumber << " ." << endl << endl; | |
473 | |
474 stream << ":feature_" << featureNumber << " a "; | |
475 | |
476 QString signalTypeURI = desc.getOutputSignalTypeURI(outputId); | |
477 if (plain || signalTypeURI == "") { | |
478 if (m_syntheticSignalTypeURIs.find(transform) != | |
479 m_syntheticSignalTypeURIs.end()) { | |
480 stream << m_syntheticSignalTypeURIs[transform] << " ;\n"; | |
481 } else { | |
482 stream << ":signal_type_" << outputId << " ;\n"; | |
483 } | |
484 } else { | |
485 stream << signalTypeURI << " ;\n"; | |
486 } | |
487 | |
488 stream << " mo:time [" | |
489 << "\n a tl:Interval ;" | |
490 << "\n tl:onTimeLine :feature_timeline_" << featureNumber << " ;"; | |
491 | |
492 RealTime startrt = transform.getStartTime(); | |
493 RealTime durationrt = transform.getDuration(); | |
494 | |
495 int start = RealTime::realTime2Frame(startrt, sampleRate) / stepSize; | |
496 int duration = RealTime::realTime2Frame(durationrt, sampleRate) / stepSize; | |
497 | |
498 if (start != 0) { | |
499 stream << "\n tl:start \"" << start << "\"^^xsd:int ;"; | |
500 } | |
501 if (duration != 0) { | |
502 stream << "\n tl:duration \"" << duration << "\"^^xsd:int ;"; | |
503 } | |
504 | |
505 stream << "\n ] ;\n"; | |
506 | |
507 if (od.hasFixedBinCount) { | |
508 // We only know the height, so write the width as zero | |
509 stream << " af:dimensions \"" << od.binCount << " 0\" ;\n"; | |
510 } | |
511 | |
512 stream << " af:value \""; | |
513 } | |
514 | |
515 QString &str = m_openDenseFeatures[sp].second; | |
516 QTextStream stream(&str); | |
517 | |
518 for (int i = 0; i < featureList.size(); ++i) { | |
519 | |
520 const Plugin::Feature &feature = featureList[i]; | |
521 | |
522 for (int j = 0; j < feature.values.size(); ++j) { | |
523 stream << feature.values[j] << " "; | |
524 } | |
525 } | |
526 } | |
527 | |
528 void RDFFeatureWriter::finish() | |
529 { | |
530 // cerr << "RDFFeatureWriter::finish()" << endl; | |
531 | |
532 // close any open dense feature literals | |
533 | |
534 for (map<StringTransformPair, StreamBuffer>::iterator i = | |
535 m_openDenseFeatures.begin(); | |
536 i != m_openDenseFeatures.end(); ++i) { | |
537 cerr << "closing a stream" << endl; | |
538 StreamBuffer &b = i->second; | |
539 *(b.first) << b.second << "\" ." << endl; | |
540 } | |
541 | |
542 m_openDenseFeatures.clear(); | |
543 } | |
544 | |
545 |