view rdf/RDFImporter.cpp @ 439:beb2948baa77

* Merge revisions 1041 to 1130 from sv-rdf-import branch
author Chris Cannam
date Thu, 18 Sep 2008 12:09:32 +0000
parents
children 5746c559af15
line wrap: on
line source
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */

/*
    Sonic Visualiser
    An audio file viewer and annotation editor.
    Centre for Digital Music, Queen Mary, University of London.
    This file copyright 2008 QMUL.
   
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation; either version 2 of the
    License, or (at your option) any later version.  See the file
    COPYING included with this distribution for more information.
*/

#include "RDFImporter.h"

#include <map>
#include <vector>

#include <iostream>
#include <cmath>

#include "SimpleSPARQLQuery.h"

#include "base/ProgressReporter.h"
#include "base/RealTime.h"

#include "data/model/SparseOneDimensionalModel.h"
#include "data/model/SparseTimeValueModel.h"
#include "data/model/EditableDenseThreeDimensionalModel.h"

using std::cerr;
using std::endl;

class RDFImporterImpl
{
public:
    RDFImporterImpl(QString url, int sampleRate);
    virtual ~RDFImporterImpl();
    
    bool isOK();
    QString getErrorString() const;

    std::vector<Model *> getDataModels(ProgressReporter *);

protected:
    QString m_uristring;
    QString m_errorString;
    int m_sampleRate;

    typedef std::vector<float> ValueList;
    typedef std::map<RealTime, ValueList> TimeValueMap;
    typedef std::map<QString, TimeValueMap> TypeTimeValueMap;
    typedef std::map<QString, TypeTimeValueMap> SourceTypeTimeValueMap;

    void extractStructure(const TimeValueMap &map, bool &sparse,
                          int &minValueCount, int &maxValueCount);

    void fillModel(SparseOneDimensionalModel *, const TimeValueMap &);
    void fillModel(SparseTimeValueModel *, const TimeValueMap &);
    void fillModel(EditableDenseThreeDimensionalModel *, const TimeValueMap &);
};


QString
RDFImporter::getKnownExtensions()
{
    return "*.rdf *.n3 *.ttl";
}

RDFImporter::RDFImporter(QString url, int sampleRate) :
    m_d(new RDFImporterImpl(url, sampleRate)) 
{
}

RDFImporter::~RDFImporter()
{
    delete m_d;
}

bool
RDFImporter::isOK()
{
    return m_d->isOK();
}

QString
RDFImporter::getErrorString() const
{
    return m_d->getErrorString();
}

std::vector<Model *>
RDFImporter::getDataModels(ProgressReporter *r)
{
    return m_d->getDataModels(r);
}

RDFImporterImpl::RDFImporterImpl(QString uri, int sampleRate) :
    m_uristring(uri),
    m_sampleRate(sampleRate)
{
}

RDFImporterImpl::~RDFImporterImpl()
{
}

bool
RDFImporterImpl::isOK()
{
    return (m_errorString == "");
}

QString
RDFImporterImpl::getErrorString() const
{
    return m_errorString;
}

