comparison base/MovingMedian.h @ 1573:f04038819c26 spectrogramparam

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