From 64d75baadfc1f5300dc5fe93e447732e984d241a Mon Sep 17 00:00:00 2001 From: srcejon Date: Tue, 18 Jun 2024 16:26:08 +0100 Subject: [PATCH] Heat Map: Handle memory allocation errors. Allow selecting which data to be saved to reduce memory requirements. --- plugins/channelrx/heatmap/heatmapgui.cpp | 311 ++++++++++++++---- plugins/channelrx/heatmap/heatmapgui.h | 7 +- plugins/channelrx/heatmap/heatmapgui.ui | 121 ++++++- plugins/channelrx/heatmap/heatmapsettings.cpp | 15 + plugins/channelrx/heatmap/heatmapsettings.h | 5 + 5 files changed, 388 insertions(+), 71 deletions(-) diff --git a/plugins/channelrx/heatmap/heatmapgui.cpp b/plugins/channelrx/heatmap/heatmapgui.cpp index 7bbdb5022..13f4cdb0a 100644 --- a/plugins/channelrx/heatmap/heatmapgui.cpp +++ b/plugins/channelrx/heatmap/heatmapgui.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include @@ -226,9 +225,17 @@ void HeatMapGUI::on_mode_currentIndexChanged(int index) ui->writeImage->setEnabled(!none); ui->writeCSV->setEnabled(!none); ui->readCSV->setEnabled(!none); - if (none) { + ui->colorMapLabel->setEnabled(!none); + ui->colorMap->setEnabled(!none); + if (none) + { deleteFromMap(); - } else { + } + else + { + if (m_image.isNull()) { + createImage(m_width, m_height); + } plotMap(); } applySettings(); @@ -551,6 +558,9 @@ HeatMapGUI::HeatMapGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandS m_heatMap = reinterpret_cast(rxChannel); m_heatMap->setMessageQueueToGUI(getInputMessageQueue()); + // Disable 256MB limit on image size + QImageReader::setAllocationLimit(0); + connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); // 50 ms m_scopeVis = m_heatMap->getScopeSink(); @@ -708,6 +718,11 @@ void HeatMapGUI::displaySettings() ui->displayPulseAverage->setChecked(m_settings.m_displayPulseAverage); ui->displayPathLoss->setChecked(m_settings.m_displayPathLoss); ui->displayMins->setValue(m_settings.m_displayMins); + ui->recordAverage->setChecked(m_settings.m_recordAverage); + ui->recordMax->setChecked(m_settings.m_recordMax); + ui->recordMin->setChecked(m_settings.m_recordMin); + ui->recordPulseAverage->setChecked(m_settings.m_recordPulseAverage); + ui->recordPathLoss->setChecked(m_settings.m_recordPathLoss); m_scopeVis->setLiveRate(m_settings.m_sampleRate); @@ -765,7 +780,9 @@ void HeatMapGUI::tick() if (!std::isnan(magAvg)) { powDbAvg = CalcDb::dbPower(magAvg * magAvg); - m_powerAverage[idx] = powDbAvg; + if (m_powerAverage) { + m_powerAverage[idx] = powDbAvg; + } if (m_tickCount % 4 == 0) { ui->average->setText(QString::number(powDbAvg, 'f', 1)); } @@ -777,7 +794,9 @@ void HeatMapGUI::tick() if (!std::isnan(magPulseAvg)) { powDbPulseAvg = CalcDb::dbPower(magPulseAvg * magPulseAvg); - m_powerPulseAverage[idx] = powDbPulseAvg; + if (m_powerPulseAverage) { + m_powerPulseAverage[idx] = powDbPulseAvg; + } if (m_tickCount % 4 == 0) { ui->pulseAverage->setText(QString::number(powDbPulseAvg, 'f', 1)); } @@ -789,7 +808,9 @@ void HeatMapGUI::tick() if (magMaxPeak != -std::numeric_limits::max()) { powDbMaxPeak = CalcDb::dbPower(magMaxPeak * magMaxPeak); - m_powerMaxPeak[idx] = powDbMaxPeak; + if (m_powerMaxPeak) { + m_powerMaxPeak[idx] = powDbMaxPeak; + } if (m_tickCount % 4 == 0) { ui->maxPeak->setText(QString::number(powDbMaxPeak, 'f', 1)); } @@ -801,7 +822,9 @@ void HeatMapGUI::tick() if (magMinPeak != std::numeric_limits::max()) { powDbMinPeak = CalcDb::dbPower(magMinPeak * magMinPeak); - m_powerMinPeak[idx] = powDbMinPeak; + if (m_powerMinPeak) { + m_powerMinPeak[idx] = powDbMinPeak; + } if (m_tickCount % 4 == 0) { ui->minPeak->setText(QString::number(powDbMinPeak, 'f', 1)); } @@ -814,7 +837,9 @@ void HeatMapGUI::tick() double range = calcRange(m_latitude, m_longitude); double frequency = m_deviceCenterFrequency + m_settings.m_inputFrequencyOffset; powDbPathLoss = m_settings.m_txPower - calcFreeSpacePathLoss(range, frequency); - m_powerPathLoss[idx] = powDbPathLoss; + if (m_powerPathLoss) { + m_powerPathLoss[idx] = powDbPathLoss; + } if (m_heatMap->getDeviceAPI()->state(0) == DeviceAPI::StRunning) { @@ -823,9 +848,12 @@ void HeatMapGUI::tick() { // Plot newest measurement on map float *power = getCurrentModePowerData(); - double powDb = power[idx]; - if (!std::isnan(powDb)) { - plotPixel(m_x, m_y, powDb); + if (power) + { + double powDb = power[idx]; + if (!std::isnan(powDb)) { + plotPixel(m_x, m_y, powDb); + } } } @@ -882,9 +910,15 @@ void HeatMapGUI::updatePower(double latitude, double longitude, float power) void HeatMapGUI::plotMap() { - clearImage(); - plotMap(getCurrentModePowerData()); - sendToMap(); + if ((m_settings.m_mode != HeatMapSettings::None) && !m_image.isNull()) + { + clearImage(); + float *data = getCurrentModePowerData(); + if (data) { + plotMap(data); + } + sendToMap(); + } } void HeatMapGUI::clearPower() @@ -903,7 +937,35 @@ void HeatMapGUI::clearPower(float *power) void HeatMapGUI::clearPower(float *power, int size) { - std::fill_n(power, size, std::numeric_limits::quiet_NaN()); + if (power) { + std::fill_n(power, size, std::numeric_limits::quiet_NaN()); + } +} + +void HeatMapGUI::createImage(int width, int height) +{ + if (!m_image.isNull()) { + m_painter.end(); + } + + try + { + if (m_settings.m_mode != HeatMapSettings::None) + { + qDebug() << "HeatMapGUI::createImage" << width << "*" << height; + m_image = QImage(width, height, QImage::Format_ARGB32); + m_painter.begin(&m_image); + } + else + { + m_image = QImage(); + } + } + catch (std::bad_alloc&) + { + m_image = QImage(); + QMessageBox::critical(this, "Heat Map", QString("Failed to allocate memory (width=%1 height=%2)").arg(m_width).arg(m_height)); + } } void HeatMapGUI::clearImage() @@ -929,6 +991,10 @@ void HeatMapGUI::plotMap(float *power) void HeatMapGUI::plotPixel(int x, int y, double power) { + if (m_image.isNull()) { + return; + } + // Normalise to [0,1] float powNorm = (power - m_settings.m_minPower) / (m_settings.m_maxPower - m_settings.m_minPower); if (powNorm < 0) { @@ -1017,6 +1083,11 @@ void HeatMapGUI::makeUIConnections() QObject::connect(ui->displayPulseAverage, &QCheckBox::clicked, this, &HeatMapGUI::on_displayPulseAverage_clicked); QObject::connect(ui->displayPathLoss, &QCheckBox::clicked, this, &HeatMapGUI::on_displayPathLoss_clicked); QObject::connect(ui->displayMins, QOverload::of(&QSpinBox::valueChanged), this, &HeatMapGUI::on_displayMins_valueChanged); + QObject::connect(ui->recordAverage, &QCheckBox::clicked, this, &HeatMapGUI::on_recordAverage_clicked); + QObject::connect(ui->recordMax, &QCheckBox::clicked, this, &HeatMapGUI::on_recordMax_clicked); + QObject::connect(ui->recordMin, &QCheckBox::clicked, this, &HeatMapGUI::on_recordMin_clicked); + QObject::connect(ui->recordPulseAverage, &QCheckBox::clicked, this, &HeatMapGUI::on_recordPulseAverage_clicked); + QObject::connect(ui->recordPathLoss, &QCheckBox::clicked, this, &HeatMapGUI::on_recordPathLoss_clicked); } void HeatMapGUI::updateAbsoluteCenterFrequency() @@ -1171,21 +1242,48 @@ void HeatMapGUI::createMap() m_degreesLonPerPixel = m_resolution / scale / (earthCircumference / 360.0); m_degreesLatPerPixel = m_resolution / (earthCircumference / 360.0); int size = m_width * m_height; - m_powerAverage = new float[size]; - m_powerPulseAverage = new float[size]; - m_powerMaxPeak = new float[size]; - m_powerMinPeak = new float[size]; - m_powerPathLoss = new float[size]; + try + { + if (m_settings.m_recordAverage) { + m_powerAverage = new float[size]; + } else { + m_powerAverage = nullptr; + } + if (m_settings.m_recordPulseAverage) { + m_powerPulseAverage = new float[size]; + } else { + m_powerPulseAverage = nullptr; + } + if (m_settings.m_recordMax) { + m_powerMaxPeak = new float[size]; + } else { + m_powerMaxPeak = nullptr; + } + if (m_settings.m_recordMin) { + m_powerMinPeak = new float[size]; + } else { + m_powerMinPeak = nullptr; + } + if (m_settings.m_recordPathLoss) { + m_powerPathLoss = new float[size]; + } else { + m_powerPathLoss = nullptr; + } - m_north = m_latitude + m_degreesLatPerPixel * m_height / 2; - m_south = m_latitude - m_degreesLatPerPixel * m_height / 2; - m_east = m_longitude + m_degreesLonPerPixel * m_width / 2; - m_west = m_longitude - m_degreesLonPerPixel * m_width / 2; - m_x = m_width / 2; - m_y = m_height / 2; + m_north = m_latitude + m_degreesLatPerPixel * m_height / 2; + m_south = m_latitude - m_degreesLatPerPixel * m_height / 2; + m_east = m_longitude + m_degreesLonPerPixel * m_width / 2; + m_west = m_longitude - m_degreesLonPerPixel * m_width / 2; + m_x = m_width / 2; + m_y = m_height / 2; - m_image = QImage(m_width, m_height, QImage::Format_ARGB32); - m_painter.begin(&m_image); + createImage(m_width, m_height); + } + catch (std::bad_alloc&) + { + deleteMap(); + QMessageBox::critical(this, "Heat Map", QString("Failed to allocate memory (width=%1 height=%2)").arg(m_width).arg(m_height)); + } on_clearHeatMap_clicked(); } @@ -1203,7 +1301,9 @@ void HeatMapGUI::deleteMap() m_powerMinPeak = nullptr; delete[] m_powerPathLoss; m_powerPathLoss = nullptr; - m_painter.end(); + if (!m_image.isNull()) { + m_painter.end(); + } } void HeatMapGUI::resizeMap(int x, int y) @@ -1245,47 +1345,92 @@ void HeatMapGUI::resizeMap(int x, int y) m_south -= m_blockSize * m_degreesLatPerPixel; } + float *powerAverage = nullptr; + float *powerPulseAverage = nullptr; + float *powerMaxPeak = nullptr; + float *powerMinPeak = nullptr; + float *powerPathLoss = nullptr; + int newSize = newWidth * newHeight; - float *powerAverage = new float[newSize]; - float *powerPulseAverage = new float[newSize]; - float *powerMaxPeak = new float[newSize]; - float *powerMinPeak = new float[newSize]; - float *powerPathLoss = new float[newSize]; - clearPower(powerAverage, newSize); - clearPower(powerPulseAverage, newSize); - clearPower(powerMaxPeak, newSize); - clearPower(powerMinPeak, newSize); - clearPower(powerPathLoss, newSize); + qDebug() << "HeatMapGUI::resizeMap:" << m_width << "*" << m_height << "to" << newWidth << "*" << newHeight; - // Copy across old data - for (int j = 0; j < m_height; j++) + try { - int srcStart = j * m_width; - int srcEnd = (j + 1) * m_width; - int destStart = j * newWidth + yOffset + xOffset; - //qDebug() << srcStart << srcEnd << destStart; - std::copy(m_powerAverage + srcStart, m_powerAverage + srcEnd, powerAverage + destStart); - std::copy(m_powerPulseAverage + srcStart, m_powerPulseAverage + srcEnd, powerPulseAverage + destStart); - std::copy(m_powerMaxPeak + srcStart, m_powerMaxPeak + srcEnd, powerMaxPeak + destStart); - std::copy(m_powerMinPeak + srcStart, m_powerMinPeak + srcEnd, powerMinPeak + destStart); - std::copy(m_powerPathLoss + srcStart, m_powerPathLoss + srcEnd, powerPathLoss + destStart); - } + // Allocate new memory + if (m_settings.m_recordAverage) { + powerAverage = new float[newSize]; + } + if (m_settings.m_recordPulseAverage) { + powerPulseAverage = new float[newSize]; + } + if (m_settings.m_recordMax) { + powerMaxPeak = new float[newSize]; + } + if (m_settings.m_recordMin) { + powerMinPeak = new float[newSize]; + } + if (m_settings.m_recordPathLoss) { + powerPathLoss = new float[newSize]; + } - delete[] m_powerAverage; - delete[] m_powerPulseAverage; - delete[] m_powerMaxPeak; - delete[] m_powerMinPeak; - m_powerAverage = powerAverage; - m_powerPulseAverage = powerPulseAverage; - m_powerMaxPeak = powerMaxPeak; - m_powerMinPeak = powerMinPeak; - m_powerPathLoss = powerPathLoss; - m_width = newWidth; - m_height = newHeight; - m_painter.end(); - m_image = QImage(m_width, m_height, QImage::Format_ARGB32); - m_painter.begin(&m_image); - plotMap(); + clearPower(powerAverage, newSize); + clearPower(powerPulseAverage, newSize); + clearPower(powerMaxPeak, newSize); + clearPower(powerMinPeak, newSize); + clearPower(powerPathLoss, newSize); + + // Copy across old data + for (int j = 0; j < m_height; j++) + { + int srcStart = j * m_width; + int srcEnd = (j + 1) * m_width; + int destStart = j * newWidth + yOffset + xOffset; + //qDebug() << srcStart << srcEnd << destStart; + if (powerAverage && m_powerAverage) { + std::copy(m_powerAverage + srcStart, m_powerAverage + srcEnd, powerAverage + destStart); + } + if (powerPulseAverage && m_powerPulseAverage) { + std::copy(m_powerPulseAverage + srcStart, m_powerPulseAverage + srcEnd, powerPulseAverage + destStart); + } + if (powerMaxPeak && m_powerMaxPeak) { + std::copy(m_powerMaxPeak + srcStart, m_powerMaxPeak + srcEnd, powerMaxPeak + destStart); + } + if (powerMinPeak && m_powerMinPeak) { + std::copy(m_powerMinPeak + srcStart, m_powerMinPeak + srcEnd, powerMinPeak + destStart); + } + if (powerPathLoss && m_powerPathLoss) { + std::copy(m_powerPathLoss + srcStart, m_powerPathLoss + srcEnd, powerPathLoss + destStart); + } + } + + createImage(newWidth, newHeight); + + m_width = newWidth; + m_height = newHeight; + + // Delete old memory + delete[] m_powerAverage; + delete[] m_powerPulseAverage; + delete[] m_powerMaxPeak; + delete[] m_powerMinPeak; + m_powerAverage = powerAverage; + m_powerPulseAverage = powerPulseAverage; + m_powerMaxPeak = powerMaxPeak; + m_powerMinPeak = powerMinPeak; + m_powerPathLoss = powerPathLoss; + + plotMap(); + } + catch (std::bad_alloc&) + { + // Detete partially allocated memory + delete[] powerAverage; + delete[] powerPulseAverage; + delete[] powerMaxPeak; + delete[] powerMinPeak; + delete[] powerPathLoss; + QMessageBox::critical(this, "Heat Map", QString("Failed to allocate memory (width=%1 height=%2)").arg(newWidth).arg(newHeight)); + } } } @@ -1462,3 +1607,37 @@ void HeatMapGUI::on_displayMins_valueChanged(int value) applySettings(); } +void HeatMapGUI::on_recordAverage_clicked(bool checked) +{ + m_settings.m_recordAverage = checked; + resizeMap(0, 0); + applySettings(); +} + +void HeatMapGUI::on_recordMax_clicked(bool checked) +{ + m_settings.m_recordMax = checked; + resizeMap(0, 0); + applySettings(); +} + +void HeatMapGUI::on_recordMin_clicked(bool checked) +{ + m_settings.m_recordMin = checked; + resizeMap(0, 0); + applySettings(); +} + +void HeatMapGUI::on_recordPulseAverage_clicked(bool checked) +{ + m_settings.m_recordPulseAverage = checked; + resizeMap(0, 0); + applySettings(); +} + +void HeatMapGUI::on_recordPathLoss_clicked(bool checked) +{ + m_settings.m_recordPathLoss = checked; + resizeMap(0, 0); + applySettings(); +} diff --git a/plugins/channelrx/heatmap/heatmapgui.h b/plugins/channelrx/heatmap/heatmapgui.h index b3f8d9409..398f05c35 100644 --- a/plugins/channelrx/heatmap/heatmapgui.h +++ b/plugins/channelrx/heatmap/heatmapgui.h @@ -142,7 +142,6 @@ private: void blockApplySettings(bool block); void applySettings(bool force = false); void displaySettings(); - void messageReceived(const QByteArray& message, const QDateTime& dateTime); bool handleMessage(const Message& message); void makeUIConnections(); void updateAbsoluteCenterFrequency(); @@ -158,6 +157,7 @@ private: void clearPower(); void clearPower(float *power); void clearPower(float *power, int size); + void createImage(int width, int height); void clearImage(); void updatePower(double latitude, double longitude, float power); void plotMap(); @@ -209,6 +209,11 @@ private slots: void on_displayPulseAverage_clicked(bool checked=false); void on_displayPathLoss_clicked(bool checked=false); void on_displayMins_valueChanged(int value); + void on_recordAverage_clicked(bool checked=false); + void on_recordMax_clicked(bool checked=false); + void on_recordMin_clicked(bool checked=false); + void on_recordPulseAverage_clicked(bool checked=false); + void on_recordPathLoss_clicked(bool checked=false); void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDialogCalled(const QPoint& p); void handleInputMessages(); diff --git a/plugins/channelrx/heatmap/heatmapgui.ui b/plugins/channelrx/heatmap/heatmapgui.ui index 7cd3f80b8..c55d170b5 100644 --- a/plugins/channelrx/heatmap/heatmapgui.ui +++ b/plugins/channelrx/heatmap/heatmapgui.ui @@ -40,7 +40,7 @@ 0 0 390 - 191 + 211 @@ -800,13 +800,127 @@ + + + + Qt::Horizontal + + + + + + + + + Record + + + + + + + Check to record average power + + + + + + Avg + + + true + + + true + + + + + + + Check to record max power + + + Max + + + true + + + true + + + + + + + Check to record min power + + + Min + + + true + + + true + + + + + + + Check to record pulse average power + + + Pulse + + + true + + + true + + + + + + + Check to record path loss + + + Path Loss + + + true + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + 0 - 200 + 220 391 91 @@ -1349,7 +1463,7 @@ 0 - 300 + 320 381 121 @@ -1617,7 +1731,6 @@ - diff --git a/plugins/channelrx/heatmap/heatmapsettings.cpp b/plugins/channelrx/heatmap/heatmapsettings.cpp index 5ff0d51f0..97e356315 100644 --- a/plugins/channelrx/heatmap/heatmapsettings.cpp +++ b/plugins/channelrx/heatmap/heatmapsettings.cpp @@ -50,6 +50,11 @@ void HeatMapSettings::resetToDefaults() m_displayPulseAverage = true; m_displayPathLoss = true; m_displayMins = 2; + m_recordAverage = true; + m_recordMax = true; + m_recordMin = true; + m_recordPulseAverage = true; + m_recordPathLoss = true; m_rgbColor = QColor(102, 40, 220).rgb(); m_title = "Heat Map"; m_streamIndex = 0; @@ -86,6 +91,11 @@ QByteArray HeatMapSettings::serialize() const s.writeBool(18, m_displayPulseAverage); s.writeBool(19, m_displayPathLoss); s.writeS32(20, m_displayMins); + s.writeBool(40, m_recordAverage); + s.writeBool(41, m_recordMax); + s.writeBool(42, m_recordMin); + s.writeBool(43, m_recordPulseAverage); + s.writeBool(44, m_recordPathLoss); s.writeU32(21, m_rgbColor); s.writeString(22, m_title); @@ -148,6 +158,11 @@ bool HeatMapSettings::deserialize(const QByteArray& data) d.readBool(18, &m_displayPulseAverage, true); d.readBool(19, &m_displayPathLoss, true); d.readS32(20, &m_displayMins, 2); + d.readBool(40, &m_recordAverage, true); + d.readBool(41, &m_recordMax, true); + d.readBool(42, &m_recordMin, true); + d.readBool(43, &m_recordPulseAverage, true); + d.readBool(44, &m_recordPathLoss, true); d.readU32(21, &m_rgbColor, QColor(102, 40, 220).rgb()); d.readString(22, &m_title, "Heat Map"); diff --git a/plugins/channelrx/heatmap/heatmapsettings.h b/plugins/channelrx/heatmap/heatmapsettings.h index 3c8282974..5acb28008 100644 --- a/plugins/channelrx/heatmap/heatmapsettings.h +++ b/plugins/channelrx/heatmap/heatmapsettings.h @@ -57,6 +57,11 @@ struct HeatMapSettings bool m_displayPulseAverage; bool m_displayPathLoss; int m_displayMins; + bool m_recordAverage; + bool m_recordMax; + bool m_recordMin; + bool m_recordPulseAverage; + bool m_recordPathLoss; quint32 m_rgbColor; QString m_title;