std::vector<Model *>
RDFImporterImpl::getDataModels(ProgressReporter *reporter)
{
    std::vector<Model *> models;

    // Our query is intended to retrieve every thing that has a time,
    // and every feature type and value associated with a thing that
    // has a time.

    // We will then need to refine this big bag of results into a set
    // of data models.

    // Results that have different source signals should go into
    // different models.

    // Results that have different feature types should go into
    // different models.

    // Results that are sparse should go into different models from
    // those that are dense (we need to examine the timestamps to
    // establish this -- if the timestamps are regular, the results
    // are dense -- so we can't do it as we go along, only after
    // collecting all results).

    // Timed things that have features associated with them should not
    // appear directly in any model -- their features should appear
    // instead -- and these should be different models from those used
    // for timed things that do not have features.

    // As we load the results, we'll push them into a partially
    // structured container that maps from source signal (URI as
    // string) -> feature type (likewise) -> time -> list of values.
    // If the source signal or feature type is unavailable, the empty
    // string will do.

    SourceTypeTimeValueMap m;

    QString queryString = QString(

        " PREFIX event: <http://purl.org/NET/c4dm/event.owl#>"
        " PREFIX time: <http://purl.org/NET/c4dm/timeline.owl#>"
        " PREFIX mo: <http://purl.org/ontology/mo/>"
        " PREFIX af: <http://purl.org/ontology/af/>"

        " SELECT ?signalSource ?time ?eventType ?value"
        " FROM <%1>"

        " WHERE {"
        "   ?signal mo:available_as ?signalSource ."
        "   ?signal mo:time ?interval ."
        "   ?interval time:onTimeLine ?tl ."
        "   ?t time:onTimeLine ?tl ."
        "   ?t time:at ?time ."
        "   ?timedThing event:time ?t ."
        "   ?timedThing a ?eventType ."
        "   OPTIONAL {"
        "     ?timedThing af:hasFeature ?feature ."
        "     ?feature af:value ?value"
        "   }"
        " }"

        ).arg(m_uristring);

    SimpleSPARQLQuery query(queryString);
    query.setProgressReporter(reporter);

    cerr << "Query will be: " << queryString.toStdString() << endl;

    SimpleSPARQLQuery::ResultList results = query.execute();

    if (!query.isOK()) {
        m_errorString = query.getErrorString();
        return models;
    }

    if (query.wasCancelled()) {
        m_errorString = "Query cancelled";
        return models;
    }        

    for (int i = 0; i < results.size(); ++i) {

        QString source = results[i]["signalSource"].value;

        QString timestring = results[i]["time"].value;
        RealTime time;
        time = RealTime::fromXsdDuration(timestring.toStdString());
        cerr << "time = " << time.toString() << " (from xsd:duration \""
             << timestring.toStdString() << "\")" << endl;

        QString type = results[i]["eventType"].value;

        QString valuestring = results[i]["value"].value;
        float value = 0.f;
        bool haveValue = false;
        if (valuestring != "") {
            value = valuestring.toFloat(&haveValue);
            cerr << "value = " << value << endl;
        }

        if (haveValue) {
            m[source][type][time].push_back(value);
        } else if (m[source][type].find(time) == m[source][type].end()) {
            m[source][type][time] = ValueList();
        }
    }

    for (SourceTypeTimeValueMap::const_iterator mi = m.begin();
         mi != m.end(); ++mi) {
        
        QString source = mi->first;

        for (TypeTimeValueMap::const_iterator ttvi = mi->second.begin();
             ttvi != mi->second.end(); ++ttvi) {
            
            QString type = ttvi->first;

            // Now we need to work out what sort of model to use for
            // this source/type combination.  Ultimately we'll
            // hopefully be able to map directly from the type to the
            // model on the basis of known structures for the types,
            // but we also want to be able to handle untyped data
            // according to its apparent structure so let's do that
            // first.

            bool sparse = false;
            int minValueCount = 0, maxValueCount = 0;

            extractStructure(ttvi->second, sparse, minValueCount, maxValueCount);
    
            cerr << "For source \"" << source.toStdString() << "\", type \""
                 << type.toStdString() << "\" we have sparse = " << sparse
                 << ", min value count = " << minValueCount << ", max = "
                 << maxValueCount << endl;

            // Model allocations:
            //
            // Sparse, no values: SparseOneDimensionalModel
            //
            // Sparse, always 1 value: SparseTimeValueModel
            //
            // Sparse, > 1 value: No standard model for this.  If
            // there are always 2 values, perhaps hack it into
            // NoteModel for now?  Or always use SparseTimeValueModel
            // and discard all but the first value.
            //
            // Dense, no values: Meaningless; no suitable model
            //
            // Dense, > 0 values: EditableDenseThreeDimensionalModel
            //
            // These should just be our fallback positions; we want to
            // be reading semantic data from the RDF in order to pick
            // the right model directly

            enum { SODM, STVM, EDTDM } modelType = SODM;

            if (sparse) {
                if (maxValueCount == 0) {
                    modelType = SODM;
                } else if (minValueCount == 1 && maxValueCount == 1) {
                    modelType = STVM;
                } else {
                    cerr << "WARNING: No suitable model available for sparse data with between " << minValueCount << " and " << maxValueCount << " values" << endl;
                    modelType = STVM;
                }
            } else {
                if (maxValueCount == 0) {
                    cerr << "WARNING: Dense data set with no values is not meaningful, skipping" << endl;
                    continue;
                } else {
                    modelType = EDTDM;
                }
            }

            //!!! set model name &c

            if (modelType == SODM) {

                SparseOneDimensionalModel *model = 
                    new SparseOneDimensionalModel(m_sampleRate, 1, false);
                
                fillModel(model, ttvi->second);
                models.push_back(model);

            } else if (modelType == STVM) {

                SparseTimeValueModel *model = 
                    new SparseTimeValueModel(m_sampleRate, 1, false);
                
                fillModel(model, ttvi->second);
                models.push_back(model);

            } else {
                
                EditableDenseThreeDimensionalModel *model =
                    new EditableDenseThreeDimensionalModel(m_sampleRate, 1, 0,
                                                           false);

                fillModel(model, ttvi->second);
                models.push_back(model);
            }
        }
    }


    return models;
}

