Commit 5add000e21a4dde7964344ffb48727e5e30f78d4
Committed by
David Gräff
1 parent
84371ad2
Introduce new directory 'post' for post processing. Move all post processing funtionality there.
Showing
17 changed files
with
670 additions
and
416 deletions
openhantek/src/analyse/dataanalyzer.cpp deleted
| 1 | -// SPDX-License-Identifier: GPL-2.0+ | |
| 2 | - | |
| 3 | -#define _USE_MATH_DEFINES | |
| 4 | -#include <cmath> | |
| 5 | - | |
| 6 | -#include <QColor> | |
| 7 | -#include <QMutex> | |
| 8 | -#include <QTimer> | |
| 9 | - | |
| 10 | -#include <fftw3.h> | |
| 11 | - | |
| 12 | -#include "dataanalyzer.h" | |
| 13 | - | |
| 14 | -#include "glscope.h" | |
| 15 | -#include "settings.h" | |
| 16 | -#include "utils/printutils.h" | |
| 17 | - | |
| 18 | -std::unique_ptr<DataAnalyzerResult> DataAnalyzer::convertData(const DSOsamples *data, const DsoSettingsScope *scope, unsigned physicalChannels) { | |
| 19 | - QReadLocker locker(&data->lock); | |
| 20 | - | |
| 21 | - unsigned int channelCount = (unsigned int)scope->voltage.size(); | |
| 22 | - | |
| 23 | - std::unique_ptr<DataAnalyzerResult> result = | |
| 24 | - std::unique_ptr<DataAnalyzerResult>(new DataAnalyzerResult(channelCount)); | |
| 25 | - | |
| 26 | - for (ChannelID channel = 0; channel < channelCount; ++channel) { | |
| 27 | - DataChannel *const channelData = result->modifyData(channel); | |
| 28 | - | |
| 29 | - bool gotDataForChannel = channel < physicalChannels && channel < (unsigned int)data->data.size() && | |
| 30 | - !data->data.at(channel).empty(); | |
| 31 | - bool isMathChannel = channel >= physicalChannels && | |
| 32 | - (scope->voltage[channel].used || scope->spectrum[channel].used) && | |
| 33 | - result->channelCount() >= 2 && !result->data(0)->voltage.sample.empty() && | |
| 34 | - !result->data(1)->voltage.sample.empty(); | |
| 35 | - | |
| 36 | - if (!gotDataForChannel && !isMathChannel) { | |
| 37 | - // Clear unused channels | |
| 38 | - channelData->voltage.sample.clear(); | |
| 39 | - result->modifyData(physicalChannels)->voltage.interval = 0; | |
| 40 | - continue; | |
| 41 | - } | |
| 42 | - | |
| 43 | - // Set sampling interval | |
| 44 | - const double interval = 1.0 / data->samplerate; | |
| 45 | - if (interval != channelData->voltage.interval) { | |
| 46 | - channelData->voltage.interval = interval; | |
| 47 | - if (data->append) // Clear roll buffer if the samplerate changed | |
| 48 | - channelData->voltage.sample.clear(); | |
| 49 | - } | |
| 50 | - | |
| 51 | - unsigned int size; | |
| 52 | - if (channel < physicalChannels) { | |
| 53 | - size = (unsigned) data->data.at(channel).size(); | |
| 54 | - if (data->append) size += channelData->voltage.sample.size(); | |
| 55 | - result->challengeMaxSamples(size); | |
| 56 | - } else | |
| 57 | - size = result->getMaxSamples(); | |
| 58 | - | |
| 59 | - // Physical channels | |
| 60 | - if (channel < physicalChannels) { | |
| 61 | - // Copy the buffer of the oscilloscope into the sample buffer | |
| 62 | - if (data->append) | |
| 63 | - channelData->voltage.sample.insert(channelData->voltage.sample.end(), data->data.at(channel).begin(), | |
| 64 | - data->data.at(channel).end()); | |
| 65 | - else | |
| 66 | - channelData->voltage.sample = data->data.at(channel); | |
| 67 | - } else { // Math channel | |
| 68 | - // Resize the sample vector | |
| 69 | - channelData->voltage.sample.resize(size); | |
| 70 | - // Set sampling interval | |
| 71 | - result->modifyData(physicalChannels)->voltage.interval = result->data(0)->voltage.interval; | |
| 72 | - | |
| 73 | - // Resize the sample vector | |
| 74 | - result->modifyData(physicalChannels) | |
| 75 | - ->voltage.sample.resize( | |
| 76 | - qMin(result->data(0)->voltage.sample.size(), result->data(1)->voltage.sample.size())); | |
| 77 | - | |
| 78 | - // Calculate values and write them into the sample buffer | |
| 79 | - std::vector<double>::const_iterator ch1Iterator = result->data(0)->voltage.sample.begin(); | |
| 80 | - std::vector<double>::const_iterator ch2Iterator = result->data(1)->voltage.sample.begin(); | |
| 81 | - std::vector<double> &resultData = result->modifyData(physicalChannels)->voltage.sample; | |
| 82 | - for (std::vector<double>::iterator resultIterator = resultData.begin(); resultIterator != resultData.end(); | |
| 83 | - ++resultIterator) { | |
| 84 | - switch (scope->voltage[physicalChannels].math) { | |
| 85 | - case Dso::MathMode::ADD_CH1_CH2: | |
| 86 | - *resultIterator = *ch1Iterator + *ch2Iterator; | |
| 87 | - break; | |
| 88 | - case Dso::MathMode::SUB_CH2_FROM_CH1: | |
| 89 | - *resultIterator = *ch1Iterator - *ch2Iterator; | |
| 90 | - break; | |
| 91 | - case Dso::MathMode::SUB_CH1_FROM_CH2: | |
| 92 | - *resultIterator = *ch2Iterator - *ch1Iterator; | |
| 93 | - break; | |
| 94 | - } | |
| 95 | - ++ch1Iterator; | |
| 96 | - ++ch2Iterator; | |
| 97 | - } | |
| 98 | - } | |
| 99 | - } | |
| 100 | - return result; | |
| 101 | -} | |
| 102 | - | |
| 103 | -/// \brief Analyzes the data from the dso. | |
| 104 | -DataAnalyzer::~DataAnalyzer() { | |
| 105 | - if (window) fftw_free(window); | |
| 106 | -} | |
| 107 | - | |
| 108 | -void DataAnalyzer::applySettings(DsoSettings *settings) { this->settings = settings; } | |
| 109 | - | |
| 110 | -void DataAnalyzer::setSourceData(const DSOsamples *data) { sourceData = data; } | |
| 111 | - | |
| 112 | -std::unique_ptr<DataAnalyzerResult> DataAnalyzer::getNextResult() { return std::move(lastResult); } | |
| 113 | - | |
| 114 | -void DataAnalyzer::samplesAvailable() { | |
| 115 | - if (sourceData == nullptr) return; | |
| 116 | - std::unique_ptr<DataAnalyzerResult> result = convertData(sourceData, &settings->scope,settings->deviceSpecification->channels); | |
| 117 | - spectrumAnalysis(result.get(), lastWindow, lastRecordLength, window, &settings->scope); | |
| 118 | - lastResult.swap(result); | |
| 119 | - emit analyzed(); | |
| 120 | -} | |
| 121 | - | |
| 122 | -void DataAnalyzer::spectrumAnalysis(DataAnalyzerResult *result, Dso::WindowFunction &lastWindow, | |
| 123 | - unsigned int lastRecordLength, double *&lastWindowBuffer, | |
| 124 | - const DsoSettingsScope *scope) { | |
| 125 | - // Calculate frequencies, peak-to-peak voltages and spectrums | |
| 126 | - for (ChannelID channel = 0; channel < result->channelCount(); ++channel) { | |
| 127 | - DataChannel *const channelData = result->modifyData(channel); | |
| 128 | - | |
| 129 | - if (channelData->voltage.sample.empty()) { | |
| 130 | - // Clear unused channels | |
| 131 | - channelData->spectrum.interval = 0; | |
| 132 | - channelData->spectrum.sample.clear(); | |
| 133 | - continue; | |
| 134 | - } | |
| 135 | - | |
| 136 | - // Calculate new window | |
| 137 | - size_t sampleCount = channelData->voltage.sample.size(); | |
| 138 | - if (!lastWindowBuffer || lastWindow != scope->spectrumWindow || lastRecordLength != sampleCount) { | |
| 139 | - if (lastWindowBuffer) fftw_free(lastWindowBuffer); | |
| 140 | - lastWindowBuffer = fftw_alloc_real(sampleCount); | |
| 141 | - lastRecordLength = (unsigned)sampleCount; | |
| 142 | - | |
| 143 | - unsigned int windowEnd = lastRecordLength - 1; | |
| 144 | - lastWindow = scope->spectrumWindow; | |
| 145 | - | |
| 146 | - switch (scope->spectrumWindow) { | |
| 147 | - case Dso::WindowFunction::HAMMING: | |
| 148 | - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) | |
| 149 | - *(lastWindowBuffer + windowPosition) = 0.54 - 0.46 * cos(2.0 * M_PI * windowPosition / windowEnd); | |
| 150 | - break; | |
| 151 | - case Dso::WindowFunction::HANN: | |
| 152 | - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) | |
| 153 | - *(lastWindowBuffer + windowPosition) = 0.5 * (1.0 - cos(2.0 * M_PI * windowPosition / windowEnd)); | |
| 154 | - break; | |
| 155 | - case Dso::WindowFunction::COSINE: | |
| 156 | - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) | |
| 157 | - *(lastWindowBuffer + windowPosition) = sin(M_PI * windowPosition / windowEnd); | |
| 158 | - break; | |
| 159 | - case Dso::WindowFunction::LANCZOS: | |
| 160 | - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) { | |
| 161 | - double sincParameter = (2.0 * windowPosition / windowEnd - 1.0) * M_PI; | |
| 162 | - if (sincParameter == 0) | |
| 163 | - *(lastWindowBuffer + windowPosition) = 1; | |
| 164 | - else | |
| 165 | - *(lastWindowBuffer + windowPosition) = sin(sincParameter) / sincParameter; | |
| 166 | - } | |
| 167 | - break; | |
| 168 | - case Dso::WindowFunction::BARTLETT: | |
| 169 | - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) | |
| 170 | - *(lastWindowBuffer + windowPosition) = | |
| 171 | - 2.0 / windowEnd * (windowEnd / 2 - std::abs((double)(windowPosition - windowEnd / 2.0))); | |
| 172 | - break; | |
| 173 | - case Dso::WindowFunction::TRIANGULAR: | |
| 174 | - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) | |
| 175 | - *(lastWindowBuffer + windowPosition) = | |
| 176 | - 2.0 / lastRecordLength * | |
| 177 | - (lastRecordLength / 2 - std::abs((double)(windowPosition - windowEnd / 2.0))); | |
| 178 | - break; | |
| 179 | - case Dso::WindowFunction::GAUSS: { | |
| 180 | - double sigma = 0.4; | |
| 181 | - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) | |
| 182 | - *(lastWindowBuffer + windowPosition) = | |
| 183 | - exp(-0.5 * pow(((windowPosition - windowEnd / 2) / (sigma * windowEnd / 2)), 2)); | |
| 184 | - } break; | |
| 185 | - case Dso::WindowFunction::BARTLETTHANN: | |
| 186 | - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) | |
| 187 | - *(lastWindowBuffer + windowPosition) = 0.62 - | |
| 188 | - 0.48 * std::abs((double)(windowPosition / windowEnd - 0.5)) - | |
| 189 | - 0.38 * cos(2.0 * M_PI * windowPosition / windowEnd); | |
| 190 | - break; | |
| 191 | - case Dso::WindowFunction::BLACKMAN: { | |
| 192 | - double alpha = 0.16; | |
| 193 | - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) | |
| 194 | - *(lastWindowBuffer + windowPosition) = (1 - alpha) / 2 - | |
| 195 | - 0.5 * cos(2.0 * M_PI * windowPosition / windowEnd) + | |
| 196 | - alpha / 2 * cos(4.0 * M_PI * windowPosition / windowEnd); | |
| 197 | - } break; | |
| 198 | - // case Dso::WindowFunction::WINDOW_KAISER: | |
| 199 | - // TODO WINDOW_KAISER | |
| 200 | - // double alpha = 3.0; | |
| 201 | - // for(unsigned int windowPosition = 0; windowPosition < | |
| 202 | - // lastRecordLength; ++windowPosition) | |
| 203 | - //*(window + windowPosition) = ; | |
| 204 | - // break; | |
| 205 | - case Dso::WindowFunction::NUTTALL: | |
| 206 | - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) | |
| 207 | - *(lastWindowBuffer + windowPosition) = 0.355768 - | |
| 208 | - 0.487396 * cos(2 * M_PI * windowPosition / windowEnd) + | |
| 209 | - 0.144232 * cos(4 * M_PI * windowPosition / windowEnd) - | |
| 210 | - 0.012604 * cos(6 * M_PI * windowPosition / windowEnd); | |
| 211 | - break; | |
| 212 | - case Dso::WindowFunction::BLACKMANHARRIS: | |
| 213 | - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) | |
| 214 | - *(lastWindowBuffer + windowPosition) = 0.35875 - | |
| 215 | - 0.48829 * cos(2 * M_PI * windowPosition / windowEnd) + | |
| 216 | - 0.14128 * cos(4 * M_PI * windowPosition / windowEnd) - | |
| 217 | - 0.01168 * cos(6 * M_PI * windowPosition / windowEnd); | |
| 218 | - break; | |
| 219 | - case Dso::WindowFunction::BLACKMANNUTTALL: | |
| 220 | - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) | |
| 221 | - *(lastWindowBuffer + windowPosition) = 0.3635819 - | |
| 222 | - 0.4891775 * cos(2 * M_PI * windowPosition / windowEnd) + | |
| 223 | - 0.1365995 * cos(4 * M_PI * windowPosition / windowEnd) - | |
| 224 | - 0.0106411 * cos(6 * M_PI * windowPosition / windowEnd); | |
| 225 | - break; | |
| 226 | - case Dso::WindowFunction::FLATTOP: | |
| 227 | - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) | |
| 228 | - *(lastWindowBuffer + windowPosition) = 1.0 - 1.93 * cos(2 * M_PI * windowPosition / windowEnd) + | |
| 229 | - 1.29 * cos(4 * M_PI * windowPosition / windowEnd) - | |
| 230 | - 0.388 * cos(6 * M_PI * windowPosition / windowEnd) + | |
| 231 | - 0.032 * cos(8 * M_PI * windowPosition / windowEnd); | |
| 232 | - break; | |
| 233 | - default: // Dso::WINDOW_RECTANGULAR | |
| 234 | - for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) | |
| 235 | - *(lastWindowBuffer + windowPosition) = 1.0; | |
| 236 | - } | |
| 237 | - } | |
| 238 | - | |
| 239 | - // Set sampling interval | |
| 240 | - channelData->spectrum.interval = 1.0 / channelData->voltage.interval / sampleCount; | |
| 241 | - | |
| 242 | - // Number of real/complex samples | |
| 243 | - unsigned int dftLength = sampleCount / 2; | |
| 244 | - | |
| 245 | - // Reallocate memory for samples if the sample count has changed | |
| 246 | - channelData->spectrum.sample.resize(sampleCount); | |
| 247 | - | |
| 248 | - // Create sample buffer and apply window | |
| 249 | - std::unique_ptr<double[]> windowedValues = std::unique_ptr<double[]>(new double[sampleCount]); | |
| 250 | - | |
| 251 | - for (unsigned int position = 0; position < sampleCount; ++position) | |
| 252 | - windowedValues[position] = lastWindowBuffer[position] * channelData->voltage.sample[position]; | |
| 253 | - | |
| 254 | - { | |
| 255 | - // Do discrete real to half-complex transformation | |
| 256 | - /// \todo Check if record length is multiple of 2 | |
| 257 | - /// \todo Reuse plan and use FFTW_MEASURE to get fastest algorithm | |
| 258 | - fftw_plan fftPlan = fftw_plan_r2r_1d(sampleCount, windowedValues.get(), | |
| 259 | - &channelData->spectrum.sample.front(), FFTW_R2HC, FFTW_ESTIMATE); | |
| 260 | - fftw_execute(fftPlan); | |
| 261 | - fftw_destroy_plan(fftPlan); | |
| 262 | - } | |
| 263 | - | |
| 264 | - // Do an autocorrelation to get the frequency of the signal | |
| 265 | - std::unique_ptr<double[]> conjugateComplex = std::move(windowedValues); | |
| 266 | - | |
| 267 | - // Real values | |
| 268 | - unsigned int position; | |
| 269 | - double correctionFactor = 1.0 / dftLength / dftLength; | |
| 270 | - conjugateComplex[0] = (channelData->spectrum.sample[0] * channelData->spectrum.sample[0]) * correctionFactor; | |
| 271 | - for (position = 1; position < dftLength; ++position) | |
| 272 | - conjugateComplex[position] = | |
| 273 | - (channelData->spectrum.sample[position] * channelData->spectrum.sample[position] + | |
| 274 | - channelData->spectrum.sample[sampleCount - position] * | |
| 275 | - channelData->spectrum.sample[sampleCount - position]) * | |
| 276 | - correctionFactor; | |
| 277 | - // Complex values, all zero for autocorrelation | |
| 278 | - conjugateComplex[dftLength] = | |
| 279 | - (channelData->spectrum.sample[dftLength] * channelData->spectrum.sample[dftLength]) * correctionFactor; | |
| 280 | - for (++position; position < sampleCount; ++position) conjugateComplex[position] = 0; | |
| 281 | - | |
| 282 | - // Do half-complex to real inverse transformation | |
| 283 | - std::unique_ptr<double[]> correlation = std::unique_ptr<double[]>(new double[sampleCount]); | |
| 284 | - fftw_plan fftPlan = | |
| 285 | - fftw_plan_r2r_1d(sampleCount, conjugateComplex.get(), correlation.get(), FFTW_HC2R, FFTW_ESTIMATE); | |
| 286 | - fftw_execute(fftPlan); | |
| 287 | - fftw_destroy_plan(fftPlan); | |
| 288 | - | |
| 289 | - // Calculate peak-to-peak voltage | |
| 290 | - double minimalVoltage, maximalVoltage; | |
| 291 | - minimalVoltage = maximalVoltage = channelData->voltage.sample[0]; | |
| 292 | - | |
| 293 | - for (unsigned int position = 1; position < sampleCount; ++position) { | |
| 294 | - if (channelData->voltage.sample[position] < minimalVoltage) | |
| 295 | - minimalVoltage = channelData->voltage.sample[position]; | |
| 296 | - else if (channelData->voltage.sample[position] > maximalVoltage) | |
| 297 | - maximalVoltage = channelData->voltage.sample[position]; | |
| 298 | - } | |
| 299 | - | |
| 300 | - channelData->amplitude = maximalVoltage - minimalVoltage; | |
| 301 | - | |
| 302 | - // Get the frequency from the correlation results | |
| 303 | - double minimumCorrelation = correlation[0]; | |
| 304 | - double peakCorrelation = 0; | |
| 305 | - unsigned int peakPosition = 0; | |
| 306 | - | |
| 307 | - for (unsigned int position = 1; position < sampleCount / 2; ++position) { | |
| 308 | - if (correlation[position] > peakCorrelation && correlation[position] > minimumCorrelation * 2) { | |
| 309 | - peakCorrelation = correlation[position]; | |
| 310 | - peakPosition = position; | |
| 311 | - } else if (correlation[position] < minimumCorrelation) | |
| 312 | - minimumCorrelation = correlation[position]; | |
| 313 | - } | |
| 314 | - correlation.reset(nullptr); | |
| 315 | - | |
| 316 | - // Calculate the frequency in Hz | |
| 317 | - if (peakPosition) | |
| 318 | - channelData->frequency = 1.0 / (channelData->voltage.interval * peakPosition); | |
| 319 | - else | |
| 320 | - channelData->frequency = 0; | |
| 321 | - | |
| 322 | - // Finally calculate the real spectrum if we want it | |
| 323 | - if (scope->spectrum[channel].used) { | |
| 324 | - // Convert values into dB (Relative to the reference level) | |
| 325 | - double offset = 60 - scope->spectrumReference - 20 * log10(dftLength); | |
| 326 | - double offsetLimit = scope->spectrumLimit - scope->spectrumReference; | |
| 327 | - for (std::vector<double>::iterator spectrumIterator = channelData->spectrum.sample.begin(); | |
| 328 | - spectrumIterator != channelData->spectrum.sample.end(); ++spectrumIterator) { | |
| 329 | - double value = 20 * log10(fabs(*spectrumIterator)) + offset; | |
| 330 | - | |
| 331 | - // Check if this value has to be limited | |
| 332 | - if (offsetLimit > value) value = offsetLimit; | |
| 333 | - | |
| 334 | - *spectrumIterator = value; | |
| 335 | - } | |
| 336 | - } | |
| 337 | - } | |
| 338 | -} |
openhantek/src/analyse/dataanalyzerresult.cpp deleted
| 1 | -// SPDX-License-Identifier: GPL-2.0+ | |
| 2 | - | |
| 3 | -#include "dataanalyzerresult.h" | |
| 4 | -#include <QDebug> | |
| 5 | -#include <stdexcept> | |
| 6 | - | |
| 7 | -DataAnalyzerResult::DataAnalyzerResult(unsigned int channelCount) { analyzedData.resize(channelCount); } | |
| 8 | - | |
| 9 | -/// \brief Returns the analyzed data. | |
| 10 | -/// \param channel Channel, whose data should be returned. | |
| 11 | -/// \return Analyzed data as AnalyzedData struct. | |
| 12 | -const DataChannel *DataAnalyzerResult::data(ChannelID channel) const { | |
| 13 | - if (channel >= this->analyzedData.size()) return 0; | |
| 14 | - | |
| 15 | - return &this->analyzedData[(size_t)channel]; | |
| 16 | -} | |
| 17 | - | |
| 18 | -DataChannel *DataAnalyzerResult::modifyData(ChannelID channel) { | |
| 19 | - if (channel >= this->analyzedData.size()) | |
| 20 | - throw new std::runtime_error("If you modfiy the DataAnalyzerResult, you " | |
| 21 | - "need to set the channels first!"); | |
| 22 | - | |
| 23 | - return &this->analyzedData[(size_t)channel]; | |
| 24 | -} | |
| 25 | - | |
| 26 | -/// \brief Returns the sample count of the analyzed data. | |
| 27 | -/// \return The maximum sample count of the last analyzed data. | |
| 28 | -unsigned int DataAnalyzerResult::sampleCount() const { return this->maxSamples; } | |
| 29 | - | |
| 30 | -unsigned int DataAnalyzerResult::channelCount() const { return analyzedData.size(); } | |
| 31 | - | |
| 32 | -void DataAnalyzerResult::challengeMaxSamples(unsigned int newMaxSamples) { | |
| 33 | - if (newMaxSamples > this->maxSamples) this->maxSamples = newMaxSamples; | |
| 34 | -} | |
| 35 | - | |
| 36 | -unsigned int DataAnalyzerResult::getMaxSamples() const { return maxSamples; } |
openhantek/src/analyse/enums.cpp renamed to openhantek/src/post/enums.cpp
openhantek/src/analyse/enums.h renamed to openhantek/src/post/enums.h
openhantek/src/post/graphgenerator.cpp
0 → 100644
| 1 | +// SPDX-License-Identifier: GPL-2.0+ | |
| 2 | + | |
| 3 | +#include <QDebug> | |
| 4 | +#include <QMutex> | |
| 5 | +#include <exception> | |
| 6 | + | |
| 7 | +#include "post/graphgenerator.h" | |
| 8 | +#include "post/ppresult.h" | |
| 9 | +#include "post/softwaretrigger.h" | |
| 10 | +#include "hantekdso/controlspecification.h" | |
| 11 | +#include "scopesettings.h" | |
| 12 | +#include "utils/printutils.h" | |
| 13 | +#include "viewconstants.h" | |
| 14 | + | |
| 15 | +static const SampleValues &useSpecSamplesOf(ChannelID channel, const PPresult *result, | |
| 16 | + const DsoSettingsScope *scope) { | |
| 17 | + static SampleValues emptyDefault; | |
| 18 | + if (!scope->spectrum[channel].used || !result->data(channel)) return emptyDefault; | |
| 19 | + return result->data(channel)->spectrum; | |
| 20 | +} | |
| 21 | + | |
| 22 | +static const SampleValues &useVoltSamplesOf(ChannelID channel, const PPresult *result, | |
| 23 | + const DsoSettingsScope *scope) { | |
| 24 | + static SampleValues emptyDefault; | |
| 25 | + if (!scope->voltage[channel].used || !result->data(channel)) return emptyDefault; | |
| 26 | + return result->data(channel)->voltage; | |
| 27 | +} | |
| 28 | + | |
| 29 | +GraphGenerator::GraphGenerator(const DsoSettingsScope *scope, bool isSoftwareTriggerDevice) | |
| 30 | + : scope(scope), isSoftwareTriggerDevice(isSoftwareTriggerDevice) {} | |
| 31 | + | |
| 32 | +bool GraphGenerator::isReady() const { return ready; } | |
| 33 | + | |
| 34 | +void GraphGenerator::generateGraphsTYvoltage(PPresult *result) { | |
| 35 | + unsigned preTrigSamples = 0; | |
| 36 | + unsigned postTrigSamples = 0; | |
| 37 | + unsigned swTriggerStart = 0; | |
| 38 | + | |
| 39 | + // check trigger point for software trigger | |
| 40 | + if (isSoftwareTriggerDevice && scope->trigger.source < result->channelCount()) | |
| 41 | + std::tie(preTrigSamples, postTrigSamples, swTriggerStart) = SoftwareTrigger::compute(result, scope); | |
| 42 | + result->softwareTriggerTriggered = postTrigSamples > preTrigSamples; | |
| 43 | + | |
| 44 | + result->vaChannelVoltage.resize(scope->voltage.size()); | |
| 45 | + for (ChannelID channel = 0; channel < scope->voltage.size(); ++channel) { | |
| 46 | + ChannelGraph &target = result->vaChannelVoltage[channel]; | |
| 47 | + const SampleValues &samples = useVoltSamplesOf(channel, result, scope); | |
| 48 | + | |
| 49 | + // Check if this channel is used and available at the data analyzer | |
| 50 | + if (samples.sample.empty()) { | |
| 51 | + // Delete all vector arrays | |
| 52 | + target.clear(); | |
| 53 | + continue; | |
| 54 | + } | |
| 55 | + // Check if the sample count has changed | |
| 56 | + size_t sampleCount = samples.sample.size(); | |
| 57 | + if (sampleCount > 500000) { | |
| 58 | + qWarning() << "Sample count too high!"; | |
| 59 | + throw new std::runtime_error("Sample count too high!"); | |
| 60 | + } | |
| 61 | + sampleCount -= (swTriggerStart - preTrigSamples); | |
| 62 | + size_t neededSize = sampleCount * 2; | |
| 63 | + | |
| 64 | + // Set size directly to avoid reallocations | |
| 65 | + target.resize(neededSize); | |
| 66 | + | |
| 67 | + // What's the horizontal distance between sampling points? | |
| 68 | + float horizontalFactor = (float)(samples.interval / scope->horizontal.timebase); | |
| 69 | + | |
| 70 | + // Fill vector array | |
| 71 | + std::vector<double>::const_iterator dataIterator = samples.sample.begin(); | |
| 72 | + const float gain = (float)scope->gain(channel); | |
| 73 | + const float offset = (float)scope->voltage[channel].offset; | |
| 74 | + const float invert = scope->voltage[channel].inverted ? -1.0f : 1.0f; | |
| 75 | + | |
| 76 | + std::advance(dataIterator, swTriggerStart - preTrigSamples); | |
| 77 | + | |
| 78 | + for (unsigned int position = 0; position < sampleCount; ++position) { | |
| 79 | + target.push_back(QVector3D(position * horizontalFactor - DIVS_TIME / 2, | |
| 80 | + (float)*(dataIterator++) / gain * invert + offset, 0.0)); | |
| 81 | + } | |
| 82 | + } | |
| 83 | +} | |
| 84 | + | |
| 85 | +void GraphGenerator::generateGraphsTYspectrum(PPresult *result) { | |
| 86 | + ready = true; | |
| 87 | + result->vaChannelSpectrum.resize(scope->spectrum.size()); | |
| 88 | + for (ChannelID channel = 0; channel < scope->voltage.size(); ++channel) { | |
| 89 | + ChannelGraph &target = result->vaChannelSpectrum[channel]; | |
| 90 | + const SampleValues &samples = useSpecSamplesOf(channel, result, scope); | |
| 91 | + | |
| 92 | + // Check if this channel is used and available at the data analyzer | |
| 93 | + if (samples.sample.empty()) { | |
| 94 | + // Delete all vector arrays | |
| 95 | + target.clear(); | |
| 96 | + continue; | |
| 97 | + } | |
| 98 | + // Check if the sample count has changed | |
| 99 | + size_t sampleCount = samples.sample.size(); | |
| 100 | + if (sampleCount > 500000) { | |
| 101 | + qWarning() << "Sample count too high!"; | |
| 102 | + throw new std::runtime_error("Sample count too high!"); | |
| 103 | + } | |
| 104 | + size_t neededSize = sampleCount * 2; | |
| 105 | + | |
| 106 | + // Set size directly to avoid reallocations | |
| 107 | + target.resize(neededSize); | |
| 108 | + | |
| 109 | + // What's the horizontal distance between sampling points? | |
| 110 | + float horizontalFactor = (float)(samples.interval / scope->horizontal.frequencybase); | |
| 111 | + | |
| 112 | + // Fill vector array | |
| 113 | + std::vector<double>::const_iterator dataIterator = samples.sample.begin(); | |
| 114 | + const float magnitude = (float)scope->spectrum[channel].magnitude; | |
| 115 | + const float offset = (float)scope->spectrum[channel].offset; | |
| 116 | + | |
| 117 | + for (unsigned int position = 0; position < sampleCount; ++position) { | |
| 118 | + target.push_back(QVector3D(position * horizontalFactor - DIVS_TIME / 2, | |
| 119 | + (float)*(dataIterator++) / magnitude + offset, 0.0)); | |
| 120 | + } | |
| 121 | + } | |
| 122 | +} | |
| 123 | + | |
| 124 | +void GraphGenerator::process(PPresult *data) { | |
| 125 | + if (scope->horizontal.format == Dso::GraphFormat::TY) { | |
| 126 | + ready = true; | |
| 127 | + generateGraphsTYspectrum(data); | |
| 128 | + generateGraphsTYvoltage(data); | |
| 129 | + } else | |
| 130 | + generateGraphsXY(data, scope); | |
| 131 | +} | |
| 132 | + | |
| 133 | +void GraphGenerator::generateGraphsXY(PPresult *result, const DsoSettingsScope *scope) { | |
| 134 | + result->vaChannelVoltage.resize(scope->voltage.size()); | |
| 135 | + | |
| 136 | + // Delete all spectrum graphs | |
| 137 | + for (ChannelGraph &data : result->vaChannelSpectrum) data.clear(); | |
| 138 | + | |
| 139 | + // Generate voltage graphs for pairs of channels | |
| 140 | + for (ChannelID channel = 0; channel < scope->voltage.size(); channel += 2) { | |
| 141 | + // We need pairs of channels. | |
| 142 | + if (channel + 1 == scope->voltage.size()) { | |
| 143 | + result->vaChannelVoltage[channel].clear(); | |
| 144 | + continue; | |
| 145 | + } | |
| 146 | + | |
| 147 | + const ChannelID xChannel = channel; | |
| 148 | + const ChannelID yChannel = channel + 1; | |
| 149 | + | |
| 150 | + const SampleValues &xSamples = useVoltSamplesOf(xChannel, result, scope); | |
| 151 | + const SampleValues &ySamples = useVoltSamplesOf(yChannel, result, scope); | |
| 152 | + | |
| 153 | + // The channels need to be active | |
| 154 | + if (!xSamples.sample.size() || !ySamples.sample.size()) { | |
| 155 | + result->vaChannelVoltage[channel].clear(); | |
| 156 | + result->vaChannelVoltage[channel + 1].clear(); | |
| 157 | + continue; | |
| 158 | + } | |
| 159 | + | |
| 160 | + // Check if the sample count has changed | |
| 161 | + const size_t sampleCount = std::min(xSamples.sample.size(), ySamples.sample.size()); | |
| 162 | + ChannelGraph &drawLines = result->vaChannelVoltage[channel]; | |
| 163 | + drawLines.resize(sampleCount * 2); | |
| 164 | + | |
| 165 | + // Fill vector array | |
| 166 | + std::vector<double>::const_iterator xIterator = xSamples.sample.begin(); | |
| 167 | + std::vector<double>::const_iterator yIterator = ySamples.sample.begin(); | |
| 168 | + const double xGain = scope->gain(xChannel); | |
| 169 | + const double yGain = scope->gain(yChannel); | |
| 170 | + const double xOffset = scope->voltage[xChannel].offset; | |
| 171 | + const double yOffset = scope->voltage[yChannel].offset; | |
| 172 | + const double xInvert = scope->voltage[xChannel].inverted ? -1.0 : 1.0; | |
| 173 | + const double yInvert = scope->voltage[yChannel].inverted ? -1.0 : 1.0; | |
| 174 | + | |
| 175 | + for (unsigned int position = 0; position < sampleCount; ++position) { | |
| 176 | + drawLines.push_back(QVector3D((float)(*(xIterator++) / xGain * xInvert + xOffset), | |
| 177 | + (float)(*(yIterator++) / yGain * yInvert + yOffset), 0.0)); | |
| 178 | + } | |
| 179 | + } | |
| 180 | +} | ... | ... |
openhantek/src/post/graphgenerator.h
0 → 100644
| 1 | +// SPDX-License-Identifier: GPL-2.0+ | |
| 2 | + | |
| 3 | +#pragma once | |
| 4 | + | |
| 5 | +#include <deque> | |
| 6 | + | |
| 7 | +#include <QObject> | |
| 8 | +#include <QVector3D> | |
| 9 | + | |
| 10 | +#include "hantekdso/enums.h" | |
| 11 | +#include "hantekprotocol/types.h" | |
| 12 | +#include "processor.h" | |
| 13 | + | |
| 14 | +struct DsoSettingsScope; | |
| 15 | +class PPresult; | |
| 16 | +namespace Dso { | |
| 17 | +struct ControlSpecification; | |
| 18 | +} | |
| 19 | + | |
| 20 | +/// \brief Generates ready to be used vertex arrays | |
| 21 | +class GraphGenerator : public QObject, public Processor { | |
| 22 | + Q_OBJECT | |
| 23 | + | |
| 24 | + public: | |
| 25 | + GraphGenerator(const DsoSettingsScope *scope, bool isSoftwareTriggerDevice); | |
| 26 | + void generateGraphsXY(PPresult *result, const DsoSettingsScope *scope); | |
| 27 | + | |
| 28 | + bool isReady() const; | |
| 29 | + | |
| 30 | + private: | |
| 31 | + void generateGraphsTYvoltage(PPresult *result); | |
| 32 | + void generateGraphsTYspectrum(PPresult *result); | |
| 33 | + | |
| 34 | + private: | |
| 35 | + bool ready = false; | |
| 36 | + const DsoSettingsScope *scope; | |
| 37 | + const bool isSoftwareTriggerDevice; | |
| 38 | + | |
| 39 | + // Processor interface | |
| 40 | + private: | |
| 41 | + virtual void process(PPresult *) override; | |
| 42 | +}; | ... | ... |
openhantek/src/post/mathchannelgenerator.cpp
0 → 100644
| 1 | +#include "mathchannelgenerator.h" | |
| 2 | +#include "scopesettings.h" | |
| 3 | + | |
| 4 | +MathChannelGenerator::MathChannelGenerator(const DsoSettingsScope *scope, unsigned physicalChannels) | |
| 5 | + : physicalChannels(physicalChannels), scope(scope) {} | |
| 6 | + | |
| 7 | +MathChannelGenerator::~MathChannelGenerator() {} | |
| 8 | + | |
| 9 | +void MathChannelGenerator::process(PPresult *result) { | |
| 10 | + bool channelsHaveData = !result->data(0)->voltage.sample.empty() && !result->data(1)->voltage.sample.empty(); | |
| 11 | + if (!channelsHaveData) return; | |
| 12 | + | |
| 13 | + for (ChannelID channel = physicalChannels; channel < result->channelCount(); ++channel) { | |
| 14 | + DataChannel *const channelData = result->modifyData(channel); | |
| 15 | + | |
| 16 | + // Math channel enabled? | |
| 17 | + if (!scope->voltage[channel].used && !scope->spectrum[channel].used) continue; | |
| 18 | + | |
| 19 | + // Set sampling interval | |
| 20 | + channelData->voltage.interval = result->data(0)->voltage.interval; | |
| 21 | + | |
| 22 | + // Resize the sample vector | |
| 23 | + std::vector<double> &resultData = channelData->voltage.sample; | |
| 24 | + resultData.resize(std::min(result->data(0)->voltage.sample.size(), result->data(1)->voltage.sample.size())); | |
| 25 | + | |
| 26 | + // Calculate values and write them into the sample buffer | |
| 27 | + std::vector<double>::const_iterator ch1Iterator = result->data(0)->voltage.sample.begin(); | |
| 28 | + std::vector<double>::const_iterator ch2Iterator = result->data(1)->voltage.sample.begin(); | |
| 29 | + for (std::vector<double>::iterator it = resultData.begin(); it != resultData.end(); ++it) { | |
| 30 | + switch (scope->voltage[physicalChannels].math) { | |
| 31 | + case Dso::MathMode::ADD_CH1_CH2: | |
| 32 | + *it = *ch1Iterator + *ch2Iterator; | |
| 33 | + break; | |
| 34 | + case Dso::MathMode::SUB_CH2_FROM_CH1: | |
| 35 | + *it = *ch1Iterator - *ch2Iterator; | |
| 36 | + break; | |
| 37 | + case Dso::MathMode::SUB_CH1_FROM_CH2: | |
| 38 | + *it = *ch2Iterator - *ch1Iterator; | |
| 39 | + break; | |
| 40 | + } | |
| 41 | + ++ch1Iterator; | |
| 42 | + ++ch2Iterator; | |
| 43 | + } | |
| 44 | + } | |
| 45 | +} | ... | ... |
openhantek/src/post/mathchannelgenerator.h
0 → 100644
| 1 | +// SPDX-License-Identifier: GPL-2.0+ | |
| 2 | + | |
| 3 | +#pragma once | |
| 4 | + | |
| 5 | +#include "processor.h" | |
| 6 | + | |
| 7 | +struct DsoSettingsScope; | |
| 8 | +class PPresult; | |
| 9 | + | |
| 10 | +class MathChannelGenerator : public Processor | |
| 11 | +{ | |
| 12 | +public: | |
| 13 | + MathChannelGenerator(const DsoSettingsScope *scope, unsigned physicalChannels); | |
| 14 | + virtual ~MathChannelGenerator(); | |
| 15 | + virtual void process(PPresult *) override; | |
| 16 | +private: | |
| 17 | + const unsigned physicalChannels; | |
| 18 | + const DsoSettingsScope *scope; | |
| 19 | +}; | ... | ... |
openhantek/src/post/postprocessing.cpp
0 → 100644
| 1 | +#include "postprocessing.h" | |
| 2 | + | |
| 3 | +PostProcessing::PostProcessing(unsigned channelCount) : channelCount(channelCount) { | |
| 4 | + qRegisterMetaType<std::shared_ptr<PPresult>>(); | |
| 5 | +} | |
| 6 | + | |
| 7 | +void PostProcessing::registerProcessor(Processor *processor) { processors.push_back(processor); } | |
| 8 | + | |
| 9 | +void PostProcessing::convertData(const DSOsamples *source, PPresult *destination) { | |
| 10 | + QReadLocker locker(&source->lock); | |
| 11 | + | |
| 12 | + for (ChannelID channel = 0; channel < source->data.size(); ++channel) { | |
| 13 | + const std::vector<double> &rawChannelData = source->data.at(channel); | |
| 14 | + | |
| 15 | + if (rawChannelData.empty()) { continue; } | |
| 16 | + | |
| 17 | + DataChannel *const channelData = destination->modifyData(channel); | |
| 18 | + channelData->voltage.interval = 1.0 / source->samplerate; | |
| 19 | + channelData->voltage.sample = rawChannelData; | |
| 20 | + } | |
| 21 | +} | |
| 22 | + | |
| 23 | +void PostProcessing::input(const DSOsamples *data) { | |
| 24 | + currentData.reset(new PPresult(channelCount)); | |
| 25 | + convertData(data, currentData.get()); | |
| 26 | + for (Processor *p : processors) p->process(currentData.get()); | |
| 27 | + std::shared_ptr<PPresult> res = std::move(currentData); | |
| 28 | + emit processingFinished(res); | |
| 29 | +} | ... | ... |
openhantek/src/post/postprocessing.h
0 → 100644
| 1 | +// SPDX-License-Identifier: GPL-2.0+ | |
| 2 | + | |
| 3 | +#pragma once | |
| 4 | + | |
| 5 | +#include "dsosamples.h" | |
| 6 | +#include "processor.h" | |
| 7 | + | |
| 8 | +#include <memory> | |
| 9 | +#include <vector> | |
| 10 | + | |
| 11 | +#include <QObject> | |
| 12 | + | |
| 13 | +struct DsoSettingsScope; | |
| 14 | + | |
| 15 | +/** | |
| 16 | + * Manages all post processing processors. Register another processor with `registerProcessor(p)`. | |
| 17 | + * All processors, in the order of insertion, will process the input data, given by `input(data)`. | |
| 18 | + * The final result will be made available via the `processingFinished` signal. | |
| 19 | + */ | |
| 20 | +class PostProcessing : public QObject { | |
| 21 | + Q_OBJECT | |
| 22 | + public: | |
| 23 | + PostProcessing(unsigned channelCount); | |
| 24 | + /** | |
| 25 | + * Adds a new processor that is called when a new input arrived. The order of the processors is | |
| 26 | + * imporant. The first added processor will be called first. This class does not take ownership | |
| 27 | + * of the processors. | |
| 28 | + * @param processor | |
| 29 | + */ | |
| 30 | + void registerProcessor(Processor *processor); | |
| 31 | + | |
| 32 | + | |
| 33 | + private: | |
| 34 | + /// A new `PPresult` is created for each new input. We need to know the channel size. | |
| 35 | + const unsigned channelCount; | |
| 36 | + /// The list of processors. Processors are not memory managed by this class. | |
| 37 | + std::vector<Processor *> processors; | |
| 38 | + /// | |
| 39 | + std::unique_ptr<PPresult> currentData; | |
| 40 | + static void convertData(const DSOsamples *source, PPresult *destination); | |
| 41 | + public slots: | |
| 42 | + /** | |
| 43 | + * Start processing new data. The actual data may be processed in another thread if you have moved | |
| 44 | + * this class object into another thread. | |
| 45 | + * @param data | |
| 46 | + */ | |
| 47 | + void input(const DSOsamples *data); | |
| 48 | +signals: | |
| 49 | + void processingFinished(std::shared_ptr<PPresult> result); | |
| 50 | +}; | |
| 51 | + | |
| 52 | +Q_DECLARE_METATYPE(std::shared_ptr<PPresult>) | ... | ... |
openhantek/src/post/ppresult.cpp
0 → 100644
| 1 | +// SPDX-License-Identifier: GPL-2.0+ | |
| 2 | + | |
| 3 | +#include "ppresult.h" | |
| 4 | +#include <QDebug> | |
| 5 | +#include <stdexcept> | |
| 6 | + | |
| 7 | +PPresult::PPresult(unsigned int channelCount) { analyzedData.resize(channelCount); } | |
| 8 | + | |
| 9 | +const DataChannel *PPresult::data(ChannelID channel) const { | |
| 10 | + if (channel >= this->analyzedData.size()) return 0; | |
| 11 | + | |
| 12 | + return &this->analyzedData[(size_t)channel]; | |
| 13 | +} | |
| 14 | + | |
| 15 | +DataChannel *PPresult::modifyData(ChannelID channel) { return &this->analyzedData[(size_t)channel]; } | |
| 16 | + | |
| 17 | +unsigned int PPresult::sampleCount() const { return (unsigned)analyzedData[0].voltage.sample.size(); } | |
| 18 | + | |
| 19 | +unsigned int PPresult::channelCount() const { return (unsigned)analyzedData.size(); } | |
| 20 | + | |
| 21 | +double DataChannel::computeAmplitude() const { | |
| 22 | + double minimalVoltage, maximalVoltage; | |
| 23 | + minimalVoltage = maximalVoltage = voltage.sample[0]; | |
| 24 | + | |
| 25 | + for (unsigned int position = 1; position < voltage.sample.size(); ++position) { | |
| 26 | + if (voltage.sample[position] < minimalVoltage) | |
| 27 | + minimalVoltage = voltage.sample[position]; | |
| 28 | + else if (voltage.sample[position] > maximalVoltage) | |
| 29 | + maximalVoltage = voltage.sample[position]; | |
| 30 | + } | |
| 31 | + | |
| 32 | + return maximalVoltage - minimalVoltage; | |
| 33 | +} | ... | ... |
openhantek/src/analyse/dataanalyzerresult.h renamed to openhantek/src/post/ppresult.h
| ... | ... | @@ -2,8 +2,11 @@ |
| 2 | 2 | |
| 3 | 3 | #pragma once |
| 4 | 4 | |
| 5 | +#include <QVector3D> | |
| 6 | +#include <QReadWriteLock> | |
| 7 | + | |
| 5 | 8 | #include <vector> |
| 6 | -#include "hantekprotocol/definitions.h" | |
| 9 | +#include "hantekprotocol/types.h" | |
| 7 | 10 | |
| 8 | 11 | /// \brief Struct for a array of sample values. |
| 9 | 12 | struct SampleValues { |
| ... | ... | @@ -15,28 +18,34 @@ struct SampleValues { |
| 15 | 18 | struct DataChannel { |
| 16 | 19 | SampleValues voltage; ///< The time-domain voltage levels (V) |
| 17 | 20 | SampleValues spectrum; ///< The frequency-domain power levels (dB) |
| 18 | - double amplitude = 0.0; ///< The amplitude of the signal | |
| 21 | + | |
| 19 | 22 | double frequency = 0.0; ///< The frequency of the signal |
| 23 | + // Calculate peak-to-peak voltage | |
| 24 | + double computeAmplitude() const; | |
| 20 | 25 | }; |
| 21 | 26 | |
| 22 | -/// A result from the { @link DataAnalyzer } class. | |
| 23 | -class DataAnalyzerResult { | |
| 27 | +typedef std::vector<QVector3D> ChannelGraph; | |
| 28 | +typedef std::vector<ChannelGraph> ChannelsGraphs; | |
| 29 | + | |
| 30 | +/// Post processing results | |
| 31 | +class PPresult { | |
| 24 | 32 | public: |
| 25 | - DataAnalyzerResult(unsigned int channelCount); | |
| 33 | + PPresult(unsigned int channelCount); | |
| 34 | + | |
| 35 | + /// \brief Returns the analyzed data. | |
| 36 | + /// \param channel Channel, whose data should be returned. | |
| 26 | 37 | const DataChannel *data(ChannelID channel) const; |
| 38 | + /// \brief Returns the analyzed data. The data structure can be modifed. | |
| 39 | + /// \param channel Channel, whose data should be returned. | |
| 27 | 40 | DataChannel *modifyData(ChannelID channel); |
| 41 | + /// \return The maximum sample count of the last analyzed data. This assumes there is at least one channel. | |
| 28 | 42 | unsigned int sampleCount() const; |
| 29 | 43 | unsigned int channelCount() const; |
| 30 | 44 | |
| 31 | - /** | |
| 32 | - * Applies a new maximum samples value, if the given value is higher than the | |
| 33 | - * already stored one | |
| 34 | - * @param newMaxSamples Maximum samples value | |
| 35 | - */ | |
| 36 | - void challengeMaxSamples(unsigned int newMaxSamples); | |
| 37 | - unsigned int getMaxSamples() const; | |
| 45 | + bool softwareTriggerTriggered = false; | |
| 38 | 46 | |
| 47 | + ChannelsGraphs vaChannelSpectrum; | |
| 48 | + ChannelsGraphs vaChannelVoltage; | |
| 39 | 49 | private: |
| 40 | 50 | std::vector<DataChannel> analyzedData; ///< The analyzed data for each channel |
| 41 | - unsigned int maxSamples = 0; ///< The maximum record length of the analyzed data | |
| 42 | 51 | }; | ... | ... |
openhantek/src/post/processor.h
0 → 100644
openhantek/src/hantekdso/softwaretrigger.cpp renamed to openhantek/src/post/softwaretrigger.cpp
| 1 | -#include "softwaretrigger.h" | |
| 2 | -#include "analyse/dataanalyzerresult.h" | |
| 1 | +#include "post/softwaretrigger.h" | |
| 2 | +#include "post/ppresult.h" | |
| 3 | 3 | #include "scopesettings.h" |
| 4 | 4 | #include "viewconstants.h" |
| 5 | 5 | #include "utils/printutils.h" |
| 6 | 6 | |
| 7 | -SoftwareTrigger::PrePostStartTriggerSamples SoftwareTrigger::compute(const DataAnalyzerResult *data, | |
| 7 | +SoftwareTrigger::PrePostStartTriggerSamples SoftwareTrigger::compute(const PPresult *data, | |
| 8 | 8 | const DsoSettingsScope *scope) |
| 9 | 9 | { |
| 10 | 10 | unsigned int preTrigSamples = 0; | ... | ... |
openhantek/src/hantekdso/softwaretrigger.h renamed to openhantek/src/post/softwaretrigger.h
| 1 | 1 | #pragma once |
| 2 | 2 | #include <tuple> |
| 3 | 3 | struct DsoSettingsScope; |
| 4 | -class DataAnalyzerResult; | |
| 4 | +class PPresult; | |
| 5 | 5 | |
| 6 | 6 | |
| 7 | 7 | /** |
| ... | ... | @@ -18,5 +18,5 @@ class SoftwareTrigger { |
| 18 | 18 | * @param scope Scope settings |
| 19 | 19 | * @return Returns a tuple of positions [preTrigger, postTrigger, startTrigger] |
| 20 | 20 | */ |
| 21 | - static PrePostStartTriggerSamples compute(const DataAnalyzerResult *data, const DsoSettingsScope *scope); | |
| 21 | + static PrePostStartTriggerSamples compute(const PPresult *data, const DsoSettingsScope *scope); | |
| 22 | 22 | }; | ... | ... |
openhantek/src/post/spectrumgenerator.cpp
0 → 100644
| 1 | +// SPDX-License-Identifier: GPL-2.0+ | |
| 2 | + | |
| 3 | +#define _USE_MATH_DEFINES | |
| 4 | +#include <cmath> | |
| 5 | + | |
| 6 | +#include <QColor> | |
| 7 | +#include <QMutex> | |
| 8 | +#include <QTimer> | |
| 9 | + | |
| 10 | +#include <fftw3.h> | |
| 11 | + | |
| 12 | +#include "spectrumgenerator.h" | |
| 13 | + | |
| 14 | +#include "glscope.h" | |
| 15 | +#include "settings.h" | |
| 16 | +#include "utils/printutils.h" | |
| 17 | + | |
| 18 | +/// \brief Analyzes the data from the dso. | |
| 19 | +SpectrumGenerator::SpectrumGenerator(const DsoSettingsScope *scope) : scope(scope) {} | |
| 20 | + | |
| 21 | +SpectrumGenerator::~SpectrumGenerator() { | |
| 22 | + if (lastWindowBuffer) fftw_free(lastWindowBuffer); | |
| 23 | +} | |
| 24 | + | |
| 25 | +void SpectrumGenerator::process(PPresult *result) { | |
| 26 | + // Calculate frequencies and spectrums | |
| 27 | + for (ChannelID channel = 0; channel < result->channelCount(); ++channel) { | |
| 28 | + DataChannel *const channelData = result->modifyData(channel); | |
| 29 | + | |
| 30 | + if (channelData->voltage.sample.empty()) { | |
| 31 | + // Clear unused channels | |
| 32 | + channelData->spectrum.interval = 0; | |
| 33 | + channelData->spectrum.sample.clear(); | |
| 34 | + continue; | |
| 35 | + } | |
| 36 | + | |
| 37 | + // Calculate new window | |
| 38 | + size_t sampleCount = channelData->voltage.sample.size(); | |
| 39 | + if (!lastWindowBuffer || lastWindow != scope->spectrumWindow || lastRecordLength != sampleCount) { | |
| 40 | + if (lastWindowBuffer) fftw_free(lastWindowBuffer); | |
| 41 | + lastWindowBuffer = fftw_alloc_real(sampleCount); | |
| 42 | + lastRecordLength = (unsigned)sampleCount; | |
| 43 | + | |
| 44 | + unsigned int windowEnd = lastRecordLength - 1; | |
| 45 | + lastWindow = scope->spectrumWindow; | |
| 46 | + | |
| 47 | + switch (scope->spectrumWindow) { | |
| 48 | + case Dso::WindowFunction::HAMMING: | |
| 49 | + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) | |
| 50 | + *(lastWindowBuffer + windowPosition) = 0.54 - 0.46 * cos(2.0 * M_PI * windowPosition / windowEnd); | |
| 51 | + break; | |
| 52 | + case Dso::WindowFunction::HANN: | |
| 53 | + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) | |
| 54 | + *(lastWindowBuffer + windowPosition) = 0.5 * (1.0 - cos(2.0 * M_PI * windowPosition / windowEnd)); | |
| 55 | + break; | |
| 56 | + case Dso::WindowFunction::COSINE: | |
| 57 | + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) | |
| 58 | + *(lastWindowBuffer + windowPosition) = sin(M_PI * windowPosition / windowEnd); | |
| 59 | + break; | |
| 60 | + case Dso::WindowFunction::LANCZOS: | |
| 61 | + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) { | |
| 62 | + double sincParameter = (2.0 * windowPosition / windowEnd - 1.0) * M_PI; | |
| 63 | + if (sincParameter == 0) | |
| 64 | + *(lastWindowBuffer + windowPosition) = 1; | |
| 65 | + else | |
| 66 | + *(lastWindowBuffer + windowPosition) = sin(sincParameter) / sincParameter; | |
| 67 | + } | |
| 68 | + break; | |
| 69 | + case Dso::WindowFunction::BARTLETT: | |
| 70 | + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) | |
| 71 | + *(lastWindowBuffer + windowPosition) = | |
| 72 | + 2.0 / windowEnd * (windowEnd / 2 - std::abs((double)(windowPosition - windowEnd / 2.0))); | |
| 73 | + break; | |
| 74 | + case Dso::WindowFunction::TRIANGULAR: | |
| 75 | + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) | |
| 76 | + *(lastWindowBuffer + windowPosition) = | |
| 77 | + 2.0 / lastRecordLength * | |
| 78 | + (lastRecordLength / 2 - std::abs((double)(windowPosition - windowEnd / 2.0))); | |
| 79 | + break; | |
| 80 | + case Dso::WindowFunction::GAUSS: { | |
| 81 | + double sigma = 0.4; | |
| 82 | + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) | |
| 83 | + *(lastWindowBuffer + windowPosition) = | |
| 84 | + exp(-0.5 * pow(((windowPosition - windowEnd / 2) / (sigma * windowEnd / 2)), 2)); | |
| 85 | + } break; | |
| 86 | + case Dso::WindowFunction::BARTLETTHANN: | |
| 87 | + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) | |
| 88 | + *(lastWindowBuffer + windowPosition) = 0.62 - | |
| 89 | + 0.48 * std::abs((double)(windowPosition / windowEnd - 0.5)) - | |
| 90 | + 0.38 * cos(2.0 * M_PI * windowPosition / windowEnd); | |
| 91 | + break; | |
| 92 | + case Dso::WindowFunction::BLACKMAN: { | |
| 93 | + double alpha = 0.16; | |
| 94 | + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) | |
| 95 | + *(lastWindowBuffer + windowPosition) = (1 - alpha) / 2 - | |
| 96 | + 0.5 * cos(2.0 * M_PI * windowPosition / windowEnd) + | |
| 97 | + alpha / 2 * cos(4.0 * M_PI * windowPosition / windowEnd); | |
| 98 | + } break; | |
| 99 | + // case Dso::WindowFunction::WINDOW_KAISER: | |
| 100 | + // TODO WINDOW_KAISER | |
| 101 | + // double alpha = 3.0; | |
| 102 | + // for(unsigned int windowPosition = 0; windowPosition < | |
| 103 | + // lastRecordLength; ++windowPosition) | |
| 104 | + //*(window + windowPosition) = ; | |
| 105 | + // break; | |
| 106 | + case Dso::WindowFunction::NUTTALL: | |
| 107 | + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) | |
| 108 | + *(lastWindowBuffer + windowPosition) = 0.355768 - | |
| 109 | + 0.487396 * cos(2 * M_PI * windowPosition / windowEnd) + | |
| 110 | + 0.144232 * cos(4 * M_PI * windowPosition / windowEnd) - | |
| 111 | + 0.012604 * cos(6 * M_PI * windowPosition / windowEnd); | |
| 112 | + break; | |
| 113 | + case Dso::WindowFunction::BLACKMANHARRIS: | |
| 114 | + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) | |
| 115 | + *(lastWindowBuffer + windowPosition) = 0.35875 - | |
| 116 | + 0.48829 * cos(2 * M_PI * windowPosition / windowEnd) + | |
| 117 | + 0.14128 * cos(4 * M_PI * windowPosition / windowEnd) - | |
| 118 | + 0.01168 * cos(6 * M_PI * windowPosition / windowEnd); | |
| 119 | + break; | |
| 120 | + case Dso::WindowFunction::BLACKMANNUTTALL: | |
| 121 | + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) | |
| 122 | + *(lastWindowBuffer + windowPosition) = 0.3635819 - | |
| 123 | + 0.4891775 * cos(2 * M_PI * windowPosition / windowEnd) + | |
| 124 | + 0.1365995 * cos(4 * M_PI * windowPosition / windowEnd) - | |
| 125 | + 0.0106411 * cos(6 * M_PI * windowPosition / windowEnd); | |
| 126 | + break; | |
| 127 | + case Dso::WindowFunction::FLATTOP: | |
| 128 | + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) | |
| 129 | + *(lastWindowBuffer + windowPosition) = 1.0 - 1.93 * cos(2 * M_PI * windowPosition / windowEnd) + | |
| 130 | + 1.29 * cos(4 * M_PI * windowPosition / windowEnd) - | |
| 131 | + 0.388 * cos(6 * M_PI * windowPosition / windowEnd) + | |
| 132 | + 0.032 * cos(8 * M_PI * windowPosition / windowEnd); | |
| 133 | + break; | |
| 134 | + default: // Dso::WINDOW_RECTANGULAR | |
| 135 | + for (unsigned int windowPosition = 0; windowPosition < lastRecordLength; ++windowPosition) | |
| 136 | + *(lastWindowBuffer + windowPosition) = 1.0; | |
| 137 | + } | |
| 138 | + } | |
| 139 | + | |
| 140 | + // Set sampling interval | |
| 141 | + channelData->spectrum.interval = 1.0 / channelData->voltage.interval / sampleCount; | |
| 142 | + | |
| 143 | + // Number of real/complex samples | |
| 144 | + unsigned int dftLength = sampleCount / 2; | |
| 145 | + | |
| 146 | + // Reallocate memory for samples if the sample count has changed | |
| 147 | + channelData->spectrum.sample.resize(sampleCount); | |
| 148 | + | |
| 149 | + // Create sample buffer and apply window | |
| 150 | + std::unique_ptr<double[]> windowedValues = std::unique_ptr<double[]>(new double[sampleCount]); | |
| 151 | + | |
| 152 | + for (unsigned int position = 0; position < sampleCount; ++position) | |
| 153 | + windowedValues[position] = lastWindowBuffer[position] * channelData->voltage.sample[position]; | |
| 154 | + | |
| 155 | + { | |
| 156 | + // Do discrete real to half-complex transformation | |
| 157 | + /// \todo Check if record length is multiple of 2 | |
| 158 | + /// \todo Reuse plan and use FFTW_MEASURE to get fastest algorithm | |
| 159 | + fftw_plan fftPlan = fftw_plan_r2r_1d(sampleCount, windowedValues.get(), | |
| 160 | + &channelData->spectrum.sample.front(), FFTW_R2HC, FFTW_ESTIMATE); | |
| 161 | + fftw_execute(fftPlan); | |
| 162 | + fftw_destroy_plan(fftPlan); | |
| 163 | + } | |
| 164 | + | |
| 165 | + // Do an autocorrelation to get the frequency of the signal | |
| 166 | + std::unique_ptr<double[]> conjugateComplex = std::move(windowedValues); | |
| 167 | + | |
| 168 | + // Real values | |
| 169 | + unsigned int position; | |
| 170 | + double correctionFactor = 1.0 / dftLength / dftLength; | |
| 171 | + conjugateComplex[0] = (channelData->spectrum.sample[0] * channelData->spectrum.sample[0]) * correctionFactor; | |
| 172 | + for (position = 1; position < dftLength; ++position) | |
| 173 | + conjugateComplex[position] = | |
| 174 | + (channelData->spectrum.sample[position] * channelData->spectrum.sample[position] + | |
| 175 | + channelData->spectrum.sample[sampleCount - position] * | |
| 176 | + channelData->spectrum.sample[sampleCount - position]) * | |
| 177 | + correctionFactor; | |
| 178 | + // Complex values, all zero for autocorrelation | |
| 179 | + conjugateComplex[dftLength] = | |
| 180 | + (channelData->spectrum.sample[dftLength] * channelData->spectrum.sample[dftLength]) * correctionFactor; | |
| 181 | + for (++position; position < sampleCount; ++position) conjugateComplex[position] = 0; | |
| 182 | + | |
| 183 | + // Do half-complex to real inverse transformation | |
| 184 | + std::unique_ptr<double[]> correlation = std::unique_ptr<double[]>(new double[sampleCount]); | |
| 185 | + fftw_plan fftPlan = | |
| 186 | + fftw_plan_r2r_1d(sampleCount, conjugateComplex.get(), correlation.get(), FFTW_HC2R, FFTW_ESTIMATE); | |
| 187 | + fftw_execute(fftPlan); | |
| 188 | + fftw_destroy_plan(fftPlan); | |
| 189 | + | |
| 190 | + // Get the frequency from the correlation results | |
| 191 | + double minimumCorrelation = correlation[0]; | |
| 192 | + double peakCorrelation = 0; | |
| 193 | + unsigned int peakPosition = 0; | |
| 194 | + | |
| 195 | + for (unsigned int position = 1; position < sampleCount / 2; ++position) { | |
| 196 | + if (correlation[position] > peakCorrelation && correlation[position] > minimumCorrelation * 2) { | |
| 197 | + peakCorrelation = correlation[position]; | |
| 198 | + peakPosition = position; | |
| 199 | + } else if (correlation[position] < minimumCorrelation) | |
| 200 | + minimumCorrelation = correlation[position]; | |
| 201 | + } | |
| 202 | + correlation.reset(nullptr); | |
| 203 | + | |
| 204 | + // Calculate the frequency in Hz | |
| 205 | + if (peakPosition) | |
| 206 | + channelData->frequency = 1.0 / (channelData->voltage.interval * peakPosition); | |
| 207 | + else | |
| 208 | + channelData->frequency = 0; | |
| 209 | + | |
| 210 | + // Finally calculate the real spectrum if we want it | |
| 211 | + if (scope->spectrum[channel].used) { | |
| 212 | + // Convert values into dB (Relative to the reference level) | |
| 213 | + double offset = 60 - scope->spectrumReference - 20 * log10(dftLength); | |
| 214 | + double offsetLimit = scope->spectrumLimit - scope->spectrumReference; | |
| 215 | + for (std::vector<double>::iterator spectrumIterator = channelData->spectrum.sample.begin(); | |
| 216 | + spectrumIterator != channelData->spectrum.sample.end(); ++spectrumIterator) { | |
| 217 | + double value = 20 * log10(fabs(*spectrumIterator)) + offset; | |
| 218 | + | |
| 219 | + // Check if this value has to be limited | |
| 220 | + if (offsetLimit > value) value = offsetLimit; | |
| 221 | + | |
| 222 | + *spectrumIterator = value; | |
| 223 | + } | |
| 224 | + } | |
| 225 | + } | |
| 226 | +} | ... | ... |
openhantek/src/analyse/dataanalyzer.h renamed to openhantek/src/post/spectrumgenerator.h
| ... | ... | @@ -8,43 +8,28 @@ |
| 8 | 8 | #include <QThread> |
| 9 | 9 | #include <memory> |
| 10 | 10 | |
| 11 | -#include "dataanalyzerresult.h" | |
| 11 | +#include "ppresult.h" | |
| 12 | 12 | #include "dsosamples.h" |
| 13 | 13 | #include "utils/printutils.h" |
| 14 | 14 | #include "enums.h" |
| 15 | 15 | |
| 16 | +#include "processor.h" | |
| 17 | + | |
| 16 | 18 | class DsoSettings; |
| 17 | 19 | struct DsoSettingsScope; |
| 18 | 20 | |
| 19 | 21 | /// \brief Analyzes the data from the dso. |
| 20 | 22 | /// Calculates the spectrum and various data about the signal and saves the |
| 21 | 23 | /// time-/frequencysteps between two values. |
| 22 | -class DataAnalyzer : public QObject { | |
| 23 | - Q_OBJECT | |
| 24 | - | |
| 24 | +class SpectrumGenerator : public Processor { | |
| 25 | 25 | public: |
| 26 | - ~DataAnalyzer(); | |
| 27 | - void applySettings(DsoSettings *settings); | |
| 28 | - void setSourceData(const DSOsamples *data); | |
| 29 | - std::unique_ptr<DataAnalyzerResult> getNextResult(); | |
| 30 | - /** | |
| 31 | - * Call this if the source data changed. | |
| 32 | - */ | |
| 33 | - void samplesAvailable(); | |
| 34 | - | |
| 35 | - private: | |
| 36 | - static std::unique_ptr<DataAnalyzerResult> convertData(const DSOsamples *data, const DsoSettingsScope *scope, unsigned physicalChannels); | |
| 37 | - static void spectrumAnalysis(DataAnalyzerResult *result, Dso::WindowFunction &lastWindow, | |
| 38 | - unsigned int lastRecordLength, double *&lastWindowBuffer, | |
| 39 | - const DsoSettingsScope *scope); | |
| 26 | + SpectrumGenerator(const DsoSettingsScope* scope); | |
| 27 | + virtual ~SpectrumGenerator(); | |
| 28 | + virtual void process(PPresult *data) override; | |
| 40 | 29 | |
| 41 | 30 | private: |
| 42 | - DsoSettings *settings; | |
| 31 | + const DsoSettingsScope* scope; | |
| 43 | 32 | unsigned int lastRecordLength = 0; ///< The record length of the previously analyzed data |
| 44 | 33 | Dso::WindowFunction lastWindow = (Dso::WindowFunction)-1; ///< The previously used dft window function |
| 45 | - double *window = nullptr; | |
| 46 | - const DSOsamples *sourceData = nullptr; | |
| 47 | - std::unique_ptr<DataAnalyzerResult> lastResult; | |
| 48 | - signals: | |
| 49 | - void analyzed(); | |
| 34 | + double *lastWindowBuffer = nullptr; | |
| 50 | 35 | }; | ... | ... |