annotate rdf/RDFImporter.cpp @ 1777:d484490cdf69

Split EditableDenseThreeDimensionalModel into explicitly compressed and uncompressed variants. Simplifies the uncompressed version, and we may want to consider whether we need the compressed one at all.
author Chris Cannam
date Tue, 10 Sep 2019 16:34:47 +0100
parents 6d09d68165a4
children a454c7477b4f
rev   line source
Chris@439 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@439 2
Chris@439 3 /*
Chris@439 4 Sonic Visualiser
Chris@439 5 An audio file viewer and annotation editor.
Chris@439 6 Centre for Digital Music, Queen Mary, University of London.
Chris@727 7 This file copyright 2008-2012 QMUL.
Chris@439 8
Chris@439 9 This program is free software; you can redistribute it and/or
Chris@439 10 modify it under the terms of the GNU General Public License as
Chris@439 11 published by the Free Software Foundation; either version 2 of the
Chris@439 12 License, or (at your option) any later version. See the file
Chris@439 13 COPYING included with this distribution for more information.
Chris@439 14 */
Chris@439 15
Chris@439 16 #include "RDFImporter.h"
Chris@439 17
Chris@439 18 #include <map>
Chris@439 19 #include <vector>
Chris@439 20
Chris@439 21 #include <iostream>
Chris@439 22 #include <cmath>
Chris@439 23
Chris@439 24 #include "base/ProgressReporter.h"
Chris@439 25 #include "base/RealTime.h"
Chris@439 26
Chris@439 27 #include "data/model/SparseOneDimensionalModel.h"
Chris@439 28 #include "data/model/SparseTimeValueModel.h"
Chris@439 29 #include "data/model/EditableDenseThreeDimensionalModel.h"
Chris@449 30 #include "data/model/NoteModel.h"
Chris@510 31 #include "data/model/TextModel.h"
Chris@449 32 #include "data/model/RegionModel.h"
Chris@1122 33 #include "data/model/ReadOnlyWaveFileModel.h"
Chris@499 34
Chris@499 35 #include "data/fileio/FileSource.h"
Chris@520 36 #include "data/fileio/CachedFile.h"
Chris@581 37 #include "data/fileio/FileFinder.h"
Chris@522 38
Chris@726 39 #include <dataquay/BasicStore.h>
Chris@726 40 #include <dataquay/PropertyObject.h>
Chris@726 41
Chris@726 42 using Dataquay::Uri;
Chris@726 43 using Dataquay::Node;
Chris@726 44 using Dataquay::Nodes;
Chris@726 45 using Dataquay::Triple;
Chris@726 46 using Dataquay::Triples;
Chris@726 47 using Dataquay::BasicStore;
Chris@726 48 using Dataquay::PropertyObject;
Chris@726 49
Chris@439 50 class RDFImporterImpl
Chris@439 51 {
Chris@439 52 public:
Chris@1040 53 RDFImporterImpl(QString url, sv_samplerate_t sampleRate);
Chris@439 54 virtual ~RDFImporterImpl();
Chris@490 55
Chris@1040 56 void setSampleRate(sv_samplerate_t sampleRate) { m_sampleRate = sampleRate; }
Chris@439 57
Chris@439 58 bool isOK();
Chris@439 59 QString getErrorString() const;
Chris@439 60
Chris@1752 61 std::vector<ModelId> getDataModels(ProgressReporter *);
Chris@439 62
Chris@439 63 protected:
Chris@726 64 BasicStore *m_store;
Chris@730 65 Uri expand(QString s) { return m_store->expand(s); }
Chris@726 66
Chris@439 67 QString m_uristring;
Chris@439 68 QString m_errorString;
Chris@1752 69 std::map<QString, ModelId> m_audioModelMap;
Chris@1040 70 sv_samplerate_t m_sampleRate;
Chris@439 71
Chris@1752 72 std::map<ModelId, std::map<QString, float> > m_labelValueMap;
Chris@617 73
Chris@1752 74 void getDataModelsAudio(std::vector<ModelId> &, ProgressReporter *);
Chris@1752 75 void getDataModelsSparse(std::vector<ModelId> &, ProgressReporter *);
Chris@1752 76 void getDataModelsDense(std::vector<ModelId> &, ProgressReporter *);
Chris@440 77
Chris@1752 78 QString getDenseModelTitle(QString featureUri, QString featureTypeUri);
Chris@493 79
Chris@440 80 void getDenseFeatureProperties(QString featureUri,
Chris@1040 81 sv_samplerate_t &sampleRate, int &windowLength,
Chris@440 82 int &hopSize, int &width, int &height);
Chris@440 83
Chris@1752 84 void fillModel(ModelId, sv_frame_t, sv_frame_t,
Chris@1039 85 bool, std::vector<float> &, QString);
Chris@439 86 };
Chris@439 87
Chris@439 88 QString
Chris@439 89 RDFImporter::getKnownExtensions()
Chris@439 90 {
Chris@439 91 return "*.rdf *.n3 *.ttl";
Chris@439 92 }
Chris@439 93
Chris@1040 94 RDFImporter::RDFImporter(QString url, sv_samplerate_t sampleRate) :
Chris@439 95 m_d(new RDFImporterImpl(url, sampleRate))
Chris@439 96 {
Chris@439 97 }
Chris@439 98
Chris@439 99 RDFImporter::~RDFImporter()
Chris@439 100 {
Chris@439 101 delete m_d;
Chris@439 102 }
Chris@439 103
Chris@490 104 void
Chris@1040 105 RDFImporter::setSampleRate(sv_samplerate_t sampleRate)
Chris@490 106 {
Chris@490 107 m_d->setSampleRate(sampleRate);
Chris@490 108 }
Chris@490 109
Chris@439 110 bool
Chris@439 111 RDFImporter::isOK()
Chris@439 112 {
Chris@439 113 return m_d->isOK();
Chris@439 114 }
Chris@439 115
Chris@439 116 QString
Chris@439 117 RDFImporter::getErrorString() const
Chris@439 118 {
Chris@439 119 return m_d->getErrorString();
Chris@439 120 }
Chris@439 121
Chris@1752 122 std::vector<ModelId>
Chris@439 123 RDFImporter::getDataModels(ProgressReporter *r)
Chris@439 124 {
Chris@439 125 return m_d->getDataModels(r);
Chris@439 126 }
Chris@439 127
Chris@1040 128 RDFImporterImpl::RDFImporterImpl(QString uri, sv_samplerate_t sampleRate) :
Chris@726 129 m_store(new BasicStore),
Chris@439 130 m_uristring(uri),
Chris@439 131 m_sampleRate(sampleRate)
Chris@439 132 {
Chris@726 133 //!!! retrieve data if remote... then
Chris@726 134
Chris@726 135 m_store->addPrefix("mo", Uri("http://purl.org/ontology/mo/"));
Chris@726 136 m_store->addPrefix("af", Uri("http://purl.org/ontology/af/"));
Chris@726 137 m_store->addPrefix("dc", Uri("http://purl.org/dc/elements/1.1/"));
Chris@726 138 m_store->addPrefix("tl", Uri("http://purl.org/NET/c4dm/timeline.owl#"));
Chris@726 139 m_store->addPrefix("event", Uri("http://purl.org/NET/c4dm/event.owl#"));
Chris@726 140 m_store->addPrefix("rdfs", Uri("http://www.w3.org/2000/01/rdf-schema#"));
Chris@727 141
Chris@738 142 try {
Chris@738 143 QUrl url;
Chris@738 144 if (uri.startsWith("file:")) {
Chris@738 145 url = QUrl(uri);
Chris@738 146 } else {
Chris@738 147 url = QUrl::fromLocalFile(uri);
Chris@738 148 }
Chris@738 149 m_store->import(url, BasicStore::ImportIgnoreDuplicates);
Chris@738 150 } catch (std::exception &e) {
Chris@738 151 m_errorString = e.what();
Chris@736 152 }
Chris@439 153 }
Chris@439 154
Chris@439 155 RDFImporterImpl::~RDFImporterImpl()
Chris@439 156 {
Chris@726 157 delete m_store;
Chris@439 158 }
Chris@439 159
Chris@439 160 bool
Chris@439 161 RDFImporterImpl::isOK()
Chris@439 162 {
Chris@439 163 return (m_errorString == "");
Chris@439 164 }
Chris@439 165
Chris@439 166 QString
Chris@439 167 RDFImporterImpl::getErrorString() const
Chris@439 168 {
Chris@439 169 return m_errorString;
Chris@439 170 }
Chris@439 171
Chris@1752 172 std::vector<ModelId>
Chris@439 173 RDFImporterImpl::getDataModels(ProgressReporter *reporter)
Chris@439 174 {
Chris@1752 175 std::vector<ModelId> models;
Chris@439 176
Chris@499 177 getDataModelsAudio(models, reporter);
Chris@499 178
Chris@490 179 if (m_sampleRate == 0) {
Chris@616 180 m_errorString = QString("Invalid audio data model (is audio file format supported?)");
Chris@843 181 cerr << m_errorString << endl;
Chris@490 182 return models;
Chris@490 183 }
Chris@490 184
Chris@508 185 QString error;
Chris@508 186
Chris@522 187 if (m_errorString != "") {
Chris@522 188 error = m_errorString;
Chris@522 189 }
Chris@508 190 m_errorString = "";
Chris@508 191
Chris@440 192 getDataModelsDense(models, reporter);
Chris@440 193
Chris@522 194 if (m_errorString != "") {
Chris@522 195 error = m_errorString;
Chris@522 196 }
Chris@440 197 m_errorString = "";
Chris@440 198
Chris@440 199 getDataModelsSparse(models, reporter);
Chris@440 200
Chris@522 201 if (m_errorString == "" && error != "") {
Chris@522 202 m_errorString = error;
Chris@522 203 }
Chris@440 204
Chris@440 205 return models;
Chris@440 206 }
Chris@440 207
Chris@440 208 void
Chris@1752 209 RDFImporterImpl::getDataModelsAudio(std::vector<ModelId> &models,
Chris@499 210 ProgressReporter *reporter)
Chris@499 211 {
Chris@726 212 Nodes sigs = m_store->match
Chris@730 213 (Triple(Node(), Uri("a"), expand("mo:Signal"))).subjects();
Chris@499 214
Chris@726 215 foreach (Node sig, sigs) {
Chris@726 216
Chris@730 217 Node file = m_store->complete(Triple(Node(), expand("mo:encodes"), sig));
Chris@726 218 if (file == Node()) {
Chris@730 219 file = m_store->complete(Triple(sig, expand("mo:available_as"), Node()));
Chris@726 220 }
Chris@726 221 if (file == Node()) {
Chris@843 222 cerr << "RDFImporterImpl::getDataModelsAudio: ERROR: No source for signal " << sig << endl;
Chris@726 223 continue;
Chris@726 224 }
Chris@499 225
Chris@726 226 QString signal = sig.value;
Chris@726 227 QString source = file.value;
Chris@589 228
Chris@726 229 SVDEBUG << "NOTE: Seeking signal source \"" << source
Chris@726 230 << "\"..." << endl;
Chris@616 231
Chris@522 232 FileSource *fs = new FileSource(source, reporter);
Chris@616 233 if (fs->isAvailable()) {
Chris@690 234 SVDEBUG << "NOTE: Source is available: Local filename is \""
Chris@726 235 << fs->getLocalFilename()
Chris@726 236 << "\"..." << endl;
Chris@616 237 }
Chris@616 238
Chris@522 239 #ifdef NO_SV_GUI
Chris@522 240 if (!fs->isAvailable()) {
Chris@522 241 m_errorString = QString("Signal source \"%1\" is not available").arg(source);
Chris@522 242 delete fs;
Chris@522 243 continue;
Chris@522 244 }
Chris@522 245 #else
Chris@522 246 if (!fs->isAvailable()) {
Chris@726 247 SVDEBUG << "NOTE: Signal source \"" << source
Chris@726 248 << "\" is not available, using file finder..." << endl;
Chris@522 249 FileFinder *ff = FileFinder::getInstance();
Chris@581 250 if (ff) {
Chris@581 251 QString path = ff->find(FileFinder::AudioFile,
Chris@581 252 fs->getLocation(),
Chris@581 253 m_uristring);
Chris@581 254 if (path != "") {
Chris@844 255 cerr << "File finder returns: \"" << path
Chris@843 256 << "\"" << endl;
Chris@522 257 delete fs;
Chris@581 258 fs = new FileSource(path, reporter);
Chris@581 259 if (!fs->isAvailable()) {
Chris@581 260 delete fs;
Chris@581 261 m_errorString = QString("Signal source \"%1\" is not available").arg(source);
Chris@581 262 continue;
Chris@581 263 }
Chris@522 264 }
Chris@499 265 }
Chris@522 266 }
Chris@522 267 #endif
Chris@522 268
Chris@522 269 if (reporter) {
Chris@522 270 reporter->setMessage(RDFImporter::tr("Importing audio referenced in RDF..."));
Chris@522 271 }
Chris@522 272 fs->waitForData();
Chris@1752 273 auto newModel = std::make_shared<ReadOnlyWaveFileModel>
Chris@1752 274 (*fs, m_sampleRate);
Chris@522 275 if (newModel->isOK()) {
Chris@843 276 cerr << "Successfully created wave file model from source at \"" << source << "\"" << endl;
Chris@1752 277 auto modelId = ModelById::add(newModel);
Chris@1752 278 models.push_back(modelId);
Chris@1752 279 m_audioModelMap[signal] = modelId;
Chris@522 280 if (m_sampleRate == 0) {
Chris@522 281 m_sampleRate = newModel->getSampleRate();
Chris@499 282 }
Chris@508 283 } else {
Chris@522 284 m_errorString = QString("Failed to create wave file model from source at \"%1\"").arg(source);
Chris@499 285 }
Chris@522 286 delete fs;
Chris@499 287 }
Chris@499 288 }
Chris@499 289
Chris@499 290 void
Chris@1752 291 RDFImporterImpl::getDataModelsDense(std::vector<ModelId> &models,
Chris@440 292 ProgressReporter *reporter)
Chris@440 293 {
Chris@499 294 if (reporter) {
Chris@499 295 reporter->setMessage(RDFImporter::tr("Importing dense signal data from RDF..."));
Chris@499 296 }
Chris@499 297
Chris@726 298 Nodes sigFeatures = m_store->match
Chris@730 299 (Triple(Node(), expand("af:signal_feature"), Node())).objects();
Chris@440 300
Chris@726 301 foreach (Node sf, sigFeatures) {
Chris@440 302
Chris@726 303 if (sf.type != Node::URI && sf.type != Node::Blank) continue;
Chris@726 304
Chris@730 305 Node t = m_store->complete(Triple(sf, expand("a"), Node()));
Chris@730 306 Node v = m_store->complete(Triple(sf, expand("af:value"), Node()));
Chris@440 307
Chris@726 308 QString feature = sf.value;
Chris@726 309 QString type = t.value;
Chris@726 310 QString value = v.value;
Chris@726 311
Chris@726 312 if (type == "" || value == "") continue;
Chris@440 313
Chris@1040 314 sv_samplerate_t sampleRate = 0;
Chris@440 315 int windowLength = 0;
Chris@440 316 int hopSize = 0;
Chris@440 317 int width = 0;
Chris@440 318 int height = 0;
Chris@440 319 getDenseFeatureProperties
Chris@440 320 (feature, sampleRate, windowLength, hopSize, width, height);
Chris@440 321
Chris@440 322 if (sampleRate != 0 && sampleRate != m_sampleRate) {
Chris@440 323 cerr << "WARNING: Sample rate in dense feature description does not match our underlying rate -- using rate from feature description" << endl;
Chris@440 324 }
Chris@440 325 if (sampleRate == 0) sampleRate = m_sampleRate;
Chris@440 326
Chris@440 327 if (hopSize == 0) {
Chris@440 328 cerr << "WARNING: Dense feature description does not specify a hop size -- assuming 1" << endl;
Chris@440 329 hopSize = 1;
Chris@440 330 }
Chris@440 331
Chris@440 332 if (height == 0) {
Chris@440 333 cerr << "WARNING: Dense feature description does not specify feature signal dimensions -- assuming one-dimensional (height = 1)" << endl;
Chris@440 334 height = 1;
Chris@440 335 }
Chris@440 336
Chris@440 337 QStringList values = value.split(' ', QString::SkipEmptyParts);
Chris@440 338
Chris@440 339 if (values.empty()) {
Chris@440 340 cerr << "WARNING: Dense feature description does not specify any values!" << endl;
Chris@440 341 continue;
Chris@440 342 }
Chris@440 343
Chris@440 344 if (height == 1) {
Chris@440 345
Chris@1752 346 auto m = std::make_shared<SparseTimeValueModel>
Chris@440 347 (sampleRate, hopSize, false);
Chris@440 348
Chris@440 349 for (int j = 0; j < values.size(); ++j) {
Chris@440 350 float f = values[j].toFloat();
Chris@1651 351 Event e(j * hopSize, f, "");
Chris@1651 352 m->add(e);
Chris@440 353 }
Chris@493 354
Chris@1752 355 m->setObjectName(getDenseModelTitle(feature, type));
Chris@558 356 m->setRDFTypeURI(type);
Chris@1752 357 models.push_back(ModelById::add(m));
Chris@440 358
Chris@440 359 } else {
Chris@440 360
Chris@1752 361 auto m = std::make_shared<EditableDenseThreeDimensionalModel>
Chris@1777 362 (sampleRate, hopSize, height, false);
Chris@440 363
Chris@440 364 EditableDenseThreeDimensionalModel::Column column;
Chris@440 365
Chris@440 366 int x = 0;
Chris@440 367
Chris@440 368 for (int j = 0; j < values.size(); ++j) {
Chris@440 369 if (j % height == 0 && !column.empty()) {
Chris@440 370 m->setColumn(x++, column);
Chris@440 371 column.clear();
Chris@440 372 }
Chris@440 373 column.push_back(values[j].toFloat());
Chris@440 374 }
Chris@440 375
Chris@440 376 if (!column.empty()) {
Chris@440 377 m->setColumn(x++, column);
Chris@440 378 }
Chris@440 379
Chris@1752 380 m->setObjectName(getDenseModelTitle(feature, type));
Chris@558 381 m->setRDFTypeURI(type);
Chris@1752 382 models.push_back(ModelById::add(m));
Chris@440 383 }
Chris@440 384 }
Chris@440 385 }
Chris@440 386
Chris@1752 387 QString
Chris@1752 388 RDFImporterImpl::getDenseModelTitle(QString featureUri,
Chris@493 389 QString featureTypeUri)
Chris@493 390 {
Chris@730 391 Node n = m_store->complete
Chris@730 392 (Triple(Uri(featureUri), expand("dc:title"), Node()));
Chris@493 393
Chris@726 394 if (n.type == Node::Literal && n.value != "") {
Chris@726 395 SVDEBUG << "RDFImporterImpl::getDenseModelTitle: Title (from signal) \"" << n.value << "\"" << endl;
Chris@1752 396 return n.value;
Chris@493 397 }
Chris@493 398
Chris@730 399 n = m_store->complete
Chris@730 400 (Triple(Uri(featureTypeUri), expand("dc:title"), Node()));
Chris@726 401
Chris@726 402 if (n.type == Node::Literal && n.value != "") {
Chris@726 403 SVDEBUG << "RDFImporterImpl::getDenseModelTitle: Title (from signal type) \"" << n.value << "\"" << endl;
Chris@1752 404 return n.value;
Chris@493 405 }
Chris@493 406
Chris@690 407 SVDEBUG << "RDFImporterImpl::getDenseModelTitle: No title available for feature <" << featureUri << ">" << endl;
Chris@1752 408 return {};
Chris@493 409 }
Chris@493 410
Chris@493 411 void
Chris@440 412 RDFImporterImpl::getDenseFeatureProperties(QString featureUri,
Chris@1040 413 sv_samplerate_t &sampleRate, int &windowLength,
Chris@440 414 int &hopSize, int &width, int &height)
Chris@440 415 {
Chris@730 416 Node dim = m_store->complete
Chris@730 417 (Triple(Uri(featureUri), expand("af:dimensions"), Node()));
Chris@489 418
Chris@726 419 cerr << "Dimensions = \"" << dim.value << "\"" << endl;
Chris@440 420
Chris@726 421 if (dim.type == Node::Literal && dim.value != "") {
Chris@726 422 QStringList dl = dim.value.split(" ");
Chris@726 423 if (dl.empty()) dl.push_back(dim.value);
Chris@440 424 if (dl.size() > 0) height = dl[0].toInt();
Chris@440 425 if (dl.size() > 1) width = dl[1].toInt();
Chris@440 426 }
Chris@726 427
Chris@726 428 // Looking for rate, hop, window from:
Chris@726 429 //
Chris@726 430 // ?feature mo:time ?time .
Chris@726 431 // ?time a tl:Interval .
Chris@726 432 // ?time tl:onTimeLine ?timeline .
Chris@726 433 // ?map tl:rangeTimeLine ?timeline .
Chris@726 434 // ?map tl:sampleRate ?rate .
Chris@726 435 // ?map tl:hopSize ?hop .
Chris@726 436 // ?map tl:windowLength ?window .
Chris@440 437
Chris@730 438 Node interval = m_store->complete(Triple(Uri(featureUri), expand("mo:time"), Node()));
Chris@440 439
Chris@730 440 if (!m_store->contains(Triple(interval, expand("a"), expand("tl:Interval")))) {
Chris@726 441 cerr << "RDFImporterImpl::getDenseFeatureProperties: Feature time node "
Chris@726 442 << interval << " is not a tl:Interval" << endl;
Chris@726 443 return;
Chris@440 444 }
Chris@440 445
Chris@730 446 Node tl = m_store->complete(Triple(interval, expand("tl:onTimeLine"), Node()));
Chris@726 447
Chris@726 448 if (tl == Node()) {
Chris@726 449 cerr << "RDFImporterImpl::getDenseFeatureProperties: Interval node "
Chris@726 450 << interval << " lacks tl:onTimeLine property" << endl;
Chris@726 451 return;
Chris@440 452 }
Chris@440 453
Chris@730 454 Node map = m_store->complete(Triple(Node(), expand("tl:rangeTimeLine"), tl));
Chris@726 455
Chris@726 456 if (map == Node()) {
Chris@726 457 cerr << "RDFImporterImpl::getDenseFeatureProperties: No map for "
Chris@726 458 << "timeline node " << tl << endl;
Chris@726 459 }
Chris@726 460
Chris@726 461 PropertyObject po(m_store, "tl:", map);
Chris@726 462
Chris@726 463 if (po.hasProperty("sampleRate")) {
Chris@1040 464 sampleRate = po.getProperty("sampleRate").toDouble();
Chris@726 465 }
Chris@726 466 if (po.hasProperty("hopSize")) {
Chris@726 467 hopSize = po.getProperty("hopSize").toInt();
Chris@726 468 }
Chris@726 469 if (po.hasProperty("windowLength")) {
Chris@726 470 windowLength = po.getProperty("windowLength").toInt();
Chris@440 471 }
Chris@440 472
Chris@440 473 cerr << "sr = " << sampleRate << ", hop = " << hopSize << ", win = " << windowLength << endl;
Chris@440 474 }
Chris@440 475
Chris@440 476 void
Chris@1752 477 RDFImporterImpl::getDataModelsSparse(std::vector<ModelId> &models,
Chris@440 478 ProgressReporter *reporter)
Chris@440 479 {
Chris@499 480 if (reporter) {
Chris@499 481 reporter->setMessage(RDFImporter::tr("Importing event data from RDF..."));
Chris@499 482 }
Chris@499 483
Chris@726 484 /*
Chris@726 485 This function is only used for sparse data (for dense data we
Chris@726 486 would be in getDataModelsDense instead).
Chris@489 487
Chris@726 488 Our query is intended to retrieve every thing that has a time,
Chris@726 489 and every feature type and value associated with a thing that
Chris@726 490 has a time.
Chris@439 491
Chris@726 492 We will then need to refine this big bag of results into a set
Chris@726 493 of data models.
Chris@439 494
Chris@726 495 Results that have different source signals should go into
Chris@726 496 different models.
Chris@439 497
Chris@726 498 Results that have different feature types should go into
Chris@726 499 different models.
Chris@726 500 */
Chris@439 501
Chris@726 502 Nodes sigs = m_store->match
Chris@730 503 (Triple(Node(), expand("a"), expand("mo:Signal"))).subjects();
Chris@449 504
Chris@616 505 // Map from timeline uri to event type to dimensionality to
Chris@1752 506 // presence of duration to model id. Whee!
Chris@1752 507 std::map<QString, std::map<QString, std::map<int, std::map<bool, ModelId> > > >
Chris@449 508 modelMap;
Chris@449 509
Chris@726 510 foreach (Node sig, sigs) {
Chris@726 511
Chris@730 512 Node interval = m_store->complete(Triple(sig, expand("mo:time"), Node()));
Chris@726 513 if (interval == Node()) continue;
Chris@439 514
Chris@730 515 Node tl = m_store->complete(Triple(interval, expand("tl:onTimeLine"), Node()));
Chris@726 516 if (tl == Node()) continue;
Chris@499 517
Chris@730 518 Nodes times = m_store->match(Triple(Node(), expand("tl:onTimeLine"), tl)).subjects();
Chris@449 519
Chris@726 520 foreach (Node tn, times) {
Chris@726 521
Chris@730 522 Nodes timedThings = m_store->match(Triple(Node(), expand("event:time"), tn)).subjects();
Chris@439 523
Chris@726 524 foreach (Node thing, timedThings) {
Chris@726 525
Chris@730 526 Node typ = m_store->complete(Triple(thing, expand("a"), Node()));
Chris@726 527 if (typ == Node()) continue;
Chris@439 528
Chris@730 529 Node valu = m_store->complete(Triple(thing, expand("af:feature"), Node()));
Chris@510 530
Chris@726 531 QString source = sig.value;
Chris@726 532 QString timeline = tl.value;
Chris@726 533 QString type = typ.value;
Chris@726 534 QString thinguri = thing.value;
Chris@510 535
Chris@726 536 /*
Chris@726 537 For sparse data, the determining factors in deciding
Chris@726 538 what model to use are: Do the features have values?
Chris@726 539 and Do the features have duration?
Chris@449 540
Chris@726 541 We can run through the results and check off whether
Chris@726 542 we find values and duration for each of the
Chris@726 543 source+type keys, and then run through the
Chris@726 544 source+type keys pushing each of the results into a
Chris@726 545 suitable model.
Chris@439 546
Chris@726 547 Unfortunately, at this point we do not yet have any
Chris@726 548 actual timing data (time/duration) -- just the time
Chris@726 549 URI.
Chris@449 550
Chris@726 551 What we _could_ do is to create one of each type of
Chris@726 552 model at the start, for each of the source+type
Chris@726 553 keys, and then push each feature into the relevant
Chris@726 554 model depending on what we find out about it. Then
Chris@726 555 return only non-empty models.
Chris@726 556 */
Chris@439 557
Chris@726 558 QString label = "";
Chris@726 559 bool text = (type.contains("Text") || type.contains("text")); // Ha, ha
Chris@726 560 bool note = (type.contains("Note") || type.contains("note")); // Guffaw
Chris@449 561
Chris@726 562 if (text) {
Chris@730 563 label = m_store->complete(Triple(thing, expand("af:text"), Node())).value;
Chris@726 564 }
Chris@726 565
Chris@726 566 if (label == "") {
Chris@730 567 label = m_store->complete(Triple(thing, expand("rdfs:label"), Node())).value;
Chris@726 568 }
Chris@449 569
Chris@726 570 RealTime time;
Chris@726 571 RealTime duration;
Chris@726 572
Chris@930 573 // bool haveTime = false;
Chris@726 574 bool haveDuration = false;
Chris@726 575
Chris@730 576 Node at = m_store->complete(Triple(tn, expand("tl:at"), Node()));
Chris@726 577
Chris@726 578 if (at != Node()) {
Chris@726 579 time = RealTime::fromXsdDuration(at.value.toStdString());
Chris@930 580 // haveTime = true;
Chris@726 581 } else {
Chris@726 582 //!!! NB we're using rather old terminology for these things, apparently:
Chris@726 583 // beginsAt -> start
Chris@726 584 // onTimeLine -> timeline
Chris@726 585
Chris@730 586 Node start = m_store->complete(Triple(tn, expand("tl:beginsAt"), Node()));
Chris@730 587 Node dur = m_store->complete(Triple(tn, expand("tl:duration"), Node()));
Chris@726 588 if (start != Node() && dur != Node()) {
Chris@726 589 time = RealTime::fromXsdDuration
Chris@726 590 (start.value.toStdString());
Chris@726 591 duration = RealTime::fromXsdDuration
Chris@726 592 (dur.value.toStdString());
Chris@930 593 // haveTime = haveDuration = true;
Chris@726 594 }
Chris@726 595 }
Chris@726 596
Chris@726 597 QString valuestring = valu.value;
Chris@726 598 std::vector<float> values;
Chris@726 599
Chris@726 600 if (valuestring != "") {
Chris@726 601 QStringList vsl = valuestring.split(" ", QString::SkipEmptyParts);
Chris@726 602 for (int j = 0; j < vsl.size(); ++j) {
Chris@726 603 bool success = false;
Chris@726 604 float v = vsl[j].toFloat(&success);
Chris@726 605 if (success) values.push_back(v);
Chris@726 606 }
Chris@726 607 }
Chris@726 608
Chris@726 609 int dimensions = 1;
Chris@726 610 if (values.size() == 1) dimensions = 2;
Chris@726 611 else if (values.size() > 1) dimensions = 3;
Chris@726 612
Chris@1752 613 ModelId modelId;
Chris@726 614
Chris@726 615 if (modelMap[timeline][type][dimensions].find(haveDuration) ==
Chris@726 616 modelMap[timeline][type][dimensions].end()) {
Chris@449 617
Chris@449 618 /*
Chris@690 619 SVDEBUG << "Creating new model: source = " << source << ", type = " << type << ", dimensions = "
Chris@449 620 << dimensions << ", haveDuration = " << haveDuration
Chris@449 621 << ", time = " << time << ", duration = " << duration
Chris@687 622 << endl;
Chris@449 623 */
Chris@1752 624
Chris@1752 625 Model *model = nullptr;
Chris@1752 626
Chris@726 627 if (!haveDuration) {
Chris@449 628
Chris@726 629 if (dimensions == 1) {
Chris@726 630 if (text) {
Chris@726 631 model = new TextModel(m_sampleRate, 1, false);
Chris@726 632 } else {
Chris@726 633 model = new SparseOneDimensionalModel(m_sampleRate, 1, false);
Chris@726 634 }
Chris@726 635 } else if (dimensions == 2) {
Chris@726 636 if (text) {
Chris@726 637 model = new TextModel(m_sampleRate, 1, false);
Chris@726 638 } else {
Chris@726 639 model = new SparseTimeValueModel(m_sampleRate, 1, false);
Chris@726 640 }
Chris@726 641 } else {
Chris@726 642 // We don't have a three-dimensional sparse model,
Chris@726 643 // so use a note model. We do have some logic (in
Chris@726 644 // extractStructure below) for guessing whether
Chris@726 645 // this should after all have been a dense model,
Chris@726 646 // but it's hard to apply it because we don't have
Chris@726 647 // all the necessary timing data yet... hmm
Chris@726 648 model = new NoteModel(m_sampleRate, 1, false);
Chris@726 649 }
Chris@449 650
Chris@726 651 } else { // haveDuration
Chris@510 652
Chris@726 653 if (note || (dimensions > 2)) {
Chris@726 654 model = new NoteModel(m_sampleRate, 1, false);
Chris@726 655 } else {
Chris@726 656 // If our units are frequency or midi pitch, we
Chris@726 657 // should be using a note model... hm
Chris@726 658 model = new RegionModel(m_sampleRate, 1, false);
Chris@726 659 }
Chris@510 660 }
Chris@449 661
Chris@726 662 model->setRDFTypeURI(type);
Chris@449 663
Chris@726 664 if (m_audioModelMap.find(source) != m_audioModelMap.end()) {
Chris@843 665 cerr << "source model for " << model << " is " << m_audioModelMap[source] << endl;
Chris@1752 666 model->setSourceModel(m_audioModelMap[source]);
Chris@510 667 }
Chris@449 668
Chris@730 669 QString title = m_store->complete
Chris@730 670 (Triple(typ, expand("dc:title"), Node())).value;
Chris@726 671 if (title == "") {
Chris@726 672 // take it from the end of the event type
Chris@726 673 title = type;
Chris@726 674 title.replace(QRegExp("^.*[/#]"), "");
Chris@726 675 }
Chris@726 676 model->setObjectName(title);
Chris@449 677
Chris@1752 678 modelId = ModelById::add(std::shared_ptr<Model>(model));
Chris@1752 679 modelMap[timeline][type][dimensions][haveDuration] = modelId;
Chris@1752 680 models.push_back(modelId);
Chris@449 681 }
Chris@449 682
Chris@1752 683 modelId = modelMap[timeline][type][dimensions][haveDuration];
Chris@449 684
Chris@1752 685 if (!modelId.isNone()) {
Chris@1752 686 sv_frame_t ftime =
Chris@1752 687 RealTime::realTime2Frame(time, m_sampleRate);
Chris@1752 688 sv_frame_t fduration =
Chris@1752 689 RealTime::realTime2Frame(duration, m_sampleRate);
Chris@1752 690 fillModel(modelId, ftime, fduration,
Chris@1752 691 haveDuration, values, label);
Chris@449 692 }
Chris@449 693 }
Chris@439 694 }
Chris@439 695 }
Chris@439 696 }
Chris@439 697
Chris@439 698 void
Chris@1752 699 RDFImporterImpl::fillModel(ModelId modelId,
Chris@1039 700 sv_frame_t ftime,
Chris@1039 701 sv_frame_t fduration,
Chris@449 702 bool haveDuration,
Chris@449 703 std::vector<float> &values,
Chris@449 704 QString label)
Chris@449 705 {
Chris@690 706 // SVDEBUG << "RDFImporterImpl::fillModel: adding point at frame " << ftime << endl;
Chris@492 707
Chris@1752 708 if (auto sodm = ModelById::getAs<SparseOneDimensionalModel>(modelId)) {
Chris@1658 709 Event point(ftime, label);
Chris@1658 710 sodm->add(point);
Chris@449 711 return;
Chris@449 712 }
Chris@449 713
Chris@1752 714 if (auto tm = ModelById::getAs<TextModel>(modelId)) {
Chris@1661 715 Event e
Chris@510 716 (ftime,
Chris@510 717 values.empty() ? 0.5f : values[0] < 0.f ? 0.f : values[0] > 1.f ? 1.f : values[0], // I was young and feckless once too
Chris@510 718 label);
Chris@1661 719 tm->add(e);
Chris@510 720 return;
Chris@510 721 }
Chris@510 722
Chris@1752 723 if (auto stvm = ModelById::getAs<SparseTimeValueModel>(modelId)) {
Chris@1651 724 Event e(ftime, values.empty() ? 0.f : values[0], label);
Chris@1651 725 stvm->add(e);
Chris@449 726 return;
Chris@449 727 }
Chris@449 728
Chris@1752 729 if (auto nm = ModelById::getAs<NoteModel>(modelId)) {
Chris@449 730 if (haveDuration) {
Chris@449 731 float value = 0.f, level = 1.f;
Chris@449 732 if (!values.empty()) {
Chris@449 733 value = values[0];
Chris@449 734 if (values.size() > 1) {
Chris@449 735 level = values[1];
Chris@449 736 }
Chris@449 737 }
Chris@1644 738 Event e(ftime, value, fduration, level, label);
Chris@1644 739 nm->add(e);
Chris@449 740 } else {
Chris@449 741 float value = 0.f, duration = 1.f, level = 1.f;
Chris@449 742 if (!values.empty()) {
Chris@449 743 value = values[0];
Chris@449 744 if (values.size() > 1) {
Chris@449 745 duration = values[1];
Chris@449 746 if (values.size() > 2) {
Chris@449 747 level = values[2];
Chris@449 748 }
Chris@449 749 }
Chris@449 750 }
Chris@1644 751 Event e(ftime, value, sv_frame_t(lrintf(duration)),
Chris@1643 752 level, label);
Chris@1644 753 nm->add(e);
Chris@449 754 }
Chris@449 755 return;
Chris@449 756 }
Chris@449 757
Chris@1752 758 if (auto rm = ModelById::getAs<RegionModel>(modelId)) {
Chris@617 759 float value = 0.f;
Chris@617 760 if (values.empty()) {
Chris@617 761 // no values? map each unique label to a distinct value
Chris@1752 762 if (m_labelValueMap[modelId].find(label) == m_labelValueMap[modelId].end()) {
Chris@1752 763 m_labelValueMap[modelId][label] = rm->getValueMaximum() + 1.f;
Chris@617 764 }
Chris@1752 765 value = m_labelValueMap[modelId][label];
Chris@617 766 } else {
Chris@617 767 value = values[0];
Chris@617 768 }
Chris@449 769 if (haveDuration) {
Chris@1649 770 Event e(ftime, value, fduration, label);
Chris@1649 771 rm->add(e);
Chris@449 772 } else {
Chris@449 773 // This won't actually happen -- we only create region models
Chris@449 774 // if we do have duration -- but just for completeness
Chris@617 775 float duration = 1.f;
Chris@449 776 if (!values.empty()) {
Chris@449 777 value = values[0];
Chris@449 778 if (values.size() > 1) {
Chris@449 779 duration = values[1];
Chris@449 780 }
Chris@449 781 }
Chris@1649 782 Event e(ftime, value, sv_frame_t(lrintf(duration)), label);
Chris@1649 783 rm->add(e);
Chris@449 784 }
Chris@449 785 return;
Chris@449 786 }
Chris@449 787
Chris@843 788 cerr << "WARNING: RDFImporterImpl::fillModel: Unknown or unexpected model type" << endl;
Chris@449 789 return;
Chris@449 790 }
Chris@449 791
Chris@490 792 RDFImporter::RDFDocumentType
Chris@490 793 RDFImporter::identifyDocumentType(QString url)
Chris@490 794 {
Chris@490 795 bool haveAudio = false;
Chris@490 796 bool haveAnnotations = false;
Chris@726 797 bool haveRDF = false;
Chris@449 798
Chris@1582 799 BasicStore *store = nullptr;
Chris@726 800
Chris@726 801 // This is not expected to return anything useful, but if it does
Chris@726 802 // anything at all then we know we have RDF
Chris@726 803 try {
Chris@738 804 //!!! non-local document?
Chris@726 805 store = BasicStore::load(QUrl(url));
Chris@730 806 Triple t = store->matchOnce(Triple());
Chris@726 807 if (t != Triple()) haveRDF = true;
Chris@1471 808 } catch (std::exception &) {
Chris@738 809 // nothing; haveRDF will be false so the next bit catches it
Chris@726 810 }
Chris@726 811
Chris@726 812 if (!haveRDF) {
Chris@726 813 delete store;
Chris@499 814 return NotRDF;
Chris@499 815 }
Chris@499 816
Chris@726 817 store->addPrefix("mo", Uri("http://purl.org/ontology/mo/"));
Chris@726 818 store->addPrefix("event", Uri("http://purl.org/NET/c4dm/event.owl#"));
Chris@726 819 store->addPrefix("af", Uri("http://purl.org/ontology/af/"));
Chris@726 820
Chris@588 821 // "MO-conformant" structure for audio files
Chris@588 822
Chris@730 823 Node n = store->complete(Triple(Node(), Uri("a"), store->expand("mo:AudioFile")));
Chris@726 824 if (n != Node() && n.type == Node::URI) {
Chris@588 825
Chris@490 826 haveAudio = true;
Chris@588 827
Chris@588 828 } else {
Chris@588 829
Chris@588 830 // Sonic Annotator v0.2 and below used to write this structure
Chris@588 831 // (which is not properly in conformance with the Music
Chris@588 832 // Ontology)
Chris@588 833
Chris@730 834 Nodes sigs = store->match(Triple(Node(), Uri("a"), store->expand("mo:Signal"))).subjects();
Chris@726 835 foreach (Node sig, sigs) {
Chris@730 836 Node aa = store->complete(Triple(sig, store->expand("mo:available_as"), Node()));
Chris@726 837 if (aa != Node()) {
Chris@726 838 haveAudio = true;
Chris@726 839 break;
Chris@726 840 }
Chris@588 841 }
Chris@490 842 }
Chris@490 843
Chris@690 844 SVDEBUG << "NOTE: RDFImporter::identifyDocumentType: haveAudio = "
Chris@687 845 << haveAudio << endl;
Chris@616 846
Chris@736 847 // can't call complete() with two Nothing nodes
Chris@736 848 n = store->matchOnce(Triple(Node(), store->expand("event:time"), Node())).c;
Chris@726 849 if (n != Node()) {
Chris@490 850 haveAnnotations = true;
Chris@490 851 }
Chris@490 852
Chris@490 853 if (!haveAnnotations) {
Chris@736 854 // can't call complete() with two Nothing nodes
Chris@736 855 n = store->matchOnce(Triple(Node(), store->expand("af:signal_feature"), Node())).c;
Chris@726 856 if (n != Node()) {
Chris@490 857 haveAnnotations = true;
Chris@490 858 }
Chris@490 859 }
Chris@490 860
Chris@690 861 SVDEBUG << "NOTE: RDFImporter::identifyDocumentType: haveAnnotations = "
Chris@687 862 << haveAnnotations << endl;
Chris@616 863
Chris@726 864 delete store;
Chris@542 865
Chris@490 866 if (haveAudio) {
Chris@490 867 if (haveAnnotations) {
Chris@490 868 return AudioRefAndAnnotations;
Chris@490 869 } else {
Chris@490 870 return AudioRef;
Chris@490 871 }
Chris@490 872 } else {
Chris@490 873 if (haveAnnotations) {
Chris@490 874 return Annotations;
Chris@490 875 } else {
Chris@499 876 return OtherRDFDocument;
Chris@490 877 }
Chris@490 878 }
Chris@492 879
Chris@542 880 return OtherRDFDocument;
Chris@490 881 }
Chris@490 882