comparison data/model/EditableDenseThreeDimensionalModel.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 6d09d68165a4
children c546429d4c2f
comparison
equal deleted inserted replaced
1776:5750b9e60818 1777:d484490cdf69
17 17
18 #include "base/LogRange.h" 18 #include "base/LogRange.h"
19 19
20 #include <QTextStream> 20 #include <QTextStream>
21 #include <QStringList> 21 #include <QStringList>
22 #include <QReadLocker> 22 #include <QMutexLocker>
23 #include <QWriteLocker>
24 23
25 #include <iostream> 24 #include <iostream>
26 25
27 #include <cmath> 26 #include <cmath>
28 #include <cassert> 27 #include <cassert>
32 #include "system/System.h" 31 #include "system/System.h"
33 32
34 EditableDenseThreeDimensionalModel::EditableDenseThreeDimensionalModel(sv_samplerate_t sampleRate, 33 EditableDenseThreeDimensionalModel::EditableDenseThreeDimensionalModel(sv_samplerate_t sampleRate,
35 int resolution, 34 int resolution,
36 int yBinCount, 35 int yBinCount,
37 CompressionType compression,
38 bool notifyOnAdd) : 36 bool notifyOnAdd) :
39 m_startFrame(0), 37 m_startFrame(0),
40 m_sampleRate(sampleRate), 38 m_sampleRate(sampleRate),
41 m_resolution(resolution), 39 m_resolution(resolution),
42 m_yBinCount(yBinCount), 40 m_yBinCount(yBinCount),
43 m_compression(compression),
44 m_minimum(0.0), 41 m_minimum(0.0),
45 m_maximum(0.0), 42 m_maximum(0.0),
46 m_haveExtents(false), 43 m_haveExtents(false),
47 m_notifyOnAdd(notifyOnAdd), 44 m_notifyOnAdd(notifyOnAdd),
48 m_sinceLastNotifyMin(-1), 45 m_sinceLastNotifyMin(-1),
143 } 140 }
144 141
145 EditableDenseThreeDimensionalModel::Column 142 EditableDenseThreeDimensionalModel::Column
146 EditableDenseThreeDimensionalModel::getColumn(int index) const 143 EditableDenseThreeDimensionalModel::getColumn(int index) const
147 { 144 {
148 QReadLocker locker(&m_lock); 145 QMutexLocker locker(&m_mutex);
149 if (in_range_for(m_data, index)) return expandAndRetrieve(index); 146 if (!in_range_for(m_data, index)) {
150 else return Column(); 147 return {};
151 } 148 }
152 149 Column c = m_data.at(index);
153 float 150 if (int(c.size()) == m_yBinCount) {
154 EditableDenseThreeDimensionalModel::getValueAt(int index, int n) const 151 return c;
155 { 152 } else {
156 Column c = getColumn(index);
157 if (in_range_for(c, n)) return c.at(n);
158 return m_minimum;
159 }
160
161 //static int given = 0, stored = 0;
162
163 void
164 EditableDenseThreeDimensionalModel::truncateAndStore(int index,
165 const Column &values)
166 {
167 assert(in_range_for(m_data, index));
168
169 //cout << "truncateAndStore(" << index << ", " << values.size() << ")" << endl;
170
171 // The default case is to store the entire column at m_data[index]
172 // and place 0 at m_trunc[index] to indicate that it has not been
173 // truncated. We only do clever stuff if one of the clever-stuff
174 // tests works out.
175
176 m_trunc[index] = 0;
177 if (index == 0 ||
178 m_compression == NoCompression ||
179 int(values.size()) != m_yBinCount) {
180 // given += values.size();
181 // stored += values.size();
182 m_data[index] = values;
183 return;
184 }
185
186 // Maximum distance between a column and the one we refer to as
187 // the source of its truncated values. Limited by having to fit
188 // in a signed char, but in any case small values are usually
189 // better
190 static int maxdist = 6;
191
192 bool known = false; // do we know whether to truncate at top or bottom?
193 bool top = false; // if we do know, will we truncate at top?
194
195 // If the previous column is not truncated, then it is the only
196 // candidate for comparison. If it is truncated, then the column
197 // that it refers to is the only candidate. Either way, we only
198 // have one possible column to compare against here, and we are
199 // being careful to ensure it is not a truncated one (to avoid
200 // doing more work recursively when uncompressing).
201 int tdist = 1;
202 int ptrunc = m_trunc[index-1];
203 if (ptrunc < 0) {
204 top = false;
205 known = true;
206 tdist = -ptrunc + 1;
207 } else if (ptrunc > 0) {
208 top = true;
209 known = true;
210 tdist = ptrunc + 1;
211 }
212
213 Column p = expandAndRetrieve(index - tdist);
214 int h = m_yBinCount;
215
216 if (int(p.size()) == h && tdist <= maxdist) {
217
218 int bcount = 0, tcount = 0;
219 if (!known || !top) {
220 // count how many identical values there are at the bottom
221 for (int i = 0; i < h; ++i) {
222 if (values.at(i) == p.at(i)) ++bcount;
223 else break;
224 }
225 }
226 if (!known || top) {
227 // count how many identical values there are at the top
228 for (int i = h; i > 0; --i) {
229 if (values.at(i-1) == p.at(i-1)) ++tcount;
230 else break;
231 }
232 }
233 if (!known) top = (tcount > bcount);
234
235 int limit = h / 4; // don't bother unless we have at least this many
236 if ((top ? tcount : bcount) > limit) {
237
238 if (!top) {
239 // create a new column with h - bcount values from bcount up
240 Column tcol(h - bcount);
241 // given += values.size();
242 // stored += h - bcount;
243 for (int i = bcount; i < h; ++i) {
244 tcol[i - bcount] = values.at(i);
245 }
246 m_data[index] = tcol;
247 m_trunc[index] = (signed char)(-tdist);
248 return;
249 } else {
250 // create a new column with h - tcount values from 0 up
251 Column tcol(h - tcount);
252 // given += values.size();
253 // stored += h - tcount;
254 for (int i = 0; i < h - tcount; ++i) {
255 tcol[i] = values.at(i);
256 }
257 m_data[index] = tcol;
258 m_trunc[index] = (signed char)(tdist);
259 return;
260 }
261 }
262 }
263
264 // given += values.size();
265 // stored += values.size();
266 // cout << "given: " << given << ", stored: " << stored << " ("
267 // << ((float(stored) / float(given)) * 100.f) << "%)" << endl;
268
269 // default case if nothing wacky worked out
270 m_data[index] = values;
271 return;
272 }
273
274 EditableDenseThreeDimensionalModel::Column
275 EditableDenseThreeDimensionalModel::rightHeight(const Column &c) const
276 {
277 if (int(c.size()) == m_yBinCount) return c;
278 else {
279 Column cc(c); 153 Column cc(c);
280 cc.resize(m_yBinCount, 0.0); 154 cc.resize(m_yBinCount, 0.0);
281 return cc; 155 return cc;
282 } 156 }
283 } 157 }
284 158
285 EditableDenseThreeDimensionalModel::Column 159 float
286 EditableDenseThreeDimensionalModel::expandAndRetrieve(int index) const 160 EditableDenseThreeDimensionalModel::getValueAt(int index, int n) const
287 { 161 {
288 // See comment above m_trunc declaration in header 162 QMutexLocker locker(&m_mutex);
289 163 if (!in_range_for(m_data, index)) {
290 assert(index >= 0 && index < int(m_data.size())); 164 return m_minimum;
291 Column c = m_data.at(index); 165 }
292 if (index == 0) { 166 const Column &c = m_data.at(index);
293 return rightHeight(c); 167 if (!in_range_for(c, n)) {
294 } 168 return m_minimum;
295 int trunc = (int)m_trunc[index]; 169 }
296 if (trunc == 0) { 170 return c.at(n);
297 return rightHeight(c);
298 }
299 bool top = true;
300 int tdist = trunc;
301 if (trunc < 0) { top = false; tdist = -trunc; }
302 Column p = expandAndRetrieve(index - tdist);
303 int psize = int(p.size()), csize = int(c.size());
304 if (psize != m_yBinCount) {
305 cerr << "WARNING: EditableDenseThreeDimensionalModel::expandAndRetrieve: Trying to expand from incorrectly sized column" << endl;
306 }
307 if (top) {
308 for (int i = csize; i < psize; ++i) {
309 c.push_back(p.at(i));
310 }
311 } else {
312 Column cc(psize);
313 for (int i = 0; i < psize - csize; ++i) {
314 cc[i] = p.at(i);
315 }
316 for (int i = 0; i < csize; ++i) {
317 cc[i + (psize - csize)] = c.at(i);
318 }
319 return cc;
320 }
321 return c;
322 } 171 }
323 172
324 void 173 void
325 EditableDenseThreeDimensionalModel::setColumn(int index, 174 EditableDenseThreeDimensionalModel::setColumn(int index,
326 const Column &values) 175 const Column &values)
327 { 176 {
328 QWriteLocker locker(&m_lock);
329
330 while (index >= int(m_data.size())) {
331 m_data.push_back(Column());
332 m_trunc.push_back(0);
333 }
334
335 bool allChange = false; 177 bool allChange = false;
336
337 for (int i = 0; in_range_for(values, i); ++i) {
338 float value = values[i];
339 if (ISNAN(value) || ISINF(value)) {
340 continue;
341 }
342 if (!m_haveExtents || value < m_minimum) {
343 m_minimum = value;
344 allChange = true;
345 }
346 if (!m_haveExtents || value > m_maximum) {
347 m_maximum = value;
348 allChange = true;
349 }
350 m_haveExtents = true;
351 }
352
353 truncateAndStore(index, values);
354
355 // assert(values == expandAndRetrieve(index));
356
357 sv_frame_t windowStart = index; 178 sv_frame_t windowStart = index;
358 windowStart *= m_resolution; 179 windowStart *= m_resolution;
180
181 {
182 QMutexLocker locker(&m_mutex);
183
184 while (index >= int(m_data.size())) {
185 m_data.push_back(Column());
186 }
187
188 for (int i = 0; in_range_for(values, i); ++i) {
189 float value = values[i];
190 if (ISNAN(value) || ISINF(value)) {
191 continue;
192 }
193 if (!m_haveExtents || value < m_minimum) {
194 m_minimum = value;
195 allChange = true;
196 }
197 if (!m_haveExtents || value > m_maximum) {
198 m_maximum = value;
199 allChange = true;
200 }
201 m_haveExtents = true;
202 }
203
204 m_data[index] = values;
205
206 if (allChange) {
207 m_sinceLastNotifyMin = -1;
208 m_sinceLastNotifyMax = -1;
209 } else {
210 if (m_sinceLastNotifyMin == -1 ||
211 windowStart < m_sinceLastNotifyMin) {
212 m_sinceLastNotifyMin = windowStart;
213 }
214 if (m_sinceLastNotifyMax == -1 ||
215 windowStart > m_sinceLastNotifyMax) {
216 m_sinceLastNotifyMax = windowStart;
217 }
218 }
219 }
359 220
360 if (m_notifyOnAdd) { 221 if (m_notifyOnAdd) {
361 if (allChange) { 222 if (allChange) {
362 emit modelChanged(getId()); 223 emit modelChanged(getId());
363 } else { 224 } else {
364 emit modelChangedWithin(getId(), 225 emit modelChangedWithin(getId(),
365 windowStart, windowStart + m_resolution); 226 windowStart, windowStart + m_resolution);
366 } 227 }
367 } else { 228 } else {
368 if (allChange) { 229 if (allChange) {
369 m_sinceLastNotifyMin = -1;
370 m_sinceLastNotifyMax = -1;
371 emit modelChanged(getId()); 230 emit modelChanged(getId());
372 } else {
373 if (m_sinceLastNotifyMin == -1 ||
374 windowStart < m_sinceLastNotifyMin) {
375 m_sinceLastNotifyMin = windowStart;
376 }
377 if (m_sinceLastNotifyMax == -1 ||
378 windowStart > m_sinceLastNotifyMax) {
379 m_sinceLastNotifyMax = windowStart;
380 }
381 } 231 }
382 } 232 }
383 } 233 }
384 234
385 QString 235 QString
436 } 286 }
437 287
438 bool 288 bool
439 EditableDenseThreeDimensionalModel::shouldUseLogValueScale() const 289 EditableDenseThreeDimensionalModel::shouldUseLogValueScale() const
440 { 290 {
441 QReadLocker locker(&m_lock); 291 QMutexLocker locker(&m_mutex);
442 292
443 vector<double> sample; 293 vector<double> sample;
444 vector<int> n; 294 vector<int> n;
445 295
446 for (int i = 0; i < 10; ++i) { 296 for (int i = 0; i < 10; ++i) {
505 EditableDenseThreeDimensionalModel::toDelimitedDataString(QString delimiter, 355 EditableDenseThreeDimensionalModel::toDelimitedDataString(QString delimiter,
506 DataExportOptions, 356 DataExportOptions,
507 sv_frame_t startFrame, 357 sv_frame_t startFrame,
508 sv_frame_t duration) const 358 sv_frame_t duration) const
509 { 359 {
510 QReadLocker locker(&m_lock); 360 QMutexLocker locker(&m_mutex);
511 QString s; 361 QString s;
512 for (int i = 0; in_range_for(m_data, i); ++i) { 362 for (int i = 0; in_range_for(m_data, i); ++i) {
513 sv_frame_t fr = m_startFrame + i * m_resolution; 363 sv_frame_t fr = m_startFrame + i * m_resolution;
514 if (fr >= startFrame && fr < startFrame + duration) { 364 if (fr >= startFrame && fr < startFrame + duration) {
515 QStringList list; 365 QStringList list;
525 void 375 void
526 EditableDenseThreeDimensionalModel::toXml(QTextStream &out, 376 EditableDenseThreeDimensionalModel::toXml(QTextStream &out,
527 QString indent, 377 QString indent,
528 QString extraAttributes) const 378 QString extraAttributes) const
529 { 379 {
530 QReadLocker locker(&m_lock); 380 QMutexLocker locker(&m_mutex);
531 381
532 // For historical reasons we read and write "resolution" as "windowSize". 382 // For historical reasons we read and write "resolution" as "windowSize".
533 383
534 // Our dataset doesn't have its own export ID, we just use 384 // Our dataset doesn't have its own export ID, we just use
535 // ours. Actually any model could do that, since datasets aren't 385 // ours. Actually any model could do that, since datasets aren't
550 400
551 out << indent; 401 out << indent;
552 out << QString("<dataset id=\"%1\" dimensions=\"3\" separator=\" \">\n") 402 out << QString("<dataset id=\"%1\" dimensions=\"3\" separator=\" \">\n")
553 .arg(getExportId()); 403 .arg(getExportId());
554 404
555 for (int i = 0; i < (int)m_binNames.size(); ++i) { 405 for (int i = 0; in_range_for(m_binNames, i); ++i) {
556 if (m_binNames[i] != "") { 406 if (m_binNames[i] != "") {
557 out << indent + " "; 407 out << indent + " ";
558 out << QString("<bin number=\"%1\" name=\"%2\"/>\n") 408 out << QString("<bin number=\"%1\" name=\"%2\"/>\n")
559 .arg(i).arg(m_binNames[i]); 409 .arg(i).arg(m_binNames[i]);
560 } 410 }
561 } 411 }
562 412
563 for (int i = 0; i < (int)m_data.size(); ++i) { 413 for (int i = 0; in_range_for(m_data, i); ++i) {
414 Column c = getColumn(i);
564 out << indent + " "; 415 out << indent + " ";
565 out << QString("<row n=\"%1\">").arg(i); 416 out << QString("<row n=\"%1\">").arg(i);
566 for (int j = 0; j < (int)m_data.at(i).size(); ++j) { 417 for (int j = 0; in_range_for(c, j); ++j) {
567 if (j > 0) out << " "; 418 if (j > 0) out << " ";
568 out << m_data.at(i).at(j); 419 out << c.at(j);
569 } 420 }
570 out << QString("</row>\n"); 421 out << QString("</row>\n");
571 out.flush(); 422 out.flush();
572 } 423 }
573 424