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