annotate rdf/RDFImporter.cpp @ 1752:6d09d68165a4 by-id

Further review of ById: make IDs only available when adding a model to the ById store, not by querying the item directly. This means any id encountered in the wild must have been added to the store at some point (even if later released), which simplifies reasoning about lifecycles
author Chris Cannam
date Fri, 05 Jul 2019 15:28:07 +0100
parents 565575463752
children d484490cdf69
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@535 362 (sampleRate, hopSize, height,
Chris@535 363 EditableDenseThreeDimensionalModel::NoCompression, false);
Chris@440 364
Chris@440 365 EditableDenseThreeDimensionalModel::Column column;
Chris@440 366
Chris@440 367 int x = 0;
Chris@440 368
Chris@440 369 for (int j = 0; j < values.size(); ++j) {
Chris@440 370 if (j % height == 0 && !column.empty()) {
Chris@440 371 m->setColumn(x++, column);
Chris@440 372 column.clear();
Chris@440 373 }
Chris@440 374 column.push_back(values[j].toFloat());
Chris@440 375 }
Chris@440 376
Chris@440 377 if (!column.empty()) {
Chris@440 378 m->setColumn(x++, column);
Chris@440 379 }
Chris@440 380
Chris@1752 381 m->setObjectName(getDenseModelTitle(feature, type));
Chris@558 382 m->setRDFTypeURI(type);
Chris@1752 383 models.push_back(ModelById::add(m));
Chris@440 384 }
Chris@440 385 }
Chris@440 386 }
Chris@440 387
Chris@1752 388 QString
Chris@1752 389 RDFImporterImpl::getDenseModelTitle(QString featureUri,
Chris@493 390 QString featureTypeUri)
Chris@493 391 {
Chris@730 392 Node n = m_store->complete
Chris@730 393 (Triple(Uri(featureUri), expand("dc:title"), Node()));
Chris@493 394
Chris@726 395 if (n.type == Node::Literal && n.value != "") {
Chris@726 396 SVDEBUG << "RDFImporterImpl::getDenseModelTitle: Title (from signal) \"" << n.value << "\"" << endl;
Chris@1752 397 return n.value;
Chris@493 398 }
Chris@493 399
Chris@730 400 n = m_store->complete
Chris@730 401 (Triple(Uri(featureTypeUri), expand("dc:title"), Node()));
Chris@726 402
Chris@726 403 if (n.type == Node::Literal && n.value != "") {
Chris@726 404 SVDEBUG << "RDFImporterImpl::getDenseModelTitle: Title (from signal type) \"" << n.value << "\"" << endl;
Chris@1752 405 return n.value;
Chris@493 406 }
Chris@493 407
Chris@690 408 SVDEBUG << "RDFImporterImpl::getDenseModelTitle: No title available for feature <" << featureUri << ">" << endl;
Chris@1752 409 return {};
Chris@493 410 }
Chris@493 411
Chris@493 412 void
Chris@440 413 RDFImporterImpl::getDenseFeatureProperties(QString featureUri,
Chris@1040 414 sv_samplerate_t &sampleRate, int &windowLength,
Chris@440 415 int &hopSize, int &width, int &height)
Chris@440 416 {
Chris@730 417 Node dim = m_store->complete
Chris@730 418 (Triple(Uri(featureUri), expand("af:dimensions"), Node()));
Chris@489 419
Chris@726 420 cerr << "Dimensions = \"" << dim.value << "\"" << endl;
Chris@440 421
Chris@726 422 if (dim.type == Node::Literal && dim.value != "") {
Chris@726 423 QStringList dl = dim.value.split(" ");
Chris@726 424 if (dl.empty()) dl.push_back(dim.value);
Chris@440 425 if (dl.size() > 0) height = dl[0].toInt();
Chris@440 426 if (dl.size() > 1) width = dl[1].toInt();
Chris@440 427 }
Chris@726 428
Chris@726 429 // Looking for rate, hop, window from:
Chris@726 430 //
Chris@726 431 // ?feature mo:time ?time .
Chris@726 432 // ?time a tl:Interval .
Chris@726 433 // ?time tl:onTimeLine ?timeline .
Chris@726 434 // ?map tl:rangeTimeLine ?timeline .
Chris@726 435 // ?map tl:sampleRate ?rate .
Chris@726 436 // ?map tl:hopSize ?hop .
Chris@726 437 // ?map tl:windowLength ?window .
Chris@440 438
Chris@730 439 Node interval = m_store->complete(Triple(Uri(featureUri), expand("mo:time"), Node()));
Chris@440 440
Chris@730 441 if (!m_store->contains(Triple(interval, expand("a"), expand("tl:Interval")))) {
Chris@726 442 cerr << "RDFImporterImpl::getDenseFeatureProperties: Feature time node "
Chris@726 443 << interval << " is not a tl:Interval" << endl;
Chris@726 444 return;
Chris@440 445 }
Chris@440 446
Chris@730 447 Node tl = m_store->complete(Triple(interval, expand("tl:onTimeLine"), Node()));
Chris@726 448
Chris@726 449 if (tl == Node()) {
Chris@726 450 cerr << "RDFImporterImpl::getDenseFeatureProperties: Interval node "
Chris@726 451 << interval << " lacks tl:onTimeLine property" << endl;
Chris@726 452 return;
Chris@440 453 }
Chris@440 454
Chris@730 455 Node map = m_store->complete(Triple(Node(), expand("tl:rangeTimeLine"), tl));
Chris@726 456
Chris@726 457 if (map == Node()) {
Chris@726 458 cerr << "RDFImporterImpl::getDenseFeatureProperties: No map for "
Chris@726 459 << "timeline node " << tl << endl;
Chris@726 460 }
Chris@726 461
Chris@726 462 PropertyObject po(m_store, "tl:", map);
Chris@726 463
Chris@726 464 if (po.hasProperty("sampleRate")) {
Chris@1040 465 sampleRate = po.getProperty("sampleRate").toDouble();
Chris@726 466 }
Chris@726 467 if (po.hasProperty("hopSize")) {
Chris@726 468 hopSize = po.getProperty("hopSize").toInt();
Chris@726 469 }
Chris@726 470 if (po.hasProperty("windowLength")) {
Chris@726 471 windowLength = po.getProperty("windowLength").toInt();
Chris@440 472 }
Chris@440 473
Chris@440 474 cerr << "sr = " << sampleRate << ", hop = " << hopSize << ", win = " << windowLength << endl;
Chris@440 475 }
Chris@440 476
Chris@440 477 void
Chris@1752 478 RDFImporterImpl::getDataModelsSparse(std::vector<ModelId> &models,
Chris@440 479 ProgressReporter *reporter)
Chris@440 480 {
Chris@499 481 if (reporter) {
Chris@499 482 reporter->setMessage(RDFImporter::tr("Importing event data from RDF..."));
Chris@499 483 }
Chris@499 484
Chris@726 485 /*
Chris@726 486 This function is only used for sparse data (for dense data we
Chris@726 487 would be in getDataModelsDense instead).
Chris@489 488
Chris@726 489 Our query is intended to retrieve every thing that has a time,
Chris@726 490 and every feature type and value associated with a thing that
Chris@726 491 has a time.
Chris@439 492
Chris@726 493 We will then need to refine this big bag of results into a set
Chris@726 494 of data models.
Chris@439 495
Chris@726 496 Results that have different source signals should go into
Chris@726 497 different models.
Chris@439 498
Chris@726 499 Results that have different feature types should go into
Chris@726 500 different models.
Chris@726 501 */
Chris@439 502
Chris@726 503 Nodes sigs = m_store->match
Chris@730 504 (Triple(Node(), expand("a"), expand("mo:Signal"))).subjects();
Chris@449 505
Chris@616 506 // Map from timeline uri to event type to dimensionality to
Chris@1752 507 // presence of duration to model id. Whee!
Chris@1752 508 std::map<QString, std::map<QString, std::map<int, std::map<bool, ModelId> > > >
Chris@449 509 modelMap;
Chris@449 510
Chris@726 511 foreach (Node sig, sigs) {
Chris@726 512
Chris@730 513 Node interval = m_store->complete(Triple(sig, expand("mo:time"), Node()));
Chris@726 514 if (interval == Node()) continue;
Chris@439 515
Chris@730 516 Node tl = m_store->complete(Triple(interval, expand("tl:onTimeLine"), Node()));
Chris@726 517 if (tl == Node()) continue;
Chris@499 518
Chris@730 519 Nodes times = m_store->match(Triple(Node(), expand("tl:onTimeLine"), tl)).subjects();
Chris@449 520
Chris@726 521 foreach (Node tn, times) {
Chris@726 522
Chris@730 523 Nodes timedThings = m_store->match(Triple(Node(), expand("event:time"), tn)).subjects();
Chris@439 524
Chris@726 525 foreach (Node thing, timedThings) {
Chris@726 526
Chris@730 527 Node typ = m_store->complete(Triple(thing, expand("a"), Node()));
Chris@726 528 if (typ == Node()) continue;
Chris@439 529
Chris@730 530 Node valu = m_store->complete(Triple(thing, expand("af:feature"), Node()));
Chris@510 531
Chris@726 532 QString source = sig.value;
Chris@726 533 QString timeline = tl.value;
Chris@726 534 QString type = typ.value;
Chris@726 535 QString thinguri = thing.value;
Chris@510 536
Chris@726 537 /*
Chris@726 538 For sparse data, the determining factors in deciding
Chris@726 539 what model to use are: Do the features have values?
Chris@726 540 and Do the features have duration?
Chris@449 541
Chris@726 542 We can run through the results and check off whether
Chris@726 543 we find values and duration for each of the
Chris@726 544 source+type keys, and then run through the
Chris@726 545 source+type keys pushing each of the results into a
Chris@726 546 suitable model.
Chris@439 547
Chris@726 548 Unfortunately, at this point we do not yet have any
Chris@726 549 actual timing data (time/duration) -- just the time
Chris@726 550 URI.
Chris@449 551
Chris@726 552 What we _could_ do is to create one of each type of
Chris@726 553 model at the start, for each of the source+type
Chris@726 554 keys, and then push each feature into the relevant
Chris@726 555 model depending on what we find out about it. Then
Chris@726 556 return only non-empty models.
Chris@726 557 */
Chris@439 558
Chris@726 559 QString label = "";
Chris@726 560 bool text = (type.contains("Text") || type.contains("text")); // Ha, ha
Chris@726 561 bool note = (type.contains("Note") || type.contains("note")); // Guffaw
Chris@449 562
Chris@726 563 if (text) {
Chris@730 564 label = m_store->complete(Triple(thing, expand("af:text"), Node())).value;
Chris@726 565 }
Chris@726 566
Chris@726 567 if (label == "") {
Chris@730 568 label = m_store->complete(Triple(thing, expand("rdfs:label"), Node())).value;
Chris@726 569 }
Chris@449 570
Chris@726 571 RealTime time;
Chris@726 572 RealTime duration;
Chris@726 573
Chris@930 574 // bool haveTime = false;
Chris@726 575 bool haveDuration = false;
Chris@726 576
Chris@730 577 Node at = m_store->complete(Triple(tn, expand("tl:at"), Node()));
Chris@726 578
Chris@726 579 if (at != Node()) {
Chris@726 580 time = RealTime::fromXsdDuration(at.value.toStdString());
Chris@930 581 // haveTime = true;
Chris@726 582 } else {
Chris@726 583 //!!! NB we're using rather old terminology for these things, apparently:
Chris@726 584 // beginsAt -> start
Chris@726 585 // onTimeLine -> timeline
Chris@726 586
Chris@730 587 Node start = m_store->complete(Triple(tn, expand("tl:beginsAt"), Node()));
Chris@730 588 Node dur = m_store->complete(Triple(tn, expand("tl:duration"), Node()));
Chris@726 589 if (start != Node() && dur != Node()) {
Chris@726 590 time = RealTime::fromXsdDuration
Chris@726 591 (start.value.toStdString());
Chris@726 592 duration = RealTime::fromXsdDuration
Chris@726 593 (dur.value.toStdString());
Chris@930 594 // haveTime = haveDuration = true;
Chris@726 595 }
Chris@726 596 }
Chris@726 597
Chris@726 598 QString valuestring = valu.value;
Chris@726 599 std::vector<float> values;
Chris@726 600
Chris@726 601 if (valuestring != "") {
Chris@726 602 QStringList vsl = valuestring.split(" ", QString::SkipEmptyParts);
Chris@726 603 for (int j = 0; j < vsl.size(); ++j) {
Chris@726 604 bool success = false;
Chris@726 605 float v = vsl[j].toFloat(&success);
Chris@726 606 if (success) values.push_back(v);
Chris@726 607 }
Chris@726 608 }
Chris@726 609
Chris@726 610 int dimensions = 1;
Chris@726 611 if (values.size() == 1) dimensions = 2;
Chris@726 612 else if (values.size() > 1) dimensions = 3;
Chris@726 613
Chris@1752 614 ModelId modelId;
Chris@726 615
Chris@726 616 if (modelMap[timeline][type][dimensions].find(haveDuration) ==
Chris@726 617 modelMap[timeline][type][dimensions].end()) {
Chris@449 618
Chris@449 619 /*
Chris@690 620 SVDEBUG << "Creating new model: source = " << source << ", type = " << type << ", dimensions = "
Chris@449 621 << dimensions << ", haveDuration = " << haveDuration
Chris@449 622 << ", time = " << time << ", duration = " << duration
Chris@687 623 << endl;
Chris@449 624 */
Chris@1752 625
Chris@1752 626 Model *model = nullptr;
Chris@1752 627
Chris@726 628 if (!haveDuration) {
Chris@449 629
Chris@726 630 if (dimensions == 1) {
Chris@726 631 if (text) {
Chris@726 632 model = new TextModel(m_sampleRate, 1, false);
Chris@726 633 } else {
Chris@726 634 model = new SparseOneDimensionalModel(m_sampleRate, 1, false);
Chris@726 635 }
Chris@726 636 } else if (dimensions == 2) {
Chris@726 637 if (text) {
Chris@726 638 model = new TextModel(m_sampleRate, 1, false);
Chris@726 639 } else {
Chris@726 640 model = new SparseTimeValueModel(m_sampleRate, 1, false);
Chris@726 641 }
Chris@726 642 } else {
Chris@726 643 // We don't have a three-dimensional sparse model,
Chris@726 644 // so use a note model. We do have some logic (in
Chris@726 645 // extractStructure below) for guessing whether
Chris@726 646 // this should after all have been a dense model,
Chris@726 647 // but it's hard to apply it because we don't have
Chris@726 648 // all the necessary timing data yet... hmm
Chris@726 649 model = new NoteModel(m_sampleRate, 1, false);
Chris@726 650 }
Chris@449 651
Chris@726 652 } else { // haveDuration
Chris@510 653
Chris@726 654 if (note || (dimensions > 2)) {
Chris@726 655 model = new NoteModel(m_sampleRate, 1, false);
Chris@726 656 } else {
Chris@726 657 // If our units are frequency or midi pitch, we
Chris@726 658 // should be using a note model... hm
Chris@726 659 model = new RegionModel(m_sampleRate, 1, false);
Chris@726 660 }
Chris@510 661 }
Chris@449 662
Chris@726 663 model->setRDFTypeURI(type);
Chris@449 664
Chris@726 665 if (m_audioModelMap.find(source) != m_audioModelMap.end()) {
Chris@843 666 cerr << "source model for " << model << " is " << m_audioModelMap[source] << endl;
Chris@1752 667 model->setSourceModel(m_audioModelMap[source]);
Chris@510 668 }
Chris@449 669
Chris@730 670 QString title = m_store->complete
Chris@730 671 (Triple(typ, expand("dc:title"), Node())).value;
Chris@726 672 if (title == "") {
Chris@726 673 // take it from the end of the event type
Chris@726 674 title = type;
Chris@726 675 title.replace(QRegExp("^.*[/#]"), "");
Chris@726 676 }
Chris@726 677 model->setObjectName(title);
Chris@449 678
Chris@1752 679 modelId = ModelById::add(std::shared_ptr<Model>(model));
Chris@1752 680 modelMap[timeline][type][dimensions][haveDuration] = modelId;
Chris@1752 681 models.push_back(modelId);
Chris@449 682 }
Chris@449 683
Chris@1752 684 modelId = modelMap[timeline][type][dimensions][haveDuration];
Chris@449 685
Chris@1752 686 if (!modelId.isNone()) {
Chris@1752 687 sv_frame_t ftime =
Chris@1752 688 RealTime::realTime2Frame(time, m_sampleRate);
Chris@1752 689 sv_frame_t fduration =
Chris@1752 690 RealTime::realTime2Frame(duration, m_sampleRate);
Chris@1752 691 fillModel(modelId, ftime, fduration,
Chris@1752 692 haveDuration, values, label);
Chris@449 693 }
Chris@449 694 }
Chris@439 695 }
Chris@439 696 }
Chris@439 697 }
Chris@439 698
Chris@439 699 void
Chris@1752 700 RDFImporterImpl::fillModel(ModelId modelId,
Chris@1039 701 sv_frame_t ftime,
Chris@1039 702 sv_frame_t fduration,
Chris@449 703 bool haveDuration,
Chris@449 704 std::vector<float> &values,
Chris@449 705 QString label)
Chris@449 706 {
Chris@690 707 // SVDEBUG << "RDFImporterImpl::fillModel: adding point at frame " << ftime << endl;
Chris@492 708
Chris@1752 709 if (auto sodm = ModelById::getAs<SparseOneDimensionalModel>(modelId)) {
Chris@1658 710 Event point(ftime, label);
Chris@1658 711 sodm->add(point);
Chris@449 712 return;
Chris@449 713 }
Chris@449 714
Chris@1752 715 if (auto tm = ModelById::getAs<TextModel>(modelId)) {
Chris@1661 716 Event e
Chris@510 717 (ftime,
Chris@510 718 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 719 label);
Chris@1661 720 tm->add(e);
Chris@510 721 return;
Chris@510 722 }
Chris@510 723
Chris@1752 724 if (auto stvm = ModelById::getAs<SparseTimeValueModel>(modelId)) {
Chris@1651 725 Event e(ftime, values.empty() ? 0.f : values[0], label);
Chris@1651 726 stvm->add(e);
Chris@449 727 return;
Chris@449 728 }
Chris@449 729
Chris@1752 730 if (auto nm = ModelById::getAs<NoteModel>(modelId)) {
Chris@449 731 if (haveDuration) {
Chris@449 732 float value = 0.f, level = 1.f;
Chris@449 733 if (!values.empty()) {
Chris@449 734 value = values[0];
Chris@449 735 if (values.size() > 1) {
Chris@449 736 level = values[1];
Chris@449 737 }
Chris@449 738 }
Chris@1644 739 Event e(ftime, value, fduration, level, label);
Chris@1644 740 nm->add(e);
Chris@449 741 } else {
Chris@449 742 float value = 0.f, duration = 1.f, level = 1.f;
Chris@449 743 if (!values.empty()) {
Chris@449 744 value = values[0];
Chris@449 745 if (values.size() > 1) {
Chris@449 746 duration = values[1];
Chris@449 747 if (values.size() > 2) {
Chris@449 748 level = values[2];
Chris@449 749 }
Chris@449 750 }
Chris@449 751 }
Chris@1644 752 Event e(ftime, value, sv_frame_t(lrintf(duration)),
Chris@1643 753 level, label);
Chris@1644 754 nm->add(e);
Chris@449 755 }
Chris@449 756 return;
Chris@449 757 }
Chris@449 758
Chris@1752 759 if (auto rm = ModelById::getAs<RegionModel>(modelId)) {
Chris@617 760 float value = 0.f;
Chris@617 761 if (values.empty()) {
Chris@617 762 // no values? map each unique label to a distinct value
Chris@1752 763 if (m_labelValueMap[modelId].find(label) == m_labelValueMap[modelId].end()) {
Chris@1752 764 m_labelValueMap[modelId][label] = rm->getValueMaximum() + 1.f;
Chris@617 765 }
Chris@1752 766 value = m_labelValueMap[modelId][label];
Chris@617 767 } else {
Chris@617 768 value = values[0];
Chris@617 769 }
Chris@449 770 if (haveDuration) {
Chris@1649 771 Event e(ftime, value, fduration, label);
Chris@1649 772 rm->add(e);
Chris@449 773 } else {
Chris@449 774 // This won't actually happen -- we only create region models
Chris@449 775 // if we do have duration -- but just for completeness
Chris@617 776 float duration = 1.f;
Chris@449 777 if (!values.empty()) {
Chris@449 778 value = values[0];
Chris@449 779 if (values.size() > 1) {
Chris@449 780 duration = values[1];
Chris@449 781 }
Chris@449 782 }
Chris@1649 783 Event e(ftime, value, sv_frame_t(lrintf(duration)), label);
Chris@1649 784 rm->add(e);
Chris@449 785 }
Chris@449 786 return;
Chris@449 787 }
Chris@449 788
Chris@843 789 cerr << "WARNING: RDFImporterImpl::fillModel: Unknown or unexpected model type" << endl;
Chris@449 790 return;
Chris@449 791 }
Chris@449 792
Chris@490 793 RDFImporter::RDFDocumentType
Chris@490 794 RDFImporter::identifyDocumentType(QString url)
Chris@490 795 {
Chris@490 796 bool haveAudio = false;
Chris@490 797 bool haveAnnotations = false;
Chris@726 798 bool haveRDF = false;
Chris@449 799
Chris@1582 800 BasicStore *store = nullptr;
Chris@726 801
Chris@726 802 // This is not expected to return anything useful, but if it does
Chris@726 803 // anything at all then we know we have RDF
Chris@726 804 try {
Chris@738 805 //!!! non-local document?
Chris@726 806 store = BasicStore::load(QUrl(url));
Chris@730 807 Triple t = store->matchOnce(Triple());
Chris@726 808 if (t != Triple()) haveRDF = true;
Chris@1471 809 } catch (std::exception &) {
Chris@738 810 // nothing; haveRDF will be false so the next bit catches it
Chris@726 811 }
Chris@726 812
Chris@726 813 if (!haveRDF) {
Chris@726 814 delete store;
Chris@499 815 return NotRDF;
Chris@499 816 }
Chris@499 817
Chris@726 818 store->addPrefix("mo", Uri("http://purl.org/ontology/mo/"));
Chris@726 819 store->addPrefix("event", Uri("http://purl.org/NET/c4dm/event.owl#"));
Chris@726 820 store->addPrefix("af", Uri("http://purl.org/ontology/af/"));
Chris@726 821
Chris@588 822 // "MO-conformant" structure for audio files
Chris@588 823
Chris@730 824 Node n = store->complete(Triple(Node(), Uri("a"), store->expand("mo:AudioFile")));
Chris@726 825 if (n != Node() && n.type == Node::URI) {
Chris@588 826
Chris@490 827 haveAudio = true;
Chris@588 828
Chris@588 829 } else {
Chris@588 830
Chris@588 831 // Sonic Annotator v0.2 and below used to write this structure
Chris@588 832 // (which is not properly in conformance with the Music
Chris@588 833 // Ontology)
Chris@588 834
Chris@730 835 Nodes sigs = store->match(Triple(Node(), Uri("a"), store->expand("mo:Signal"))).subjects();
Chris@726 836 foreach (Node sig, sigs) {
Chris@730 837 Node aa = store->complete(Triple(sig, store->expand("mo:available_as"), Node()));
Chris@726 838 if (aa != Node()) {
Chris@726 839 haveAudio = true;
Chris@726 840 break;
Chris@726 841 }
Chris@588 842 }
Chris@490 843 }
Chris@490 844
Chris@690 845 SVDEBUG << "NOTE: RDFImporter::identifyDocumentType: haveAudio = "
Chris@687 846 << haveAudio << endl;
Chris@616 847
Chris@736 848 // can't call complete() with two Nothing nodes
Chris@736 849 n = store->matchOnce(Triple(Node(), store->expand("event:time"), Node())).c;
Chris@726 850 if (n != Node()) {
Chris@490 851 haveAnnotations = true;
Chris@490 852 }
Chris@490 853
Chris@490 854 if (!haveAnnotations) {
Chris@736 855 // can't call complete() with two Nothing nodes
Chris@736 856 n = store->matchOnce(Triple(Node(), store->expand("af:signal_feature"), Node())).c;
Chris@726 857 if (n != Node()) {
Chris@490 858 haveAnnotations = true;
Chris@490 859 }
Chris@490 860 }
Chris@490 861
Chris@690 862 SVDEBUG << "NOTE: RDFImporter::identifyDocumentType: haveAnnotations = "
Chris@687 863 << haveAnnotations << endl;
Chris@616 864
Chris@726 865 delete store;
Chris@542 866
Chris@490 867 if (haveAudio) {
Chris@490 868 if (haveAnnotations) {
Chris@490 869 return AudioRefAndAnnotations;
Chris@490 870 } else {
Chris@490 871 return AudioRef;
Chris@490 872 }
Chris@490 873 } else {
Chris@490 874 if (haveAnnotations) {
Chris@490 875 return Annotations;
Chris@490 876 } else {
Chris@499 877 return OtherRDFDocument;
Chris@490 878 }
Chris@490 879 }
Chris@492 880
Chris@542 881 return OtherRDFDocument;
Chris@490 882 }
Chris@490 883