1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-12-23 01:55:48 -05:00

Heat Map: Handle memory allocation errors. Allow selecting which data to be saved to reduce memory requirements.

This commit is contained in:
srcejon 2024-06-18 16:26:08 +01:00
parent deb4feed3b
commit 64d75baadf
5 changed files with 388 additions and 71 deletions

View File

@ -22,7 +22,6 @@
#include <QDebug>
#include <QMessageBox>
#include <QAction>
#include <QRegExp>
#include <QClipboard>
#include <QBuffer>
@ -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<HeatMap*>(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<double>::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<double>::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<float>::quiet_NaN());
if (power) {
std::fill_n(power, size, std::numeric_limits<float>::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<int>::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();
}

View File

@ -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();

View File

@ -40,7 +40,7 @@
<x>0</x>
<y>0</y>
<width>390</width>
<height>191</height>
<height>211</height>
</rect>
</property>
<property name="minimumSize">
@ -800,13 +800,127 @@
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_20">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="recordLayout">
<item>
<widget class="QLabel" name="recordLabel">
<property name="text">
<string>Record</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="recordAverage">
<property name="statusTip">
<string>Check to record average power</string>
</property>
<property name="accessibleName">
<string/>
</property>
<property name="text">
<string>Avg</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="recordMax">
<property name="statusTip">
<string>Check to record max power</string>
</property>
<property name="text">
<string>Max</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="recordMin">
<property name="statusTip">
<string>Check to record min power</string>
</property>
<property name="text">
<string>Min</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="recordPulseAverage">
<property name="statusTip">
<string>Check to record pulse average power</string>
</property>
<property name="text">
<string>Pulse</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="recordPathLoss">
<property name="statusTip">
<string>Check to record path loss</string>
</property>
<property name="text">
<string>Path Loss</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_9">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="dataContainer" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>200</y>
<y>220</y>
<width>391</width>
<height>91</height>
</rect>
@ -1349,7 +1463,7 @@
<property name="geometry">
<rect>
<x>0</x>
<y>300</y>
<y>320</y>
<width>381</width>
<height>121</height>
</rect>
@ -1617,7 +1731,6 @@
</tabstops>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
<include location="../demodadsb/icons.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -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");

View File

@ -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;