Mercurial > hg > svcore
comparison data/model/BasicCompressedDenseThreeDimensionalModel.cpp @ 1777:d484490cdf69
Split EditableDenseThreeDimensionalModel into explicitly compressed and uncompressed variants. Simplifies the uncompressed version, and we may want to consider whether we need the compressed one at all.
author | Chris Cannam |
---|---|
date | Tue, 10 Sep 2019 16:34:47 +0100 |
parents | |
children | c546429d4c2f |
comparison
equal
deleted
inserted
replaced
1776:5750b9e60818 | 1777:d484490cdf69 |
---|---|
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 2006 Chris Cannam and QMUL. | |
8 | |
9 This program is free software; you can redistribute it and/or | |
10 modify it under the terms of the GNU General Public License as | |
11 published by the Free Software Foundation; either version 2 of the | |
12 License, or (at your option) any later version. See the file | |
13 COPYING included with this distribution for more information. | |
14 */ | |
15 | |
16 #include "BasicCompressedDenseThreeDimensionalModel.h" | |
17 | |
18 #include "base/LogRange.h" | |
19 | |
20 #include <QTextStream> | |
21 #include <QStringList> | |
22 #include <QReadLocker> | |
23 #include <QWriteLocker> | |
24 | |
25 #include <iostream> | |
26 | |
27 #include <cmath> | |
28 #include <cassert> | |
29 | |
30 using std::vector; | |
31 | |
32 #include "system/System.h" | |
33 | |
34 BasicCompressedDenseThreeDimensionalModel::BasicCompressedDenseThreeDimensionalModel(sv_samplerate_t sampleRate, | |
35 int resolution, | |
36 int yBinCount, | |
37 bool notifyOnAdd) : | |
38 m_startFrame(0), | |
39 m_sampleRate(sampleRate), | |
40 m_resolution(resolution), | |
41 m_yBinCount(yBinCount), | |
42 m_minimum(0.0), | |
43 m_maximum(0.0), | |
44 m_haveExtents(false), | |
45 m_notifyOnAdd(notifyOnAdd), | |
46 m_sinceLastNotifyMin(-1), | |
47 m_sinceLastNotifyMax(-1), | |
48 m_completion(100) | |
49 { | |
50 } | |
51 | |
52 bool | |
53 BasicCompressedDenseThreeDimensionalModel::isOK() const | |
54 { | |
55 return true; | |
56 } | |
57 | |
58 bool | |
59 BasicCompressedDenseThreeDimensionalModel::isReady(int *completion) const | |
60 { | |
61 if (completion) *completion = getCompletion(); | |
62 return true; | |
63 } | |
64 | |
65 sv_samplerate_t | |
66 BasicCompressedDenseThreeDimensionalModel::getSampleRate() const | |
67 { | |
68 return m_sampleRate; | |
69 } | |
70 | |
71 sv_frame_t | |
72 BasicCompressedDenseThreeDimensionalModel::getStartFrame() const | |
73 { | |
74 return m_startFrame; | |
75 } | |
76 | |
77 void | |
78 BasicCompressedDenseThreeDimensionalModel::setStartFrame(sv_frame_t f) | |
79 { | |
80 m_startFrame = f; | |
81 } | |
82 | |
83 sv_frame_t | |
84 BasicCompressedDenseThreeDimensionalModel::getTrueEndFrame() const | |
85 { | |
86 return m_resolution * m_data.size() + (m_resolution - 1); | |
87 } | |
88 | |
89 int | |
90 BasicCompressedDenseThreeDimensionalModel::getResolution() const | |
91 { | |
92 return m_resolution; | |
93 } | |
94 | |
95 void | |
96 BasicCompressedDenseThreeDimensionalModel::setResolution(int sz) | |
97 { | |
98 m_resolution = sz; | |
99 } | |
100 | |
101 int | |
102 BasicCompressedDenseThreeDimensionalModel::getWidth() const | |
103 { | |
104 return int(m_data.size()); | |
105 } | |
106 | |
107 int | |
108 BasicCompressedDenseThreeDimensionalModel::getHeight() const | |
109 { | |
110 return m_yBinCount; | |
111 } | |
112 | |
113 void | |
114 BasicCompressedDenseThreeDimensionalModel::setHeight(int sz) | |
115 { | |
116 m_yBinCount = sz; | |
117 } | |
118 | |
119 float | |
120 BasicCompressedDenseThreeDimensionalModel::getMinimumLevel() const | |
121 { | |
122 return m_minimum; | |
123 } | |
124 | |
125 void | |
126 BasicCompressedDenseThreeDimensionalModel::setMinimumLevel(float level) | |
127 { | |
128 m_minimum = level; | |
129 } | |
130 | |
131 float | |
132 BasicCompressedDenseThreeDimensionalModel::getMaximumLevel() const | |
133 { | |
134 return m_maximum; | |
135 } | |
136 | |
137 void | |
138 BasicCompressedDenseThreeDimensionalModel::setMaximumLevel(float level) | |
139 { | |
140 m_maximum = level; | |
141 } | |
142 | |
143 BasicCompressedDenseThreeDimensionalModel::Column | |
144 BasicCompressedDenseThreeDimensionalModel::getColumn(int index) const | |
145 { | |
146 QReadLocker locker(&m_lock); | |
147 if (in_range_for(m_data, index)) return expandAndRetrieve(index); | |
148 else return Column(); | |
149 } | |
150 | |
151 float | |
152 BasicCompressedDenseThreeDimensionalModel::getValueAt(int index, int n) const | |
153 { | |
154 Column c = getColumn(index); | |
155 if (in_range_for(c, n)) return c.at(n); | |
156 return m_minimum; | |
157 } | |
158 | |
159 //static int given = 0, stored = 0; | |
160 | |
161 void | |
162 BasicCompressedDenseThreeDimensionalModel::truncateAndStore(int index, | |
163 const Column &values) | |
164 { | |
165 assert(in_range_for(m_data, index)); | |
166 | |
167 //cout << "truncateAndStore(" << index << ", " << values.size() << ")" << endl; | |
168 | |
169 // The default case is to store the entire column at m_data[index] | |
170 // and place 0 at m_trunc[index] to indicate that it has not been | |
171 // truncated. We only do clever stuff if one of the clever-stuff | |
172 // tests works out. | |
173 | |
174 m_trunc[index] = 0; | |
175 if (index == 0 || | |
176 int(values.size()) != m_yBinCount) { | |
177 // given += values.size(); | |
178 // stored += values.size(); | |
179 m_data[index] = values; | |
180 return; | |
181 } | |
182 | |
183 // Maximum distance between a column and the one we refer to as | |
184 // the source of its truncated values. Limited by having to fit | |
185 // in a signed char, but in any case small values are usually | |
186 // better | |
187 static int maxdist = 6; | |
188 | |
189 bool known = false; // do we know whether to truncate at top or bottom? | |
190 bool top = false; // if we do know, will we truncate at top? | |
191 | |
192 // If the previous column is not truncated, then it is the only | |
193 // candidate for comparison. If it is truncated, then the column | |
194 // that it refers to is the only candidate. Either way, we only | |
195 // have one possible column to compare against here, and we are | |
196 // being careful to ensure it is not a truncated one (to avoid | |
197 // doing more work recursively when uncompressing). | |
198 int tdist = 1; | |
199 int ptrunc = m_trunc[index-1]; | |
200 if (ptrunc < 0) { | |
201 top = false; | |
202 known = true; | |
203 tdist = -ptrunc + 1; | |
204 } else if (ptrunc > 0) { | |
205 top = true; | |
206 known = true; | |
207 tdist = ptrunc + 1; | |
208 } | |
209 | |
210 Column p = expandAndRetrieve(index - tdist); | |
211 int h = m_yBinCount; | |
212 | |
213 if (int(p.size()) == h && tdist <= maxdist) { | |
214 | |
215 int bcount = 0, tcount = 0; | |
216 if (!known || !top) { | |
217 // count how many identical values there are at the bottom | |
218 for (int i = 0; i < h; ++i) { | |
219 if (values.at(i) == p.at(i)) ++bcount; | |
220 else break; | |
221 } | |
222 } | |
223 if (!known || top) { | |
224 // count how many identical values there are at the top | |
225 for (int i = h; i > 0; --i) { | |
226 if (values.at(i-1) == p.at(i-1)) ++tcount; | |
227 else break; | |
228 } | |
229 } | |
230 if (!known) top = (tcount > bcount); | |
231 | |
232 int limit = h / 4; // don't bother unless we have at least this many | |
233 if ((top ? tcount : bcount) > limit) { | |
234 | |
235 if (!top) { | |
236 // create a new column with h - bcount values from bcount up | |
237 Column tcol(h - bcount); | |
238 // given += values.size(); | |
239 // stored += h - bcount; | |
240 for (int i = bcount; i < h; ++i) { | |
241 tcol[i - bcount] = values.at(i); | |
242 } | |
243 m_data[index] = tcol; | |
244 m_trunc[index] = (signed char)(-tdist); | |
245 return; | |
246 } else { | |
247 // create a new column with h - tcount values from 0 up | |
248 Column tcol(h - tcount); | |
249 // given += values.size(); | |
250 // stored += h - tcount; | |
251 for (int i = 0; i < h - tcount; ++i) { | |
252 tcol[i] = values.at(i); | |
253 } | |
254 m_data[index] = tcol; | |
255 m_trunc[index] = (signed char)(tdist); | |
256 return; | |
257 } | |
258 } | |
259 } | |
260 | |
261 // given += values.size(); | |
262 // stored += values.size(); | |
263 // cout << "given: " << given << ", stored: " << stored << " (" | |
264 // << ((float(stored) / float(given)) * 100.f) << "%)" << endl; | |
265 | |
266 // default case if nothing wacky worked out | |
267 m_data[index] = values; | |
268 return; | |
269 } | |
270 | |
271 BasicCompressedDenseThreeDimensionalModel::Column | |
272 BasicCompressedDenseThreeDimensionalModel::rightHeight(const Column &c) const | |
273 { | |
274 if (int(c.size()) == m_yBinCount) return c; | |
275 else { | |
276 Column cc(c); | |
277 cc.resize(m_yBinCount, 0.0); | |
278 return cc; | |
279 } | |
280 } | |
281 | |
282 BasicCompressedDenseThreeDimensionalModel::Column | |
283 BasicCompressedDenseThreeDimensionalModel::expandAndRetrieve(int index) const | |
284 { | |
285 // See comment above m_trunc declaration in header | |
286 | |
287 assert(index >= 0 && index < int(m_data.size())); | |
288 Column c = m_data.at(index); | |
289 if (index == 0) { | |
290 return rightHeight(c); | |
291 } | |
292 int trunc = (int)m_trunc[index]; | |
293 if (trunc == 0) { | |
294 return rightHeight(c); | |
295 } | |
296 bool top = true; | |
297 int tdist = trunc; | |
298 if (trunc < 0) { top = false; tdist = -trunc; } | |
299 Column p = expandAndRetrieve(index - tdist); | |
300 int psize = int(p.size()), csize = int(c.size()); | |
301 if (psize != m_yBinCount) { | |
302 cerr << "WARNING: BasicCompressedDenseThreeDimensionalModel::expandAndRetrieve: Trying to expand from incorrectly sized column" << endl; | |
303 } | |
304 if (top) { | |
305 for (int i = csize; i < psize; ++i) { | |
306 c.push_back(p.at(i)); | |
307 } | |
308 } else { | |
309 Column cc(psize); | |
310 for (int i = 0; i < psize - csize; ++i) { | |
311 cc[i] = p.at(i); | |
312 } | |
313 for (int i = 0; i < csize; ++i) { | |
314 cc[i + (psize - csize)] = c.at(i); | |
315 } | |
316 return cc; | |
317 } | |
318 return c; | |
319 } | |
320 | |
321 void | |
322 BasicCompressedDenseThreeDimensionalModel::setColumn(int index, | |
323 const Column &values) | |
324 { | |
325 QWriteLocker locker(&m_lock); | |
326 | |
327 while (index >= int(m_data.size())) { | |
328 m_data.push_back(Column()); | |
329 m_trunc.push_back(0); | |
330 } | |
331 | |
332 bool allChange = false; | |
333 | |
334 for (int i = 0; in_range_for(values, i); ++i) { | |
335 float value = values[i]; | |
336 if (ISNAN(value) || ISINF(value)) { | |
337 continue; | |
338 } | |
339 if (!m_haveExtents || value < m_minimum) { | |
340 m_minimum = value; | |
341 allChange = true; | |
342 } | |
343 if (!m_haveExtents || value > m_maximum) { | |
344 m_maximum = value; | |
345 allChange = true; | |
346 } | |
347 m_haveExtents = true; | |
348 } | |
349 | |
350 truncateAndStore(index, values); | |
351 | |
352 // assert(values == expandAndRetrieve(index)); | |
353 | |
354 sv_frame_t windowStart = index; | |
355 windowStart *= m_resolution; | |
356 | |
357 if (m_notifyOnAdd) { | |
358 if (allChange) { | |
359 emit modelChanged(getId()); | |
360 } else { | |
361 emit modelChangedWithin(getId(), | |
362 windowStart, windowStart + m_resolution); | |
363 } | |
364 } else { | |
365 if (allChange) { | |
366 m_sinceLastNotifyMin = -1; | |
367 m_sinceLastNotifyMax = -1; | |
368 emit modelChanged(getId()); | |
369 } else { | |
370 if (m_sinceLastNotifyMin == -1 || | |
371 windowStart < m_sinceLastNotifyMin) { | |
372 m_sinceLastNotifyMin = windowStart; | |
373 } | |
374 if (m_sinceLastNotifyMax == -1 || | |
375 windowStart > m_sinceLastNotifyMax) { | |
376 m_sinceLastNotifyMax = windowStart; | |
377 } | |
378 } | |
379 } | |
380 } | |
381 | |
382 QString | |
383 BasicCompressedDenseThreeDimensionalModel::getBinName(int n) const | |
384 { | |
385 if (n >= 0 && (int)m_binNames.size() > n) return m_binNames[n]; | |
386 else return ""; | |
387 } | |
388 | |
389 void | |
390 BasicCompressedDenseThreeDimensionalModel::setBinName(int n, QString name) | |
391 { | |
392 while ((int)m_binNames.size() <= n) m_binNames.push_back(""); | |
393 m_binNames[n] = name; | |
394 emit modelChanged(getId()); | |
395 } | |
396 | |
397 void | |
398 BasicCompressedDenseThreeDimensionalModel::setBinNames(std::vector<QString> names) | |
399 { | |
400 m_binNames = names; | |
401 emit modelChanged(getId()); | |
402 } | |
403 | |
404 bool | |
405 BasicCompressedDenseThreeDimensionalModel::hasBinValues() const | |
406 { | |
407 return !m_binValues.empty(); | |
408 } | |
409 | |
410 float | |
411 BasicCompressedDenseThreeDimensionalModel::getBinValue(int n) const | |
412 { | |
413 if (n < (int)m_binValues.size()) return m_binValues[n]; | |
414 else return 0.f; | |
415 } | |
416 | |
417 void | |
418 BasicCompressedDenseThreeDimensionalModel::setBinValues(std::vector<float> values) | |
419 { | |
420 m_binValues = values; | |
421 } | |
422 | |
423 QString | |
424 BasicCompressedDenseThreeDimensionalModel::getBinValueUnit() const | |
425 { | |
426 return m_binValueUnit; | |
427 } | |
428 | |
429 void | |
430 BasicCompressedDenseThreeDimensionalModel::setBinValueUnit(QString unit) | |
431 { | |
432 m_binValueUnit = unit; | |
433 } | |
434 | |
435 bool | |
436 BasicCompressedDenseThreeDimensionalModel::shouldUseLogValueScale() const | |
437 { | |
438 QReadLocker locker(&m_lock); | |
439 | |
440 vector<double> sample; | |
441 vector<int> n; | |
442 | |
443 for (int i = 0; i < 10; ++i) { | |
444 int index = i * 10; | |
445 if (in_range_for(m_data, index)) { | |
446 const Column &c = m_data.at(index); | |
447 while (c.size() > sample.size()) { | |
448 sample.push_back(0.0); | |
449 n.push_back(0); | |
450 } | |
451 for (int j = 0; in_range_for(c, j); ++j) { | |
452 sample[j] += c.at(j); | |
453 ++n[j]; | |
454 } | |
455 } | |
456 } | |
457 | |
458 if (sample.empty()) return false; | |
459 for (decltype(sample)::size_type j = 0; j < sample.size(); ++j) { | |
460 if (n[j]) sample[j] /= n[j]; | |
461 } | |
462 | |
463 return LogRange::shouldUseLogScale(sample); | |
464 } | |
465 | |
466 void | |
467 BasicCompressedDenseThreeDimensionalModel::setCompletion(int completion, bool update) | |
468 { | |
469 if (m_completion != completion) { | |
470 m_completion = completion; | |
471 | |
472 if (completion == 100) { | |
473 | |
474 m_notifyOnAdd = true; // henceforth | |
475 emit modelChanged(getId()); | |
476 | |
477 } else if (!m_notifyOnAdd) { | |
478 | |
479 if (update && | |
480 m_sinceLastNotifyMin >= 0 && | |
481 m_sinceLastNotifyMax >= 0) { | |
482 emit modelChangedWithin(getId(), | |
483 m_sinceLastNotifyMin, | |
484 m_sinceLastNotifyMax + m_resolution); | |
485 m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1; | |
486 } else { | |
487 emit completionChanged(getId()); | |
488 } | |
489 } else { | |
490 emit completionChanged(getId()); | |
491 } | |
492 } | |
493 } | |
494 | |
495 int | |
496 BasicCompressedDenseThreeDimensionalModel::getCompletion() const | |
497 { | |
498 return m_completion; | |
499 } | |
500 | |
501 QString | |
502 BasicCompressedDenseThreeDimensionalModel::toDelimitedDataString(QString delimiter, | |
503 DataExportOptions, | |
504 sv_frame_t startFrame, | |
505 sv_frame_t duration) const | |
506 { | |
507 QReadLocker locker(&m_lock); | |
508 QString s; | |
509 for (int i = 0; in_range_for(m_data, i); ++i) { | |
510 Column c = getColumn(i); | |
511 sv_frame_t fr = m_startFrame + i * m_resolution; | |
512 if (fr >= startFrame && fr < startFrame + duration) { | |
513 QStringList list; | |
514 for (int j = 0; in_range_for(c, j); ++j) { | |
515 list << QString("%1").arg(c.at(j)); | |
516 } | |
517 s += list.join(delimiter) + "\n"; | |
518 } | |
519 } | |
520 return s; | |
521 } | |
522 | |
523 void | |
524 BasicCompressedDenseThreeDimensionalModel::toXml(QTextStream &out, | |
525 QString indent, | |
526 QString extraAttributes) const | |
527 { | |
528 QReadLocker locker(&m_lock); | |
529 | |
530 // For historical reasons we read and write "resolution" as "windowSize". | |
531 | |
532 // Our dataset doesn't have its own export ID, we just use | |
533 // ours. Actually any model could do that, since datasets aren't | |
534 // in the same id-space as models when re-read | |
535 | |
536 SVDEBUG << "BasicCompressedDenseThreeDimensionalModel::toXml" << endl; | |
537 | |
538 Model::toXml | |
539 (out, indent, | |
540 QString("type=\"dense\" dimensions=\"3\" windowSize=\"%1\" yBinCount=\"%2\" minimum=\"%3\" maximum=\"%4\" dataset=\"%5\" startFrame=\"%6\" %7") | |
541 .arg(m_resolution) | |
542 .arg(m_yBinCount) | |
543 .arg(m_minimum) | |
544 .arg(m_maximum) | |
545 .arg(getExportId()) | |
546 .arg(m_startFrame) | |
547 .arg(extraAttributes)); | |
548 | |
549 out << indent; | |
550 out << QString("<dataset id=\"%1\" dimensions=\"3\" separator=\" \">\n") | |
551 .arg(getExportId()); | |
552 | |
553 for (int i = 0; in_range_for(m_binNames, i); ++i) { | |
554 if (m_binNames[i] != "") { | |
555 out << indent + " "; | |
556 out << QString("<bin number=\"%1\" name=\"%2\"/>\n") | |
557 .arg(i).arg(m_binNames[i]); | |
558 } | |
559 } | |
560 | |
561 for (int i = 0; in_range_for(m_data, i); ++i) { | |
562 Column c = getColumn(i); | |
563 out << indent + " "; | |
564 out << QString("<row n=\"%1\">").arg(i); | |
565 for (int j = 0; in_range_for(c, j); ++j) { | |
566 if (j > 0) out << " "; | |
567 out << c.at(j); | |
568 } | |
569 out << QString("</row>\n"); | |
570 out.flush(); | |
571 } | |
572 | |
573 out << indent + "</dataset>\n"; | |
574 } | |
575 | |
576 |