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