void
RDFImporterImpl::extractStructure(const TimeValueMap &tvm,
                                  bool &sparse,
                                  int &minValueCount,
                                  int &maxValueCount)
{
    // These are floats intentionally rather than RealTime --
    // see logic for handling rounding error below
    float firstTime = 0.f;
    float timeStep = 0.f;
    bool haveTimeStep = false;
    
    for (TimeValueMap::const_iterator tvi = tvm.begin(); tvi != tvm.end(); ++tvi) {
        
        RealTime time = tvi->first;
        int valueCount = tvi->second.size();
        
        if (tvi == tvm.begin()) {
            
            minValueCount = valueCount;
            maxValueCount = valueCount;
            
            firstTime = time.toDouble();
            
        } else {
            
            if (valueCount < minValueCount) minValueCount = valueCount;
            if (valueCount > maxValueCount) maxValueCount = valueCount;
            
            if (!haveTimeStep) {
                timeStep = time.toDouble() - firstTime;
                if (timeStep == 0.f) sparse = true;
                haveTimeStep = true;
            } else if (!sparse) {
                // test whether this time is within
                // rounding-error range of being an integer
                // multiple of some constant away from the
                // first time
                float timeAsFloat = time.toDouble();
                int count = int((timeAsFloat - firstTime) / timeStep + 0.5);
                float expected = firstTime + (timeStep * count);
                if (fabsf(expected - timeAsFloat) > 1e-6) {
                    cerr << "Event at " << timeAsFloat << " is not evenly spaced -- would expect it to be " << expected << " for a spacing of " << count << " * " << timeStep << endl;
                    sparse = true;
                }
            }
        }
    }
}

void
RDFImporterImpl::fillModel(SparseOneDimensionalModel *model,
                           const TimeValueMap &tvm)
{
    //!!! labels &c not yet handled

    for (TimeValueMap::const_iterator tvi = tvm.begin();
         tvi != tvm.end(); ++tvi) {
        
        RealTime time = tvi->first;
        long frame = RealTime::realTime2Frame(time, m_sampleRate);

        SparseOneDimensionalModel::Point point(frame);

        model->addPoint(point);
    }
}

void
RDFImporterImpl::fillModel(SparseTimeValueModel *model,
                           const TimeValueMap &tvm)
{
    //!!! labels &c not yet handled

    for (TimeValueMap::const_iterator tvi = tvm.begin();
         tvi != tvm.end(); ++tvi) {
        
        RealTime time = tvi->first;
        long frame = RealTime::realTime2Frame(time, m_sampleRate);

        float value = 0.f;
        if (!tvi->second.empty()) value = *tvi->second.begin();
        
        SparseTimeValueModel::Point point(frame, value, "");

        model->addPoint(point);
    }
}

void
RDFImporterImpl::fillModel(EditableDenseThreeDimensionalModel *model,
                           const TimeValueMap &tvm)
{
    //!!! labels &c not yet handled

    //!!! start time offset not yet handled

    size_t col = 0;

    for (TimeValueMap::const_iterator tvi = tvm.begin();
         tvi != tvm.end(); ++tvi) {
        
        model->setColumn(col++, tvi->second);
    }
}