changeset 1649:1cc9a0d4b1b6 single-point

Update RegionModel following NoteModel, er, model. They have quite a bit in common that we should now pull out some of
author Chris Cannam
date Fri, 15 Mar 2019 14:23:50 +0000
parents 86bbccb79c9b
children bbfb5a1e4b84
files data/fileio/CSVFileReader.cpp data/model/NoteModel.h data/model/RegionModel.h rdf/RDFExporter.cpp rdf/RDFImporter.cpp transform/FeatureExtractionModelTransformer.cpp
diffstat 6 files changed, 317 insertions(+), 168 deletions(-) [+]
line wrap: on
line diff
--- a/data/fileio/CSVFileReader.cpp	Fri Mar 15 10:57:35 2019 +0000
+++ b/data/fileio/CSVFileReader.cpp	Fri Mar 15 14:23:50 2019 +0000
@@ -447,8 +447,8 @@
 
             } else if (modelType == CSVFormat::TwoDimensionalModelWithDuration) {
 
-                RegionModel::Point point(frameNo, value, duration, label);
-                model2a->addPoint(point);
+                Event region(frameNo, value, duration, label);
+                model2a->add(region);
 
             } else if (modelType == CSVFormat::TwoDimensionalModelWithDurationAndPitch) {
 
@@ -581,31 +581,30 @@
                 }
             }
 
