MovingMedian.h
Go to the documentation of this file.
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 
40 template <typename T>
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);
51  }
52 
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;
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;
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 
void resize(int target)
Definition: MovingMedian.h:90
void drop(T value)
Definition: MovingMedian.h:150
static void put(T *const sorted, int size, T value)
Definition: MovingMedian.h:137
Obtain the median (or other percentile) of a moving window across a time series.
Definition: MovingMedian.h:41
void reset()
Definition: MovingMedian.h:85
MovingMedian(int size, double percentile=50.f)
Definition: MovingMedian.h:44
void setPercentile(double p)
Definition: MovingMedian.h:61
void push(T value)
Definition: MovingMedian.h:66
void check() const
Definition: MovingMedian.h:173
double m_percentile
Definition: MovingMedian.h:122
static void drop(T *const sorted, int size, T value)
Definition: MovingMedian.h:154
void checkIntegrity() const
Definition: MovingMedian.h:116
MovingMedian & operator=(const MovingMedian &)=delete
void calculateIndex()
Definition: MovingMedian.h:127
void put(T value)
Definition: MovingMedian.h:133
int size() const
Definition: MovingMedian.h:81