Mercurial > hg > svcore
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 |