-            map<RegionModel::Point, RegionModel::Point,
-                RegionModel::Point::Comparator> pointMap;
-            for (RegionModel::PointList::const_iterator i =
-                     model2a->getPoints().begin();
-                 i != model2a->getPoints().end(); ++i) {
-                RegionModel::Point p(*i);
-                int count = labelCountMap[p.label];
-                v = countLabelValueMap[count][p.label];
-              //  SVCERR << "mapping from label \"" << p.label << "\" (count " << count << ") to value " << v << endl;
-                RegionModel::Point pp(p.frame, v, p.duration, p.label);
-                pointMap[p] = pp;
+            map<Event, Event> eventMap;
+
+            EventVector allEvents = model2a->getAllEvents();
+            for (const Event &e: allEvents) {
+                int count = labelCountMap[e.getLabel()];
+                v = countLabelValueMap[count][e.getLabel()];
+                // SVCERR << "mapping from label \"" << p.label
+                //       << "\" (count " << count
+                //       << ") to value " << v << endl;
+                eventMap[e] = Event(e.getFrame(), v,
+                                    e.getDuration(), e.getLabel());
             }
 
-            for (map<RegionModel::Point, RegionModel::Point>::iterator i = 
-                     pointMap.begin(); i != pointMap.end(); ++i) {
+            for (const auto &i: eventMap) {
                 // There could be duplicate regions; if so replace
                 // them all -- but we need to check we're not
                 // replacing a region by itself (or else this will
                 // never terminate)
-                if (i->first.value == i->second.value) {
+                if (i.first.getValue() == i.second.getValue()) {
                     continue;
                 }
-                while (model2a->containsPoint(i->first)) {
-                    model2a->deletePoint(i->first);
-                    model2a->addPoint(i->second);
+                while (model2a->containsEvent(i.first)) {
+                    model2a->remove(i.first);
+                    model2a->add(i.second);
                 }
             }
         }
--- a/data/model/NoteModel.h	Fri Mar 15 10:57:35 2019 +0000
+++ b/data/model/NoteModel.h	Fri Mar 15 14:23:50 2019 +0000
@@ -307,8 +307,8 @@
         }
     }
 
-    Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override
-    {
+    Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override {
+        
         if (row < 0 || row >= m_events.count()) return nullptr;
         if (role != Qt::EditRole) return nullptr;
 
--- a/data/model/RegionModel.h	Fri Mar 15 10:57:35 2019 +0000
+++ b/data/model/RegionModel.h	Fri Mar 15 14:23:50 2019 +0000
@@ -16,105 +16,76 @@
 #ifndef SV_REGION_MODEL_H
 #define SV_REGION_MODEL_H
 
-#include "IntervalModel.h"
+#include "EventCommands.h"
+#include "TabularModel.h"
+#include "Model.h"
+
 #include "base/RealTime.h"
+#include "base/EventSeries.h"
+#include "base/UnitDatabase.h"
+
+#include "system/System.h"
+
+#include <QMutex>
 
 /**
- * RegionModel -- a concrete IntervalModel for intervals associated
- * with a value, which we call regions for no very compelling reason.
+ * RegionModel -- a model for intervals associated with a value, which
+ * we call regions for no very compelling reason.
  */
-
-/**
- * Region "point" type.  A region is something that has an onset time,
- * a single value, and a duration.  Like other points, it can also
- * have a label.
- *
- * This is called RegionRec instead of Region to avoid name collisions
- * with the X11 Region struct.  Bah.
- */
-
-struct RegionRec
-{
-public:
-    RegionRec() : frame(0), value(0.f), duration(0) { }
-    RegionRec(sv_frame_t _frame) : frame(_frame), value(0.0f), duration(0) { }
-    RegionRec(sv_frame_t _frame, float _value, sv_frame_t _duration, QString _label) :
-        frame(_frame), value(_value), duration(_duration), label(_label) { }
-
-    int getDimensions() const { return 3; }
-
-    sv_frame_t frame;
-    float value;
-    sv_frame_t duration;
-    QString label;
-
-    QString getLabel() const { return label; }
-    
-    void toXml(QTextStream &stream,
-               QString indent = "",
-               QString extraAttributes = "") const
-    {
-        stream <<
-            QString("%1<point frame=\"%2\" value=\"%3\" duration=\"%4\" label=\"%5\" %6/>\n")
-            .arg(indent).arg(frame).arg(value).arg(duration)
-            .arg(XmlExportable::encodeEntities(label)).arg(extraAttributes);
-    }
-
-    QString toDelimitedDataString(QString delimiter, DataExportOptions, sv_samplerate_t sampleRate) const
-    {
-        QStringList list;
-        list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str();
-        list << QString("%1").arg(value);
-        list << RealTime::frame2RealTime(duration, sampleRate).toString().c_str();
-        if (label != "") list << label;
-        return list.join(delimiter);
-    }
-
-    struct Comparator {
-        bool operator()(const RegionRec &p1,
-                        const RegionRec &p2) const {
-            if (p1.frame != p2.frame) return p1.frame < p2.frame;
-            if (p1.value != p2.value) return p1.value < p2.value;
-            if (p1.duration != p2.duration) return p1.duration < p2.duration;
-            return p1.label < p2.label;
-        }
-    };
-    
-    struct OrderComparator {
-        bool operator()(const RegionRec &p1,
-                        const RegionRec &p2) const {
-            return p1.frame < p2.frame;
-        }
-    };
-};
-
-
-class RegionModel : public IntervalModel<RegionRec>
+class RegionModel : public Model,
+                    public TabularModel,
+                    public EventEditable
 {
     Q_OBJECT
     
 public:
-    RegionModel(sv_samplerate_t sampleRate, int resolution,
+    RegionModel(sv_samplerate_t sampleRate,
+                int resolution,
                 bool notifyOnAdd = true) :
-        IntervalModel<RegionRec>(sampleRate, resolution, notifyOnAdd),
+        m_sampleRate(sampleRate),
+        m_resolution(resolution),
+        m_valueMinimum(0.f),
+        m_valueMaximum(0.f),
+        m_haveExtents(false),
         m_valueQuantization(0),
-        m_haveDistinctValues(false)
-    {
+        m_haveDistinctValues(false),
+        m_notifyOnAdd(notifyOnAdd),
+        m_sinceLastNotifyMin(-1),
+        m_sinceLastNotifyMax(-1),
+        m_completion(0) {
     }
 
     RegionModel(sv_samplerate_t sampleRate, int resolution,
                 float valueMinimum, float valueMaximum,
                 bool notifyOnAdd = true) :
-        IntervalModel<RegionRec>(sampleRate, resolution,
-                            valueMinimum, valueMaximum,
-                            notifyOnAdd),
+        m_sampleRate(sampleRate),
+        m_resolution(resolution),
+        m_valueMinimum(valueMinimum),
+        m_valueMaximum(valueMaximum),
+        m_haveExtents(false),
         m_valueQuantization(0),
-        m_haveDistinctValues(false)
-    {
+        m_haveDistinctValues(false),
+        m_notifyOnAdd(notifyOnAdd),
+        m_sinceLastNotifyMin(-1),
+        m_sinceLastNotifyMax(-1),
+        m_completion(0) {
     }
 
-    virtual ~RegionModel()
-    {
+    virtual ~RegionModel() {
+    }
+
+    QString getTypeName() const override { return tr("Region"); }
+
+    bool isOK() const override { return true; }
+    sv_frame_t getStartFrame() const override { return m_events.getStartFrame(); }
+    sv_frame_t getEndFrame() const override { return m_events.getEndFrame(); }
+    sv_samplerate_t getSampleRate() const override { return m_sampleRate; }
+    int getResolution() const { return m_resolution; }
+
+    QString getScaleUnits() const { return m_units; }
+    void setScaleUnits(QString units) {
+        m_units = units;
+        UnitDatabase::getInstance()->registerUnit(units);
     }
 
     float getValueQuantization() const { return m_valueQuantization; }
@@ -122,33 +93,172 @@
 
     bool haveDistinctValues() const { return m_haveDistinctValues; }
 
-    QString getTypeName() const override { return tr("Region"); }
+    float getValueMinimum() const { return m_valueMinimum; }
+    float getValueMaximum() const { return m_valueMaximum; }
+    
+    int getCompletion() const { return m_completion; }
 
-    void toXml(QTextStream &out,
-                       QString indent = "",
-                       QString extraAttributes = "") const override
-    {
-        std::cerr << "RegionModel::toXml: extraAttributes = \"" 
-                  << extraAttributes.toStdString() << std::endl;
+    void setCompletion(int completion, bool update = true) {
 
-        IntervalModel<RegionRec>::toXml
-            (out,
-             indent,
-             QString("%1 subtype=\"region\" valueQuantization=\"%2\"")
-             .arg(extraAttributes).arg(m_valueQuantization));
+        bool emitCompletionChanged = true;
+        bool emitGeneralModelChanged = false;
+        bool emitRegionChanged = false;
+
+        {
+            QMutexLocker locker(&m_mutex);
+
+            if (m_completion != completion) {
+                m_completion = completion;
+
+                if (completion == 100) {
+
+                    if (m_notifyOnAdd) {
+                        emitCompletionChanged = false;
+                    }
+
+                    m_notifyOnAdd = true; // henceforth
+                    emitGeneralModelChanged = true;
+
+                } else if (!m_notifyOnAdd) {
+
+                    if (update &&
+                        m_sinceLastNotifyMin >= 0 &&
+                        m_sinceLastNotifyMax >= 0) {
+                        emitRegionChanged = true;
+                    }
+                }
+            }
+        }
+
+        if (emitCompletionChanged) {
+            emit completionChanged();
+        }
+        if (emitGeneralModelChanged) {
+            emit modelChanged();
+        }
+        if (emitRegionChanged) {
+            emit modelChangedWithin(m_sinceLastNotifyMin, m_sinceLastNotifyMax);
+            m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1;
+        }        
     }
 
     /**
+     * Query methods.
+     */
+
+    int getEventCount() const {
+        return m_events.count();
+    }
+    bool isEmpty() const {
+        return m_events.isEmpty();
+    }
+    bool containsEvent(const Event &e) const {
+        return m_events.contains(e);
+    }
+    EventVector getAllEvents() const {
+        return m_events.getAllEvents();
+    }
+    EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const {
+        return m_events.getEventsSpanning(f, duration);
+    }
+    EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration) const {
+        return m_events.getEventsWithin(f, duration);
+    }
+    EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const {
+        return m_events.getEventsStartingWithin(f, duration);
+    }
+    EventVector getEventsCovering(sv_frame_t f) const {
+        return m_events.getEventsCovering(f);
+    }
+
+    /**
+     * Editing methods.
+     */
+    void add(Event e) override {
+
+        bool allChange = false;
+           
+        {
+            QMutexLocker locker(&m_mutex);
+            m_events.add(e);
+//!!!???        if (point.getLabel() != "") m_hasTextLabels = true;
+
+            float v = e.getValue();
+            if (!ISNAN(v) && !ISINF(v)) {
+                if (!m_haveExtents || v < m_valueMinimum) {
+                    m_valueMinimum = v; allChange = true;
+                }
+                if (!m_haveExtents || v > m_valueMaximum) {
+                    m_valueMaximum = v; allChange = true;
+                }
+                m_haveExtents = true;
+            }
+
+            if (e.hasValue() && e.getValue() != 0.f) {
+                m_haveDistinctValues = true;
+            }
+            
+            sv_frame_t f = e.getFrame();
+
+            if (!m_notifyOnAdd) {
+                if (m_sinceLastNotifyMin == -1 || f < m_sinceLastNotifyMin) {
+                    m_sinceLastNotifyMin = f;
+                }
+                if (m_sinceLastNotifyMax == -1 || f > m_sinceLastNotifyMax) {
+                    m_sinceLastNotifyMax = f;
+                }
+            }
+        }
+        
+        if (m_notifyOnAdd) {
+            emit modelChangedWithin(e.getFrame(),
+                                    e.getFrame() + e.getDuration() + m_resolution);
+        }
+        if (allChange) {
+            emit modelChanged();
+        }
+    }
+    
+    void remove(Event e) override {
+        {
+            QMutexLocker locker(&m_mutex);
+            m_events.remove(e);
+        }
+        emit modelChangedWithin(e.getFrame(),
+                                e.getFrame() + e.getDuration() + m_resolution);
+    }
+    
+    /**
      * TabularModel methods.  
      */
-    
-    int getColumnCount() const override
-    {
+
+    int getRowCount() const override {
+        return m_events.count();
+    }
+
+    int getColumnCount() const override {
         return 5;
     }
 
-    QString getHeading(int column) const override
-    {
+    bool isColumnTimeValue(int column) const override {
+        // NB duration is not a "time value" -- that's for columns
+        // whose sort ordering is exactly that of the frame time
+        return (column < 2);
+    }
+
+    sv_frame_t getFrameForRow(int row) const override {
+        if (row < 0 || row >= m_events.count()) {
+            return 0;
+        }
+        Event e = m_events.getEventByIndex(row);
+        return e.getFrame();
+    }
+
+    int getRowForFrame(sv_frame_t frame) const override {
+        return m_events.getIndexForEvent(Event(frame));
+    }
+    
+    QString getHeading(int column) const override {
         switch (column) {
         case 0: return tr("Time");
         case 1: return tr("Frame");
@@ -159,41 +269,45 @@
         }
     }
 
-    QVariant getData(int row, int column, int role) const override
-    {
-        if (column < 4) {
-            return IntervalModel<RegionRec>::getData(row, column, role);
+    QVariant getData(int row, int column, int role) const override {
+
+        if (row < 0 || row >= m_events.count()) {
+            return QVariant();
         }
 
-        PointListConstIterator i = getPointListIteratorForRow(row);
-        if (i == m_points.end()) return QVariant();
+        Event e = m_events.getEventByIndex(row);
 
         switch (column) {
-        case 4: return i->label;
+        case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role);
+        case 1: return int(e.getFrame());
+        case 2: return adaptValueForRole(e.getValue(), getScaleUnits(), role);
+        case 3: return int(e.getDuration());
+        case 4: return e.getLabel();
         default: return QVariant();
         }
     }
 
-    Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override
-    {
-        if (column < 4) {
-            return IntervalModel<RegionRec>::getSetDataCommand
-                (row, column, value, role);
+    Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override {
+        
+        if (row < 0 || row >= m_events.count()) return nullptr;
+        if (role != Qt::EditRole) return nullptr;
+
+        Event e0 = m_events.getEventByIndex(row);
+        Event e1;
+
+        switch (column) {
+        case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() *
+                                                   getSampleRate()))); break;
+        case 1: e1 = e0.withFrame(value.toInt()); break;
+        case 2: e1 = e0.withValue(float(value.toDouble())); break;
+        case 3: e1 = e0.withDuration(value.toInt()); break;
+        case 4: e1 = e0.withLabel(value.toString()); break;
         }
 
-        if (role != Qt::EditRole) return 0;
-        PointListIterator i = getPointListIteratorForRow(row);
-        if (i == m_points.end()) return 0;
-        EditCommand *command = new EditCommand(this, tr("Edit Data"));
-
-        Point point(*i);
-        command->deletePoint(point);
-
-        switch (column) {
-        case 4: point.label = value.toString(); break;
-        }
-
-        command->addPoint(point);
+        ChangeEventsCommand *command =
+            new ChangeEventsCommand(this, tr("Edit Data"));
+        command->remove(e0);
+        command->add(e1);
         return command->finish();
     }
 
@@ -203,15 +317,53 @@
         return SortNumeric;
     }
 
-    void addPoint(const Point &point) override
-    {
-        if (point.value != 0.f) m_haveDistinctValues = true;
-        IntervalModel<RegionRec>::addPoint(point);
+    
+    /**
+     * XmlExportable methods.
+     */
+    void toXml(QTextStream &out,
+               QString indent = "",
+               QString extraAttributes = "") const override {
+
+        Model::toXml
+            (out,
+             indent,
+             QString("type=\"sparse\" dimensions=\"3\" resolution=\"%1\" "
+                     "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"%4\" "
+                     "valueQuantization=\"%5\" minimum=\"%6\" maximum=\"%7\" "
+                     "%8")
+             .arg(m_resolution)
+             .arg(m_notifyOnAdd ? "true" : "false")
+             .arg(getObjectExportId(&m_events))
+             .arg("region")
+             .arg(m_valueQuantization)
+             .arg(m_valueMinimum)
+             .arg(m_valueMaximum)
+             .arg(extraAttributes));
+        
+        m_events.toXml(out, indent, QString("dimensions=\"3\""));
     }
     
 protected:
+    sv_samplerate_t m_sampleRate;
+    int m_resolution;
+
+    float m_valueMinimum;
+    float m_valueMaximum;
+    bool m_haveExtents;
     float m_valueQuantization;
     bool m_haveDistinctValues;
+    QString m_units;
+    
+    bool m_notifyOnAdd;
+    sv_frame_t m_sinceLastNotifyMin;
+    sv_frame_t m_sinceLastNotifyMax;
+
+    EventSeries m_events;
+
+    int m_completion;
+
+    mutable QMutex m_mutex;
 };
 
 #endif
--- a/rdf/RDFExporter.cpp	Fri Mar 15 10:57:35 2019 +0000
+++ b/rdf/RDFExporter.cpp	Fri Mar 15 14:23:50 2019 +0000
@@ -85,14 +85,13 @@
         if (m) {
             f.hasTimestamp = true;
             f.hasDuration = true;
-            const RegionModel::PointList &pl(m->getPoints());
-            for (RegionModel::PointList::const_iterator i = pl.begin(); 
-                 i != pl.end(); ++i) {
-                f.timestamp = RealTime::frame2RealTime(i->frame, sr).toVampRealTime();
-                f.duration = RealTime::frame2RealTime(i->duration, sr).toVampRealTime();
+            EventVector ee(m->getAllEvents());
+            for (auto e: ee) {
+                f.timestamp = RealTime::frame2RealTime(e.getFrame(), sr).toVampRealTime();
+                f.duration = RealTime::frame2RealTime(e.getDuration(), sr).toVampRealTime();
                 f.values.clear();
-                f.values.push_back(i->value);
-                f.label = i->label.toStdString();
+                f.values.push_back(e.getValue());
+                f.label = e.getLabel().toStdString();
                 m_fw->write(trackId, transform, output, features, summaryType);
             }
             return;
--- a/rdf/RDFImporter.cpp	Fri Mar 15 10:57:35 2019 +0000
+++ b/rdf/RDFImporter.cpp	Fri Mar 15 14:23:50 2019 +0000
@@ -779,8 +779,8 @@
             value = values[0];
         }
         if (haveDuration) {
-            RegionModel::Point point(ftime, value, fduration, label);
-            rm->addPoint(point);
+            Event e(ftime, value, fduration, label);
+            rm->add(e);
         } else {
             // This won't actually happen -- we only create region models
             // if we do have duration -- but just for completeness
@@ -791,9 +791,8 @@
                     duration = values[1];
                 }
             }
-            RegionModel::Point point(ftime, value,
-                                     sv_frame_t(lrintf(duration)), label);
-            rm->addPoint(point);
+            Event e(ftime, value, sv_frame_t(lrintf(duration)), label);
+            rm->add(e);
         }
         return;
     }
--- a/transform/FeatureExtractionModelTransformer.cpp	Fri Mar 15 10:57:35 2019 +0000
+++ b/transform/FeatureExtractionModelTransformer.cpp	Fri Mar 15 14:23:50 2019 +0000
@@ -1057,17 +1057,17 @@
                         label = QString("[%1] %2").arg(i+1).arg(label);
                     }
 
-                    model->addPoint(RegionModel::Point(frame,
-                                                       value,
-                                                       duration,
-                                                       label));
+                    model->add(Event(frame,
+                                     value,
+                                     duration,
+                                     label));
                 }
             } else {
             
-                model->addPoint(RegionModel::Point(frame,
-                                                   value,
-                                                   duration,
-                                                   feature.label.c_str()));
+                model->add(Event(frame,
+                                 value,
+                                 duration,
+                                 feature.label.c_str()));
             }
         }