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> &params)
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