annotate base/MovingMedian.h @ 1833:21c792334c2e sensible-delimited-data-strings

Rewrite all the DelimitedDataString stuff so as to return vectors of individual cell strings rather than having the classes add the delimiters themselves. Rename accordingly to names based on StringExport. Take advantage of this in the CSV writer code so as to properly quote cells that contain delimiter characters.
author Chris Cannam
date Fri, 03 Apr 2020 17:11:05 +0100
parents f04038819c26
children
rev   line source
Chris@1573 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@1573 2
Chris@1573 3 /*
Chris@1573 4 Sonic Visualiser
Chris@1573 5 An audio file viewer and annotation editor.
Chris@1573 6 Centre for Digital Music, Queen Mary, University of London.
Chris@1573 7 This file copyright 2007-2015 Particular Programs Ltd,
Chris@1573 8 copyright 2018 Queen Mary University of London.
Chris@1573 9
Chris@1573 10 This program is free software; you can redistribute it and/or
Chris@1573 11 modify it under the terms of the GNU General Public License as
Chris@1573 12 published by the Free Software Foundation; either version 2 of the
Chris@1573 13 License, or (at your option) any later version. See the file
Chris@1573 14 COPYING included with this distribution for more information.
Chris@1573 15 */
Chris@1573 16
Chris@1573 17 #ifndef SV_MOVING_MEDIAN_H
Chris@1573 18 #define SV_MOVING_MEDIAN_H
Chris@1573 19
Chris@1573 20 #include <bqvec/Allocators.h>
Chris@1573 21 #include <bqvec/VectorOps.h>
Chris@1573 22
Chris@1573 23 #include <algorithm>
Chris@1573 24 #include <iostream>
Chris@1573 25 #include <stdexcept>
Chris@1573 26
Chris@1573 27 /**
Chris@1573 28 * Obtain the median (or other percentile) of a moving window across a
Chris@1573 29 * time series. Construct the MovingMedian object, then push() each
Chris@1573 30 * new value in the time series and get() the median of the most
Chris@1573 31 * recent window. The size of the window, and the percentile
Chris@1573 32 * calculated, can both be changed after construction.
Chris@1573 33 *
Chris@1573 34 * Note that for even-sized windows, the "median" is taken to be the
Chris@1573 35 * value at the start of the second half when sorted, e.g. for size 4,
Chris@1573 36 * the element at index 2 (zero-based) in the sorted window.
Chris@1573 37 *
Chris@1573 38 * Not thread-safe.
Chris@1573 39 */
Chris@1573 40 template <typename T>
Chris@1573 41 class MovingMedian
Chris@1573 42 {
Chris@1573 43 public:
Chris@1573 44 MovingMedian(int size, double percentile = 50.f) :
Chris@1573 45 m_size(size),
Chris@1573 46 m_percentile(percentile) {
Chris@1573 47 if (size < 1) throw std::logic_error("size must be >= 1");
Chris@1573 48 m_frame = breakfastquay::allocate_and_zero<T>(size);
Chris@1573 49 m_sorted = breakfastquay::allocate_and_zero<T>(size);
Chris@1573 50 calculateIndex();
Chris@1573 51 }
Chris@1573 52
Chris@1573 53 ~MovingMedian() {
Chris@1573 54 breakfastquay::deallocate(m_frame);
Chris@1573 55 breakfastquay::deallocate(m_sorted);
Chris@1573 56 }
Chris@1573 57
Chris@1573 58 MovingMedian(const MovingMedian &) =delete;
Chris@1573 59 MovingMedian &operator=(const MovingMedian &) =delete;
Chris@1573 60
Chris@1573 61 void setPercentile(double p) {
Chris@1573 62 m_percentile = p;
Chris@1573 63 calculateIndex();
Chris@1573 64 }
Chris@1573 65
Chris@1573 66 void push(T value) {
Chris@1573 67 if (value != value) {
Chris@1573 68 std::cerr << "WARNING: MovingMedian: NaN encountered" << std::endl;
Chris@1573 69 value = T();
Chris@1573 70 }
Chris@1573 71 drop(m_frame[0]);
Chris@1573 72 breakfastquay::v_move(m_frame, m_frame+1, m_size-1);
Chris@1573 73 m_frame[m_size-1] = value;
Chris@1573 74 put(value);
Chris@1573 75 }
Chris@1573 76
Chris@1573 77 T get() const {
Chris@1573 78 return m_sorted[m_index];
Chris@1573 79 }
Chris@1573 80
Chris@1573 81 int size() const {
Chris@1573 82 return m_size;
Chris@1573 83 }
Chris@1573 84
Chris@1573 85 void reset() {
Chris@1573 86 breakfastquay::v_zero(m_frame, m_size);
Chris@1573 87 breakfastquay::v_zero(m_sorted, m_size);
Chris@1573 88 }
Chris@1573 89
Chris@1573 90 void resize(int target) {
Chris@1573 91 if (target == m_size) return;
Chris@1573 92 int diff = std::abs(target - m_size);
Chris@1573 93 if (target > m_size) { // grow
Chris@1573 94 // we don't want to change the median, so fill spaces with it
Chris@1573 95 T fillValue = get();
Chris@1573 96 m_frame = breakfastquay::reallocate(m_frame, m_size, target);
Chris@1573 97 breakfastquay::v_move(m_frame + diff, m_frame, m_size);
Chris@1573 98 breakfastquay::v_set(m_frame, fillValue, diff);
Chris@1573 99 m_sorted = breakfastquay::reallocate(m_sorted, m_size, target);
Chris@1573 100 for (int sz = m_size + 1; sz <= target; ++sz) {
Chris@1573 101 put(m_sorted, sz, fillValue);
Chris@1573 102 }
Chris@1573 103 } else { // shrink
Chris@1573 104 for (int i = 0; i < diff; ++i) {
Chris@1573 105 drop(m_sorted, m_size - i, m_frame[i]);
Chris@1573 106 }
Chris@1573 107 m_sorted = breakfastquay::reallocate(m_sorted, m_size, target);
Chris@1573 108 breakfastquay::v_move(m_frame, m_frame + diff, target);
Chris@1573 109 m_frame = breakfastquay::reallocate(m_frame, m_size, target);
Chris@1573 110 }
Chris@1573 111
Chris@1573 112 m_size = target;
Chris@1573 113 calculateIndex();
Chris@1573 114 }
Chris@1573 115
Chris@1573 116 void checkIntegrity() const {
Chris@1573 117 check();
Chris@1573 118 }
Chris@1573 119
Chris@1573 120 private:
Chris@1573 121 int m_size;
Chris@1573 122 double m_percentile;
Chris@1573 123 int m_index;
Chris@1573 124 T *m_frame;
Chris@1573 125 T *m_sorted;
Chris@1573 126
Chris@1573 127 void calculateIndex() {
Chris@1573 128 m_index = int((m_size * m_percentile) / 100.f);
Chris@1573 129 if (m_index >= m_size) m_index = m_size-1;
Chris@1573 130 if (m_index < 0) m_index = 0;
Chris@1573 131 }
Chris@1573 132
Chris@1573 133 void put(T value) {
Chris@1573 134 put(m_sorted, m_size, value);
Chris@1573 135 }
Chris@1573 136
Chris@1573 137 static void put(T *const sorted, int size, T value) {
Chris@1573 138
Chris@1573 139 // precondition: sorted points to size-1 sorted values,
Chris@1573 140 // followed by an unused slot (i.e. only the first size-1
Chris@1573 141 // values of sorted are actually sorted)
Chris@1573 142 //
Chris@1573 143 // postcondition: sorted points to size sorted values
Chris@1573 144
Chris@1573 145 T *ptr = std::lower_bound(sorted, sorted + size - 1, value);
Chris@1573 146 breakfastquay::v_move(ptr + 1, ptr, int(sorted + size - ptr) - 1);
Chris@1573 147 *ptr = value;
Chris@1573 148 }
Chris@1573 149
Chris@1573 150 void drop(T value) {
Chris@1573 151 drop(m_sorted, m_size, value);
Chris@1573 152 }
Chris@1573 153
Chris@1573 154 static void drop(T *const sorted, int size, T value) {
Chris@1573 155
Chris@1573 156 // precondition: sorted points to size sorted values, one of
Chris@1573 157 // which is value
Chris@1573 158 //
Chris@1573 159 // postcondition: sorted points to size-1 sorted values,
Chris@1573 160 // followed by a slot that has been reset to default value
Chris@1573 161 // (i.e. only the first size-1 values of sorted are actually
Chris@1573 162 // sorted)
Chris@1573 163
Chris@1573 164 T *ptr = std::lower_bound(sorted, sorted + size, value);
Chris@1573 165 if (*ptr != value) {
Chris@1573 166 throw std::logic_error
Chris@1573 167 ("MovingMedian::drop: value being dropped is not in array");
Chris@1573 168 }
Chris@1573 169 breakfastquay::v_move(ptr, ptr + 1, int(sorted + size - ptr) - 1);
Chris@1573 170 sorted[size-1] = T();
Chris@1573 171 }
Chris@1573 172
Chris@1573 173 void check() const {
Chris@1573 174 bool good = true;
Chris@1573 175 for (int i = 1; i < m_size; ++i) {
Chris@1573 176 if (m_sorted[i] < m_sorted[i-1]) {
Chris@1573 177 std::cerr << "ERROR: MovingMedian::checkIntegrity: "
Chris@1573 178 << "mis-ordered elements in sorted array starting "
Chris@1573 179 << "at index " << i << std::endl;
Chris@1573 180 good = false;
Chris@1573 181 break;
Chris@1573 182 }
Chris@1573 183 }
Chris@1573 184 for (int i = 0; i < m_size; ++i) {
Chris@1573 185 bool found = false;
Chris@1573 186 for (int j = 0; j < m_size; ++j) {
Chris@1573 187 if (m_sorted[j] == m_frame[i]) {
Chris@1573 188 found = true;
Chris@1573 189 break;
Chris@1573 190 }
Chris@1573 191 }
Chris@1573 192 if (!found) {
Chris@1573 193 std::cerr << "ERROR: MovingMedian::checkIntegrity: "
Chris@1573 194 << "element in frame at index " << i
Chris@1573 195 << " not found in sorted array" << std::endl;
Chris@1573 196 good = false;
Chris@1573 197 break;
Chris@1573 198 }
Chris@1573 199 }
Chris@1573 200 for (int i = 0; i < m_size; ++i) {
Chris@1573 201 bool found = false;
Chris@1573 202 for (int j = 0; j < m_size; ++j) {
Chris@1573 203 if (m_sorted[i] == m_frame[j]) {
Chris@1573 204 found = true;
Chris@1573 205 break;
Chris@1573 206 }
Chris@1573 207 }
Chris@1573 208 if (!found) {
Chris@1573 209 std::cerr << "ERROR: MovingMedian::checkIntegrity: "
Chris@1573 210 << "element in sorted array at index " << i
Chris@1573 211 << " not found in source frame" << std::endl;
Chris@1573 212 good = false;
Chris@1573 213 break;
Chris@1573 214 }
Chris@1573 215 }
Chris@1573 216 if (!good) {
Chris@1573 217 std::cerr << "Frame contains:" << std::endl;
Chris@1573 218 std::cerr << "[ ";
Chris@1573 219 for (int j = 0; j < m_size; ++j) {
Chris@1573 220 std::cerr << m_frame[j] << " ";
Chris@1573 221 }
Chris@1573 222 std::cerr << "]" << std::endl;
Chris@1573 223 std::cerr << "Sorted array contains:" << std::endl;
Chris@1573 224 std::cerr << "[ ";
Chris@1573 225 for (int j = 0; j < m_size; ++j) {
Chris@1573 226 std::cerr << m_sorted[j] << " ";
Chris@1573 227 }
Chris@1573 228 std::cerr << "]" << std::endl;
Chris@1573 229 throw std::logic_error("MovingMedian failed integrity check");
Chris@1573 230 }
Chris@1573 231 }
Chris@1573 232 };
Chris@1573 233
Chris@1573 234 #endif
Chris@1573 235