mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-11-17 13:51:47 -05:00
c966f1cb5a
Add sizeToContents in ChannelGUI and FeatureGUI, called when widget is rolled, so we can remove resizing code from all of the individual channels and features. In RollupContents, use minimumSizeHint for calculated size, so that minimumWidth can come from .ui file. In DeviceGUI::sizeToContents(), call adjustSize(), so Device GUIs start out at minimum needed size (which should restore appearance prior to last patch). In stackSubWindows, use available space for channels if no spectrum/features present. In stackSubWindows, fix spectrum from being sized too big, resulting in scroll bars appearing. Reset user-defined channel width in stackSubWindows, when channels are removed. Don't stack maximized windows. There's one hack in Channel/FeatureGUI::maximizeWindow(). It seems that when maximimzing a window, QOpenGLWidgets aren't always paint properly immediately afterwards, so the code forces an additional update. I can't see why the first call to paintGL doesn't work.
6247 lines
230 KiB
C++
6247 lines
230 KiB
C++
///////////////////////////////////////////////////////////////////////////////////
|
|
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
|
|
// Copyright (C) 2021 Jon Beniston, M7RCE //
|
|
// //
|
|
// This program is free software; you can redistribute it and/or modify //
|
|
// it under the terms of the GNU General Public License as published by //
|
|
// the Free Software Foundation as version 3 of the License, or //
|
|
// (at your option) any later version. //
|
|
// //
|
|
// This program is distributed in the hope that it will be useful, //
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
|
// GNU General Public License V3 for more details. //
|
|
// //
|
|
// You should have received a copy of the GNU General Public License //
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include <limits>
|
|
#include <numeric>
|
|
#include <algorithm>
|
|
#include <functional>
|
|
#include <ctype.h>
|
|
#include <QDockWidget>
|
|
#include <QMainWindow>
|
|
#include <QDebug>
|
|
#include <QMessageBox>
|
|
#include <QAction>
|
|
#include <QRegExp>
|
|
#include <QClipboard>
|
|
#include <QFileDialog>
|
|
#include <QImage>
|
|
#include <QTimer>
|
|
|
|
#include "radioastronomygui.h"
|
|
|
|
#include "device/deviceuiset.h"
|
|
#include "dsp/dspengine.h"
|
|
#include "dsp/dspcommands.h"
|
|
#include "ui_radioastronomygui.h"
|
|
#include "plugin/pluginapi.h"
|
|
#include "util/simpleserializer.h"
|
|
#include "util/db.h"
|
|
#include "util/astronomy.h"
|
|
#include "util/interpolation.h"
|
|
#include "util/png.h"
|
|
#include "util/units.h"
|
|
#include "gui/basicchannelsettingsdialog.h"
|
|
#include "gui/devicestreamselectiondialog.h"
|
|
#include "dsp/dspengine.h"
|
|
#include "gui/crightclickenabler.h"
|
|
#include "gui/timedelegate.h"
|
|
#include "gui/decimaldelegate.h"
|
|
#include "channel/channelwebapiutils.h"
|
|
#include "maincore.h"
|
|
#include "feature/featurewebapiutils.h"
|
|
#include "feature/feature.h"
|
|
#include "feature/featureset.h"
|
|
|
|
#include "radioastronomy.h"
|
|
#include "radioastronomysink.h"
|
|
#include "radioastronomysensordialog.h"
|
|
#include "radioastronomycalibrationdialog.h"
|
|
|
|
#include "SWGMapItem.h"
|
|
#include "SWGStarTrackerTarget.h"
|
|
#include "SWGStarTrackerDisplaySettings.h"
|
|
#include "SWGStarTrackerDisplayLoSSettings.h"
|
|
|
|
// Time value is in milliseconds - Displays hh:mm:ss or d hh:mm:ss
|
|
class TimeDeltaDelegate : public QStyledItemDelegate {
|
|
|
|
public:
|
|
virtual QString displayText(const QVariant &value, const QLocale &locale) const override
|
|
{
|
|
(void) locale;
|
|
qint64 v = value.toLongLong(); // In milliseconds
|
|
bool neg = v < 0;
|
|
v = abs(v);
|
|
qint64 days = v / (1000*60*60*24);
|
|
v = v % (1000*60*60*24);
|
|
qint64 hours = v / (1000*60*60);
|
|
v = v % (1000*60*60);
|
|
qint64 minutes = v / (1000*60);
|
|
v = v % (1000*60);
|
|
qint64 seconds = v / (1000);
|
|
//qint64 msec = v % 1000;
|
|
|
|
if (days > 0) {
|
|
return QString("%1%2 %3:%4:%5").arg(neg ? "-" : "").arg(days).arg(hours, 2, 10, QChar('0')).arg(minutes, 2, 10, QChar('0')).arg(seconds, 2, 10, QChar('0'));
|
|
} else {
|
|
return QString("%1%2:%3:%4").arg(neg ? "-" : "").arg(hours, 2, 10, QChar('0')).arg(minutes, 2, 10, QChar('0')).arg(seconds, 2, 10, QChar('0'));
|
|
}
|
|
}
|
|
|
|
private:
|
|
QString m_format;
|
|
|
|
};
|
|
|
|
// Delegate for table to display hours, minutes and seconds
|
|
class HMSDelegate : public QStyledItemDelegate {
|
|
|
|
public:
|
|
virtual QString displayText(const QVariant &value, const QLocale &locale) const override
|
|
{
|
|
(void) locale;
|
|
return Units::decimalHoursToHoursMinutesAndSeconds(value.toDouble());
|
|
}
|
|
|
|
};
|
|
|
|
// Delegate for table to display degrees, minutes and seconds
|
|
class DMSDelegate : public QStyledItemDelegate {
|
|
|
|
public:
|
|
virtual QString displayText(const QVariant &value, const QLocale &locale) const override
|
|
{
|
|
(void) locale;
|
|
return Units::decimalDegreesToDegreeMinutesAndSeconds(value.toDouble());
|
|
}
|
|
|
|
};
|
|
|
|
void RadioAstronomyGUI::LABData::read(QFile* file, float l, float b)
|
|
{
|
|
m_l = l;
|
|
m_b = b;
|
|
m_vlsr.clear();
|
|
m_temp.clear();
|
|
QTextStream in(file);
|
|
|
|
while (!in.atEnd())
|
|
{
|
|
QString line = in.readLine().trimmed();
|
|
if (!line.startsWith("%") && (line.size() > 0)) // Lines starting with % are comments
|
|
{
|
|
// 4 cols: v_lsr [km/s], T_B [K], freq. [Mhz], wavel. [cm]
|
|
line = line.simplified();
|
|
QStringList cols = line.split(" ");
|
|
if (cols.size() == 4)
|
|
{
|
|
m_vlsr.append(cols[0].toFloat());
|
|
m_temp.append(cols[1].toFloat());
|
|
}
|
|
else
|
|
{
|
|
qDebug() << "RadioAstronomyGUI::parseLAB: Unexpected number of columns";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::LABData::toSeries(QLineSeries *series)
|
|
{
|
|
series->clear();
|
|
series->setName(QString("LAB l=%1 b=%2").arg(m_l).arg(m_b));
|
|
for (int i = 0; i < m_vlsr.size(); i++) {
|
|
series->append(m_vlsr[i], m_temp[i]);
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::SensorMeasurements::init(const QString& name, bool visible)
|
|
{
|
|
m_series = new QLineSeries();
|
|
m_series->setName(name);
|
|
m_series->setVisible(visible);
|
|
m_yAxis = new QValueAxis();
|
|
m_yAxis->setTitleText(name);
|
|
m_yAxis->setVisible(visible);
|
|
m_min = std::numeric_limits<double>::max();
|
|
m_max = -std::numeric_limits<double>::max();
|
|
}
|
|
|
|
void RadioAstronomyGUI::SensorMeasurements::setName(const QString& name)
|
|
{
|
|
if (m_series) {
|
|
m_series->setName(name);
|
|
}
|
|
if (m_yAxis) {
|
|
m_yAxis->setTitleText(name);
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::SensorMeasurements::clicked(bool checked)
|
|
{
|
|
if (m_series) {
|
|
m_series->setVisible(checked);
|
|
}
|
|
if (m_yAxis) {
|
|
m_yAxis->setVisible(checked);
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::SensorMeasurements::append(SensorMeasurement *measurement)
|
|
{
|
|
m_measurements.append(measurement);
|
|
addToSeries(measurement);
|
|
}
|
|
|
|
void RadioAstronomyGUI::SensorMeasurements::addToSeries(SensorMeasurement *measurement)
|
|
{
|
|
m_series->append(measurement->m_dateTime.toMSecsSinceEpoch(), measurement->m_value);
|
|
|
|
m_max = std::max(m_max, measurement->m_value);
|
|
m_min = std::min(m_min, measurement->m_value);
|
|
if (m_min == m_max) {
|
|
// Axis isn't drawn properly if min and max are the same
|
|
m_yAxis->setRange(m_min*0.9, m_max*1.1);
|
|
} else {
|
|
m_yAxis->setRange(m_min, m_max);
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::SensorMeasurements::addAllToSeries()
|
|
{
|
|
for (int i = 0; i < m_measurements.size(); i++) {
|
|
addToSeries(m_measurements[i]);
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::SensorMeasurements::clear()
|
|
{
|
|
m_series->clear();
|
|
qDeleteAll(m_measurements);
|
|
m_measurements.clear();
|
|
}
|
|
|
|
void RadioAstronomyGUI::SensorMeasurements::addToChart(QChart* chart, QDateTimeAxis* xAxis)
|
|
{
|
|
chart->addSeries(m_series);
|
|
m_series->attachAxis(xAxis);
|
|
m_series->attachAxis(m_yAxis);
|
|
}
|
|
|
|
void RadioAstronomyGUI::SensorMeasurements::setPen(const QPen& pen)
|
|
{
|
|
m_series->setPen(pen);
|
|
}
|
|
|
|
QValueAxis* RadioAstronomyGUI::SensorMeasurements::yAxis() const
|
|
{
|
|
return m_yAxis;
|
|
}
|
|
|
|
qreal RadioAstronomyGUI::SensorMeasurements::lastValue()
|
|
{
|
|
if (m_measurements.size() > 0) {
|
|
return m_measurements.last()->m_value;
|
|
} else {
|
|
return 0.0;
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::resizePowerTable()
|
|
{
|
|
// Fill table with a row of dummy data that will size the columns nicely
|
|
// Trailing spaces are for sort arrow
|
|
int row = ui->powerTable->rowCount();
|
|
ui->powerTable->setRowCount(row + 1);
|
|
ui->powerTable->setItem(row, POWER_COL_DATE, new QTableWidgetItem("15/04/2016"));
|
|
ui->powerTable->setItem(row, POWER_COL_TIME, new QTableWidgetItem("10:17:00"));
|
|
ui->powerTable->setItem(row, POWER_COL_POWER, new QTableWidgetItem("1.235-e5"));
|
|
ui->powerTable->setItem(row, POWER_COL_POWER_DB, new QTableWidgetItem("-100.0"));
|
|
ui->powerTable->setItem(row, POWER_COL_POWER_DBM, new QTableWidgetItem("-100.0"));
|
|
ui->powerTable->setItem(row, POWER_COL_TSYS, new QTableWidgetItem("3000"));
|
|
ui->powerTable->setItem(row, POWER_COL_TSYS0, new QTableWidgetItem("100"));
|
|
ui->powerTable->setItem(row, POWER_COL_TSOURCE, new QTableWidgetItem("300"));
|
|
ui->powerTable->setItem(row, POWER_COL_TB, new QTableWidgetItem("100000"));
|
|
ui->powerTable->setItem(row, POWER_COL_TSKY, new QTableWidgetItem("300"));
|
|
ui->powerTable->setItem(row, POWER_COL_FLUX, new QTableWidgetItem("100000.00"));
|
|
ui->powerTable->setItem(row, POWER_COL_SIGMA_T, new QTableWidgetItem("0.01"));
|
|
ui->powerTable->setItem(row, POWER_COL_SIGMA_S, new QTableWidgetItem("1000.0"));
|
|
ui->powerTable->setItem(row, POWER_COL_OMEGA_A, new QTableWidgetItem("0.000001"));
|
|
ui->powerTable->setItem(row, POWER_COL_OMEGA_S, new QTableWidgetItem("0.000001"));
|
|
ui->powerTable->setItem(row, POWER_COL_RA, new QTableWidgetItem("12h59m59.10s"));
|
|
ui->powerTable->setItem(row, POWER_COL_DEC, new QTableWidgetItem("-90d59\'59.00\""));
|
|
ui->powerTable->setItem(row, POWER_COL_GAL_LAT, new QTableWidgetItem("-90.0"));
|
|
ui->powerTable->setItem(row, POWER_COL_GAL_LON, new QTableWidgetItem("359.0"));
|
|
ui->powerTable->setItem(row, POWER_COL_AZ, new QTableWidgetItem("359.0"));
|
|
ui->powerTable->setItem(row, POWER_COL_EL, new QTableWidgetItem("90.0"));
|
|
ui->powerTable->setItem(row, POWER_COL_VBCRS, new QTableWidgetItem("10.0"));
|
|
ui->powerTable->setItem(row, POWER_COL_VLSR, new QTableWidgetItem("10.0"));
|
|
ui->powerTable->setItem(row, POWER_COL_SOLAR_FLUX, new QTableWidgetItem("60.0"));
|
|
ui->powerTable->setItem(row, POWER_COL_AIR_TEMP, new QTableWidgetItem("20.0"));
|
|
ui->powerTable->setItem(row, POWER_COL_SENSOR_1, new QTableWidgetItem("1.0000000"));
|
|
ui->powerTable->setItem(row, POWER_COL_SENSOR_2, new QTableWidgetItem("1.0000000"));
|
|
ui->powerTable->resizeColumnsToContents();
|
|
ui->powerTable->removeRow(row);
|
|
}
|
|
|
|
void RadioAstronomyGUI::resizePowerMarkerTable()
|
|
{
|
|
// Fill table with a row of dummy data that will size the columns nicely
|
|
int row = ui->powerMarkerTable->rowCount();
|
|
ui->powerMarkerTable->setRowCount(row + 1);
|
|
ui->powerMarkerTable->setItem(row, POWER_MARKER_COL_NAME, new QTableWidgetItem("Max"));
|
|
ui->powerMarkerTable->setItem(row, POWER_MARKER_COL_DATE, new QTableWidgetItem("15/04/2016"));
|
|
ui->powerMarkerTable->setItem(row, POWER_MARKER_COL_TIME, new QTableWidgetItem("10:17:00"));
|
|
ui->powerMarkerTable->setItem(row, POWER_MARKER_COL_VALUE, new QTableWidgetItem("1000.0"));
|
|
ui->powerMarkerTable->setItem(row, POWER_MARKER_COL_DELTA_X, new QTableWidgetItem("1 23:59:59"));
|
|
ui->powerMarkerTable->setItem(row, POWER_MARKER_COL_DELTA_Y, new QTableWidgetItem("1000.0"));
|
|
ui->powerMarkerTable->setItem(row, POWER_MARKER_COL_DELTA_TO, new QTableWidgetItem("Max"));
|
|
ui->powerMarkerTable->resizeColumnsToContents();
|
|
ui->powerMarkerTable->removeRow(row);
|
|
}
|
|
|
|
void RadioAstronomyGUI::resizeSpectrumMarkerTable()
|
|
{
|
|
// Fill table with a row of dummy data that will size the columns nicely
|
|
int row = ui->spectrumMarkerTable->rowCount();
|
|
ui->spectrumMarkerTable->setRowCount(row + 1);
|
|
ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_NAME, new QTableWidgetItem("Max"));
|
|
ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_FREQ, new QTableWidgetItem("1420.405000"));
|
|
ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_VALUE, new QTableWidgetItem("1000.0"));
|
|
ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_DELTA_X, new QTableWidgetItem("1420.405000"));
|
|
ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_DELTA_Y, new QTableWidgetItem("1000.0"));
|
|
ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_DELTA_TO, new QTableWidgetItem("M1"));
|
|
ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_VR, new QTableWidgetItem("-100.0"));
|
|
ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_R, new QTableWidgetItem("10.0"));
|
|
ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_D, new QTableWidgetItem("10.0/10.0"));
|
|
QTableWidgetItem* check = new QTableWidgetItem();
|
|
check->setFlags(Qt::ItemIsUserCheckable);
|
|
ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_PLOT_MAX, check);
|
|
ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_R_MIN, new QTableWidgetItem("10.0"));
|
|
ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_V, new QTableWidgetItem("250.0"));
|
|
ui->spectrumMarkerTable->resizeColumnsToContents();
|
|
ui->spectrumMarkerTable->removeRow(row);
|
|
}
|
|
|
|
void RadioAstronomyGUI::calcSpectrumMarkerDelta()
|
|
{
|
|
if (m_spectrumM1Valid && m_spectrumM2Valid)
|
|
{
|
|
qreal dx = m_spectrumM2X - m_spectrumM1X;
|
|
qreal dy = m_spectrumM2Y - m_spectrumM1Y;
|
|
|
|
ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_M2, SPECTRUM_MARKER_COL_DELTA_X)->setData(Qt::DisplayRole, dx);
|
|
ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_M2, SPECTRUM_MARKER_COL_DELTA_Y)->setData(Qt::DisplayRole, dy);
|
|
ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_M2, SPECTRUM_MARKER_COL_DELTA_TO)->setData(Qt::DisplayRole, "M1");
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::calcPowerMarkerDelta()
|
|
{
|
|
if (m_powerM1Valid && m_powerM2Valid)
|
|
{
|
|
qreal dx = m_powerM2X - m_powerM1X;
|
|
qreal dy = m_powerM2Y - m_powerM1Y;
|
|
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_M2, POWER_MARKER_COL_DELTA_X)->setData(Qt::DisplayRole, dx);
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_M2, POWER_MARKER_COL_DELTA_Y)->setData(Qt::DisplayRole, dy);
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_M2, POWER_MARKER_COL_DELTA_TO)->setData(Qt::DisplayRole, "M1");
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::calcPowerPeakDelta()
|
|
{
|
|
qreal dx = m_powerMaxX - m_powerMinX;
|
|
qreal dy = m_powerMaxY - m_powerMinY;
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MIN, POWER_MARKER_COL_DELTA_X)->setData(Qt::DisplayRole, dx);
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MIN, POWER_MARKER_COL_DELTA_Y)->setData(Qt::DisplayRole, dy);
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MIN, POWER_MARKER_COL_DELTA_TO)->setData(Qt::DisplayRole, "Max");
|
|
}
|
|
|
|
void RadioAstronomyGUI::addToPowerSeries(FFTMeasurement *fft, bool skipCalcs)
|
|
{
|
|
if ( ((m_settings.m_powerYUnits == RadioAstronomySettings::PY_DBFS) || fft->m_temp) // Only dBFS valid if no temp was calculated
|
|
&& !((m_settings.m_powerYUnits == RadioAstronomySettings::PY_DBM) && (fft->m_tSys == 0.0f)) // dBm value not valid if temp is 0
|
|
)
|
|
{
|
|
qreal power;
|
|
switch (m_settings.m_powerYData)
|
|
{
|
|
case RadioAstronomySettings::PY_POWER:
|
|
switch (m_settings.m_powerYUnits)
|
|
{
|
|
case RadioAstronomySettings::PY_DBFS:
|
|
power = fft->m_totalPowerdBFS;
|
|
break;
|
|
case RadioAstronomySettings::PY_DBM:
|
|
power = fft->m_totalPowerdBm;
|
|
break;
|
|
case RadioAstronomySettings::PY_WATTS:
|
|
power = fft->m_totalPowerWatts;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case RadioAstronomySettings::PY_TSYS:
|
|
power = fft->m_tSys;
|
|
break;
|
|
case RadioAstronomySettings::PY_TSOURCE:
|
|
power = fft->m_tSource;
|
|
break;
|
|
case RadioAstronomySettings::PY_FLUX:
|
|
switch (m_settings.m_powerYUnits)
|
|
{
|
|
case RadioAstronomySettings::PY_SFU:
|
|
power = Units::wattsPerMetrePerHertzToSolarFluxUnits(fft->m_flux);
|
|
break;
|
|
case RadioAstronomySettings::PY_JANSKY:
|
|
power = Units::wattsPerMetrePerHertzToJansky(fft->m_flux);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
QDateTime dateTime = fft->m_dateTime;
|
|
|
|
if (m_powerSeries->count() == 0)
|
|
{
|
|
m_powerMin = power;
|
|
m_powerMax = power;
|
|
}
|
|
else
|
|
{
|
|
m_powerMin = std::min(power, m_powerMin);
|
|
m_powerMax = std::max(power, m_powerMax);
|
|
}
|
|
m_powerSeries->append(dateTime.toMSecsSinceEpoch(), power);
|
|
addToPowerFilter(dateTime.toMSecsSinceEpoch(), power);
|
|
if (!skipCalcs)
|
|
{
|
|
if (m_settings.m_powerAutoscale)
|
|
{
|
|
blockApplySettings(true);
|
|
powerAutoscaleY(false);
|
|
blockApplySettings(false);
|
|
}
|
|
}
|
|
|
|
if (m_settings.m_powerYUnits == RadioAstronomySettings::PY_KELVIN) {
|
|
m_powerTsys0Series->append(dateTime.toMSecsSinceEpoch(), fft->m_tSys0);
|
|
} else if (m_settings.m_powerYUnits == RadioAstronomySettings::PY_DBM) {
|
|
m_powerTsys0Series->append(dateTime.toMSecsSinceEpoch(), Astronomy::noisePowerdBm(fft->m_tSys0, fft->m_sampleRate));
|
|
} else if (m_settings.m_powerYUnits == RadioAstronomySettings::PY_WATTS) {
|
|
m_powerTsys0Series->append(dateTime.toMSecsSinceEpoch(), Astronomy::m_boltzmann * fft->m_tSys0 * fft->m_sampleRate);
|
|
}
|
|
|
|
if (!m_powerPeakValid)
|
|
{
|
|
m_powerPeakValid = true;
|
|
m_powerMinY = power;
|
|
m_powerMinX = dateTime.toMSecsSinceEpoch();
|
|
m_powerMaxY = power;
|
|
m_powerMaxX = dateTime.toMSecsSinceEpoch();
|
|
m_powerPeakSeries->clear();
|
|
m_powerPeakSeries->append(m_powerMaxX, m_powerMaxY);
|
|
m_powerPeakSeries->append(m_powerMaxX, m_powerMaxY);
|
|
QDateTime dt = QDateTime::fromMSecsSinceEpoch(m_powerMaxX);
|
|
if (!skipCalcs)
|
|
{
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MAX, POWER_MARKER_COL_DATE)->setData(Qt::DisplayRole, dt.date());
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MAX, POWER_MARKER_COL_TIME)->setData(Qt::DisplayRole, dt.time());
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MAX, POWER_MARKER_COL_VALUE)->setData(Qt::DisplayRole, m_powerMaxY);
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MIN, POWER_MARKER_COL_DATE)->setData(Qt::DisplayRole, dt.date());
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MIN, POWER_MARKER_COL_TIME)->setData(Qt::DisplayRole, dt.time());
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MIN, POWER_MARKER_COL_VALUE)->setData(Qt::DisplayRole, m_powerMinY);
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MIN, POWER_MARKER_COL_DELTA_X)->setData(Qt::DisplayRole, 0.0);
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MIN, POWER_MARKER_COL_DELTA_Y)->setData(Qt::DisplayRole, 0.0);
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MIN, POWER_MARKER_COL_DELTA_TO)->setData(Qt::DisplayRole, "Max");
|
|
}
|
|
}
|
|
|
|
if (power > m_powerMaxY)
|
|
{
|
|
m_powerMaxY = power;
|
|
m_powerMaxX = dateTime.toMSecsSinceEpoch();
|
|
m_powerPeakSeries->replace(0, m_powerMaxX, m_powerMaxY);
|
|
QDateTime dt = QDateTime::fromMSecsSinceEpoch(m_powerMaxX);
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MAX, POWER_MARKER_COL_DATE)->setData(Qt::DisplayRole, dt.date());
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MAX, POWER_MARKER_COL_TIME)->setData(Qt::DisplayRole, dt.time());
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MAX, POWER_MARKER_COL_VALUE)->setData(Qt::DisplayRole, m_powerMaxY);
|
|
calcPowerPeakDelta();
|
|
}
|
|
else if (power < m_powerMinY)
|
|
{
|
|
m_powerMinY = power;
|
|
m_powerMinX = dateTime.toMSecsSinceEpoch();
|
|
m_powerPeakSeries->replace(1, m_powerMinX, m_powerMinY);
|
|
QDateTime dt = QDateTime::fromMSecsSinceEpoch(m_powerMinX);
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MIN, POWER_MARKER_COL_DATE)->setData(Qt::DisplayRole, dt.date());
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MIN, POWER_MARKER_COL_TIME)->setData(Qt::DisplayRole, dt.time());
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MIN, POWER_MARKER_COL_VALUE)->setData(Qt::DisplayRole, m_powerMinY);
|
|
calcPowerPeakDelta();
|
|
}
|
|
|
|
// Update markers (E.g. if scale changes)
|
|
int c = m_powerSeries->count();
|
|
if (c >= 2)
|
|
{
|
|
QPointF p1 = m_powerSeries->at(c-2);
|
|
QPointF p2 = m_powerSeries->at(c-1);
|
|
if (m_powerM1Valid && (m_powerM1X >= p1.x()) && (m_powerM1X < p2.x()))
|
|
{
|
|
m_powerM1Y = Interpolation::interpolate(p1.x(), p1.y(), p2.x(), p2.y(), m_powerM1X);
|
|
m_powerMarkerSeries->insert(0, QPointF(dateTime.toMSecsSinceEpoch(), m_powerM1Y));
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_M1, POWER_MARKER_COL_VALUE)->setData(Qt::DisplayRole, m_powerM1Y);
|
|
calcPowerMarkerDelta();
|
|
}
|
|
if (m_powerM2Valid && (m_powerM2X >= p1.x()) && (m_powerM2X < p2.x()))
|
|
{
|
|
m_powerM2Y = Interpolation::interpolate(p1.x(), p1.y(), p2.x(), p2.y(), m_powerM2X);
|
|
m_powerMarkerSeries->append(dateTime.toMSecsSinceEpoch(), m_powerM2Y);
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_M2, POWER_MARKER_COL_VALUE)->setData(Qt::DisplayRole, m_powerM2Y);
|
|
calcPowerMarkerDelta();
|
|
}
|
|
}
|
|
|
|
if (!skipCalcs)
|
|
{
|
|
// Set X axis format to include date if measurements span over different days
|
|
// Seems there's a QT bug here, if we call m_powerXAxis->setFormat, the chart isn't
|
|
// redrawn properly, so we have to redraw the whole thing
|
|
QDateTime minDateTime = m_powerXAxis->min();
|
|
QDateTime maxDateTime = m_powerXAxis->max();
|
|
bool sameDay = minDateTime.date() == maxDateTime.date();
|
|
if (!sameDay && m_powerXAxisSameDay)
|
|
{
|
|
m_powerXAxisSameDay = true;
|
|
QTimer::singleShot(100, this, SLOT(plotPowerChart()));
|
|
}
|
|
}
|
|
|
|
if (!skipCalcs && ui->powerShowAvg->isChecked()) {
|
|
calcAverages();
|
|
}
|
|
}
|
|
if (m_powerSeries->count() <= 1) // Don't check skipCalcs here, as that will be set for first data
|
|
{
|
|
ui->powerStartTime->setMinimumDateTime(fft->m_dateTime);
|
|
ui->powerEndTime->setMinimumDateTime(fft->m_dateTime);
|
|
if (m_settings.m_powerAutoscale)
|
|
{
|
|
ui->powerStartTime->setDateTime(fft->m_dateTime);
|
|
ui->powerEndTime->setDateTime(fft->m_dateTime);
|
|
}
|
|
}
|
|
if (!skipCalcs)
|
|
{
|
|
ui->powerStartTime->setMaximumDateTime(fft->m_dateTime);
|
|
ui->powerEndTime->setMaximumDateTime(fft->m_dateTime);
|
|
if (m_settings.m_powerAutoscale) {
|
|
ui->powerEndTime->setDateTime(fft->m_dateTime);
|
|
}
|
|
}
|
|
}
|
|
|
|
double RadioAstronomyGUI::degreesToSteradian(double deg) const
|
|
{
|
|
// https://en.wikipedia.org/wiki/Steradian - Other properties
|
|
double s = sin(Units::degreesToRadians(deg) / 4.0);
|
|
return 4.0 * M_PI * s * s;
|
|
}
|
|
|
|
double RadioAstronomyGUI::hpbwToSteradians(double hpbw) const
|
|
{
|
|
// https://www.cv.nrao.edu/~sransom/web/Ch3.html#E118
|
|
double theta = Units::degreesToRadians(hpbw);
|
|
return theta * theta * M_PI / (4.0 * M_LN2);
|
|
}
|
|
|
|
double RadioAstronomyGUI::calcOmegaA() const
|
|
{
|
|
return hpbwToSteradians(m_beamWidth);
|
|
}
|
|
|
|
double RadioAstronomyGUI::calcOmegaS() const
|
|
{
|
|
if (m_settings.m_sourceType == RadioAstronomySettings::UNKNOWN)
|
|
{
|
|
return 0.0;
|
|
}
|
|
else if (m_settings.m_sourceType == RadioAstronomySettings::EXTENDED)
|
|
{
|
|
return calcOmegaA();
|
|
}
|
|
else
|
|
{
|
|
return m_settings.m_omegaSUnits == RadioAstronomySettings::STERRADIANS ? m_settings.m_omegaS : degreesToSteradian(m_settings.m_omegaS);
|
|
}
|
|
}
|
|
|
|
double RadioAstronomyGUI::beamFillingFactor() const
|
|
{
|
|
if (m_settings.m_sourceType == RadioAstronomySettings::EXTENDED)
|
|
{
|
|
return 1.0;
|
|
}
|
|
else
|
|
{
|
|
// https://www.cv.nrao.edu/~sransom/web/Ch3.html#E55
|
|
return calcOmegaS() / calcOmegaA();
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::powerMeasurementReceived(FFTMeasurement *fft, bool skipCalcs)
|
|
{
|
|
ui->powerTable->setSortingEnabled(false);
|
|
int row = ui->powerTable->rowCount();
|
|
ui->powerTable->setRowCount(row + 1);
|
|
|
|
QTableWidgetItem* dateItem = new QTableWidgetItem();
|
|
QTableWidgetItem* timeItem = new QTableWidgetItem();
|
|
QTableWidgetItem* powerItem = new QTableWidgetItem();
|
|
QTableWidgetItem* powerDBItem = new QTableWidgetItem();
|
|
QTableWidgetItem* powerdBmItem = new QTableWidgetItem();
|
|
QTableWidgetItem* tSysItem = new QTableWidgetItem();
|
|
QTableWidgetItem* tSys0Item = new QTableWidgetItem();
|
|
QTableWidgetItem* tSourceItem = new QTableWidgetItem();
|
|
QTableWidgetItem* tBItem = new QTableWidgetItem();
|
|
QTableWidgetItem* tSkyItem = new QTableWidgetItem();
|
|
QTableWidgetItem* fluxItem = new QTableWidgetItem();
|
|
QTableWidgetItem* sigmaTItem = new QTableWidgetItem();
|
|
QTableWidgetItem* sigmaSItem = new QTableWidgetItem();
|
|
QTableWidgetItem* omegaAItem = new QTableWidgetItem();
|
|
QTableWidgetItem* omegaSItem = new QTableWidgetItem();
|
|
QTableWidgetItem* raItem = new QTableWidgetItem();
|
|
QTableWidgetItem* decItem = new QTableWidgetItem();
|
|
QTableWidgetItem* lonItem = new QTableWidgetItem();
|
|
QTableWidgetItem* latItem = new QTableWidgetItem();
|
|
QTableWidgetItem* azItem = new QTableWidgetItem();
|
|
QTableWidgetItem* elItem = new QTableWidgetItem();
|
|
QTableWidgetItem* vBCRSItem = new QTableWidgetItem();
|
|
QTableWidgetItem* vLSRItem = new QTableWidgetItem();
|
|
QTableWidgetItem* solarFluxItem = new QTableWidgetItem();
|
|
QTableWidgetItem* airTempItem = new QTableWidgetItem();
|
|
QTableWidgetItem* sensor1Item = new QTableWidgetItem();
|
|
QTableWidgetItem* sensor2Item = new QTableWidgetItem();
|
|
|
|
ui->powerTable->setItem(row, POWER_COL_DATE, dateItem);
|
|
ui->powerTable->setItem(row, POWER_COL_TIME, timeItem);
|
|
ui->powerTable->setItem(row, POWER_COL_POWER, powerItem);
|
|
ui->powerTable->setItem(row, POWER_COL_POWER_DB, powerDBItem);
|
|
ui->powerTable->setItem(row, POWER_COL_POWER_DBM, powerdBmItem);
|
|
ui->powerTable->setItem(row, POWER_COL_TSYS, tSysItem);
|
|
ui->powerTable->setItem(row, POWER_COL_TSYS0, tSys0Item);
|
|
ui->powerTable->setItem(row, POWER_COL_TSOURCE, tSourceItem);
|
|
ui->powerTable->setItem(row, POWER_COL_TB, tBItem);
|
|
ui->powerTable->setItem(row, POWER_COL_TSKY, tSkyItem);
|
|
ui->powerTable->setItem(row, POWER_COL_FLUX, fluxItem);
|
|
ui->powerTable->setItem(row, POWER_COL_SIGMA_T, sigmaTItem);
|
|
ui->powerTable->setItem(row, POWER_COL_SIGMA_S, sigmaSItem);
|
|
ui->powerTable->setItem(row, POWER_COL_OMEGA_A, omegaAItem);
|
|
ui->powerTable->setItem(row, POWER_COL_OMEGA_S, omegaSItem);
|
|
ui->powerTable->setItem(row, POWER_COL_RA, raItem);
|
|
ui->powerTable->setItem(row, POWER_COL_DEC, decItem);
|
|
ui->powerTable->setItem(row, POWER_COL_GAL_LON, lonItem);
|
|
ui->powerTable->setItem(row, POWER_COL_GAL_LAT, latItem);
|
|
ui->powerTable->setItem(row, POWER_COL_AZ, azItem);
|
|
ui->powerTable->setItem(row, POWER_COL_EL, elItem);
|
|
ui->powerTable->setItem(row, POWER_COL_VBCRS, vBCRSItem);
|
|
ui->powerTable->setItem(row, POWER_COL_VLSR, vLSRItem);
|
|
ui->powerTable->setItem(row, POWER_COL_SOLAR_FLUX, solarFluxItem);
|
|
ui->powerTable->setItem(row, POWER_COL_AIR_TEMP, airTempItem);
|
|
ui->powerTable->setItem(row, POWER_COL_SENSOR_1, sensor1Item);
|
|
ui->powerTable->setItem(row, POWER_COL_SENSOR_2, sensor2Item);
|
|
|
|
ui->powerTable->setSortingEnabled(true);
|
|
|
|
QDateTime dateTime = fft->m_dateTime;
|
|
dateItem->setData(Qt::DisplayRole, dateTime.date());
|
|
timeItem->setData(Qt::DisplayRole, dateTime.time());
|
|
|
|
powerItem->setData(Qt::DisplayRole, fft->m_totalPower);
|
|
powerDBItem->setData(Qt::DisplayRole, fft->m_totalPowerdBFS);
|
|
if (fft->m_tSys != 0.0f) {
|
|
powerdBmItem->setData(Qt::DisplayRole, fft->m_totalPowerdBm);
|
|
}
|
|
if (fft->m_temp) {
|
|
updatePowerColumns(row, fft);
|
|
}
|
|
if (fft->m_coordsValid)
|
|
{
|
|
raItem->setData(Qt::DisplayRole, fft->m_ra);
|
|
decItem->setData(Qt::DisplayRole, fft->m_dec);
|
|
latItem->setData(Qt::DisplayRole, fft->m_b);
|
|
lonItem->setData(Qt::DisplayRole, fft->m_l);
|
|
azItem->setData(Qt::DisplayRole, fft->m_azimuth);
|
|
elItem->setData(Qt::DisplayRole, fft->m_elevation);
|
|
vBCRSItem->setData(Qt::DisplayRole, fft->m_vBCRS);
|
|
vLSRItem->setData(Qt::DisplayRole, fft->m_vLSR);
|
|
tSkyItem->setData(Qt::DisplayRole, fft->m_skyTemp);
|
|
}
|
|
solarFluxItem->setData(Qt::DisplayRole, fft->m_solarFlux);
|
|
airTempItem->setData(Qt::DisplayRole, fft->m_airTemp);
|
|
sensor1Item->setData(Qt::DisplayRole, fft->m_sensor[0]);
|
|
sensor2Item->setData(Qt::DisplayRole, fft->m_sensor[1]);
|
|
|
|
addToPowerSeries(fft, skipCalcs);
|
|
}
|
|
|
|
void RadioAstronomyGUI::powerAutoscale()
|
|
{
|
|
if (m_settings.m_powerAutoscale)
|
|
{
|
|
on_powerAutoscaleX_clicked();
|
|
on_powerAutoscaleY_clicked();
|
|
}
|
|
}
|
|
|
|
// Scale X and Y axis according to min and max values
|
|
void RadioAstronomyGUI::on_powerAutoscale_toggled(bool checked)
|
|
{
|
|
m_settings.m_powerAutoscale = checked;
|
|
ui->powerAutoscaleX->setEnabled(!m_settings.m_powerAutoscale);
|
|
ui->powerAutoscaleY->setEnabled(!m_settings.m_powerAutoscale);
|
|
ui->powerReference->setEnabled(!m_settings.m_powerAutoscale);
|
|
ui->powerRange->setEnabled(!m_settings.m_powerAutoscale);
|
|
ui->powerStartTime->setEnabled(!m_settings.m_powerAutoscale);
|
|
ui->powerEndTime->setEnabled(!m_settings.m_powerAutoscale);
|
|
powerAutoscale();
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::powerAutoscaleY(bool adjustAxis)
|
|
{
|
|
double min = m_powerMin;
|
|
double max = m_powerMax;
|
|
double range = max - min;
|
|
// Round to 1 or 2 decimal places
|
|
if (range > 1.0)
|
|
{
|
|
min = std::floor(min * 10.0) / 10.0;
|
|
max = std::ceil(max * 10.0) / 10.0;
|
|
}
|
|
else
|
|
{
|
|
min = std::floor(min * 100.0) / 100.0;
|
|
max = std::ceil(max * 100.0) / 100.0;
|
|
}
|
|
range = max - min;
|
|
max += range * 0.2; // Add 20% space for markers
|
|
range = max - min;
|
|
range = std::max(0.1, range); // Don't be smaller than minimum value we can set in GUI
|
|
|
|
if (adjustAxis) {
|
|
m_powerYAxis->setRange(min, max);
|
|
}
|
|
ui->powerRange->setValue(range); // Call before setting reference, so number of decimals are adjusted
|
|
ui->powerReference->setValue(max);
|
|
}
|
|
|
|
// Scale Y axis according to min and max values
|
|
void RadioAstronomyGUI::on_powerAutoscaleY_clicked()
|
|
{
|
|
if (m_powerYAxis) {
|
|
powerAutoscaleY(true);
|
|
}
|
|
}
|
|
|
|
// Scale X axis according to min and max values in series
|
|
void RadioAstronomyGUI::on_powerAutoscaleX_clicked()
|
|
{
|
|
if (m_powerSeries && (m_powerSeries->count() > 0))
|
|
{
|
|
QDateTime start = QDateTime::fromMSecsSinceEpoch(m_powerSeries->at(0).x());
|
|
QDateTime end = QDateTime::fromMSecsSinceEpoch(m_powerSeries->at(m_powerSeries->count()-1).x());
|
|
ui->powerStartTime->setDateTime(start);
|
|
ui->powerEndTime->setDateTime(end);
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerReference_valueChanged(double value)
|
|
{
|
|
m_settings.m_powerReference = value;
|
|
if (m_powerYAxis) {
|
|
m_powerYAxis->setRange(m_settings.m_powerReference - m_settings.m_powerRange, m_settings.m_powerReference);
|
|
}
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerRange_valueChanged(double value)
|
|
{
|
|
m_settings.m_powerRange = value;
|
|
if (m_settings.m_powerRange <= 1.0)
|
|
{
|
|
ui->powerRange->setSingleStep(0.1);
|
|
ui->powerRange->setDecimals(2);
|
|
ui->powerReference->setDecimals(2);
|
|
}
|
|
else
|
|
{
|
|
ui->powerRange->setSingleStep(1.0);
|
|
ui->powerRange->setDecimals(1);
|
|
ui->powerReference->setDecimals(1);
|
|
}
|
|
if (m_powerYAxis) {
|
|
m_powerYAxis->setRange(m_settings.m_powerReference - m_settings.m_powerRange, m_settings.m_powerReference);
|
|
}
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerStartTime_dateTimeChanged(QDateTime value)
|
|
{
|
|
if (m_powerXAxis) {
|
|
m_powerXAxis->setMin(value);
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerEndTime_dateTimeChanged(QDateTime value)
|
|
{
|
|
if (m_powerXAxis) {
|
|
m_powerXAxis->setMax(value);
|
|
}
|
|
}
|
|
|
|
// Columns in table reordered
|
|
void RadioAstronomyGUI::powerTable_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex)
|
|
{
|
|
(void) oldVisualIndex;
|
|
|
|
m_settings.m_powerTableColumnIndexes[logicalIndex] = newVisualIndex;
|
|
}
|
|
|
|
// Column in table resized (when hidden size is 0)
|
|
void RadioAstronomyGUI::powerTable_sectionResized(int logicalIndex, int oldSize, int newSize)
|
|
{
|
|
(void) oldSize;
|
|
|
|
m_settings.m_powerTableColumnSizes[logicalIndex] = newSize;
|
|
}
|
|
|
|
// Right click in table header - show column select menu
|
|
void RadioAstronomyGUI::powerTableColumnSelectMenu(QPoint pos)
|
|
{
|
|
powerTableMenu->popup(ui->powerTable->horizontalHeader()->viewport()->mapToGlobal(pos));
|
|
}
|
|
|
|
// Hide/show column when menu selected
|
|
void RadioAstronomyGUI::powerTableColumnSelectMenuChecked(bool checked)
|
|
{
|
|
(void) checked;
|
|
|
|
QAction* action = qobject_cast<QAction*>(sender());
|
|
if (action != nullptr)
|
|
{
|
|
int idx = action->data().toInt(nullptr);
|
|
ui->powerTable->setColumnHidden(idx, !action->isChecked());
|
|
}
|
|
}
|
|
|
|
// Create column select menu item
|
|
QAction *RadioAstronomyGUI::createCheckableItem(QString &text, int idx, bool checked, const char *slot)
|
|
{
|
|
QAction *action = new QAction(text, this);
|
|
action->setCheckable(true);
|
|
action->setChecked(checked);
|
|
action->setData(QVariant(idx));
|
|
connect(action, SIGNAL(triggered()), this, slot);
|
|
return action;
|
|
}
|
|
|
|
RadioAstronomyGUI* RadioAstronomyGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel)
|
|
{
|
|
RadioAstronomyGUI* gui = new RadioAstronomyGUI(pluginAPI, deviceUISet, rxChannel);
|
|
return gui;
|
|
}
|
|
|
|
void RadioAstronomyGUI::destroy()
|
|
{
|
|
delete this;
|
|
}
|
|
|
|
void RadioAstronomyGUI::resetToDefaults()
|
|
{
|
|
m_settings.resetToDefaults();
|
|
displaySettings();
|
|
applySettings(true);
|
|
}
|
|
|
|
QByteArray RadioAstronomyGUI::serialize() const
|
|
{
|
|
return m_settings.serialize();
|
|
}
|
|
|
|
bool RadioAstronomyGUI::deserialize(const QByteArray& data)
|
|
{
|
|
if(m_settings.deserialize(data)) {
|
|
displaySettings();
|
|
applySettings(true);
|
|
return true;
|
|
} else {
|
|
resetToDefaults();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::updateAvailableFeatures()
|
|
{
|
|
QString currentText = ui->starTracker->currentText();
|
|
ui->starTracker->blockSignals(true);
|
|
ui->starTracker->clear();
|
|
|
|
for (const auto& feature : m_availableFeatures) {
|
|
ui->starTracker->addItem(tr("F%1:%2 %3").arg(feature.m_featureSetIndex).arg(feature.m_featureIndex).arg(feature.m_type));
|
|
}
|
|
|
|
if (currentText.isEmpty())
|
|
{
|
|
if (m_availableFeatures.size() > 0) {
|
|
ui->starTracker->setCurrentIndex(0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ui->starTracker->setCurrentIndex(ui->starTracker->findText(currentText));
|
|
}
|
|
|
|
ui->starTracker->blockSignals(false);
|
|
QString newText = ui->starTracker->currentText();
|
|
|
|
if (currentText != newText)
|
|
{
|
|
m_settings.m_starTracker = newText;
|
|
applySettings();
|
|
}
|
|
}
|
|
|
|
bool RadioAstronomyGUI::handleMessage(const Message& message)
|
|
{
|
|
if (DSPSignalNotification::match(message))
|
|
{
|
|
DSPSignalNotification& notif = (DSPSignalNotification&) message;
|
|
m_basebandSampleRate = notif.getSampleRate();
|
|
m_centerFrequency = notif.getCenterFrequency();
|
|
ui->deltaFrequency->setValueRange(false, 7, -m_basebandSampleRate/2, m_basebandSampleRate/2);
|
|
ui->deltaFrequencyLabel->setToolTip(tr("Range %1 %L2 Hz").arg(QChar(0xB1)).arg(m_basebandSampleRate/2));
|
|
updateAbsoluteCenterFrequency();
|
|
|
|
if (m_settings.m_tempGalLink) {
|
|
calcGalacticBackgroundTemp();
|
|
}
|
|
|
|
updateTSys0();
|
|
|
|
return true;
|
|
}
|
|
else if (RadioAstronomy::MsgReportAvailableFeatures::match(message))
|
|
{
|
|
qDebug("RadioAstronomyGUI::handleMessage: MsgReportAvailableFeatures");
|
|
RadioAstronomy::MsgReportAvailableFeatures& report = (RadioAstronomy::MsgReportAvailableFeatures&) message;
|
|
m_availableFeatures = report.getFeatures();
|
|
updateAvailableFeatures();
|
|
return true;
|
|
}
|
|
else if (MainCore::MsgStarTrackerTarget::match(message))
|
|
{
|
|
MainCore::MsgStarTrackerTarget& msg = (MainCore::MsgStarTrackerTarget&)message;
|
|
SWGSDRangel::SWGStarTrackerTarget *target = msg.getSWGStarTrackerTarget();
|
|
m_coordsValid = true;
|
|
m_ra = target->getRa();
|
|
m_dec = target->getDec();
|
|
m_azimuth = target->getAzimuth();
|
|
m_elevation = target->getElevation();
|
|
m_l = target->getL();
|
|
m_b = target->getB();
|
|
m_vBCRS = target->getEarthRotationVelocity() + target->getEarthOrbitVelocityBcrs();
|
|
m_vLSR = target->getSunVelocityLsr() + m_vBCRS;
|
|
m_solarFlux = target->getSolarFlux();
|
|
double airTemp = target->getAirTemperature();
|
|
m_skyTemp = target->getSkyTemperature();
|
|
m_beamWidth = target->getHpbw();
|
|
|
|
if (m_settings.m_elevationLink) {
|
|
ui->elevation->setValue(m_elevation);
|
|
}
|
|
if (m_settings.m_tempAirLink) {
|
|
ui->tempAir->setValue(airTemp);
|
|
}
|
|
SensorMeasurement* sm = new SensorMeasurement(QDateTime::currentDateTime(), airTemp);
|
|
m_airTemps.append(sm);
|
|
updateTSys0();
|
|
updateOmegaA();
|
|
|
|
return true;
|
|
}
|
|
else if (RadioAstronomy::MsgConfigureRadioAstronomy::match(message))
|
|
{
|
|
const RadioAstronomy::MsgConfigureRadioAstronomy& cfg = (RadioAstronomy::MsgConfigureRadioAstronomy&) message;
|
|
m_settings = cfg.getSettings();
|
|
blockApplySettings(true);
|
|
m_channelMarker.updateSettings(static_cast<const ChannelMarker*>(m_settings.m_channelMarker));
|
|
displaySettings();
|
|
blockApplySettings(false);
|
|
return true;
|
|
}
|
|
else if (RadioAstronomy::MsgMeasurementProgress::match(message))
|
|
{
|
|
RadioAstronomy::MsgMeasurementProgress& progress = (RadioAstronomy::MsgMeasurementProgress&) message;
|
|
ui->measurementProgress->setValue(progress.getPercentComplete());
|
|
return true;
|
|
}
|
|
else if (RadioAstronomy::MsgSweepStatus::match(message))
|
|
{
|
|
RadioAstronomy::MsgSweepStatus& status = (RadioAstronomy::MsgSweepStatus&) message;
|
|
ui->sweepStatus->setText(status.getStatus());
|
|
return true;
|
|
}
|
|
else if (RadioAstronomy::MsgSweepComplete::match(message))
|
|
{
|
|
ui->startStop->blockSignals(true);
|
|
ui->startStop->setChecked(false);
|
|
ui->startStop->blockSignals(false);
|
|
ui->startStop->setStyleSheet("QToolButton { background-color : blue; }");
|
|
return true;
|
|
}
|
|
else if (RadioAstronomy::MsgCalComplete::match(message))
|
|
{
|
|
RadioAstronomy::MsgCalComplete& measurement = (RadioAstronomy::MsgCalComplete&) message;
|
|
calCompletetReceived(measurement);
|
|
return true;
|
|
}
|
|
else if (RadioAstronomy::MsgFFTMeasurement::match(message))
|
|
{
|
|
RadioAstronomy::MsgFFTMeasurement& measurement = (RadioAstronomy::MsgFFTMeasurement&) message;
|
|
fftMeasurementReceived(measurement);
|
|
if (m_settings.m_runMode == RadioAstronomySettings::SINGLE)
|
|
{
|
|
ui->startStop->blockSignals(true);
|
|
ui->startStop->setChecked(false);
|
|
ui->startStop->blockSignals(false);
|
|
ui->startStop->setStyleSheet("QToolButton { background-color : blue; }");
|
|
}
|
|
return true;
|
|
}
|
|
else if (RadioAstronomy::MsgSensorMeasurement::match(message))
|
|
{
|
|
RadioAstronomy::MsgSensorMeasurement& measurement = (RadioAstronomy::MsgSensorMeasurement&) message;
|
|
sensorMeasurementReceived(measurement);
|
|
return true;
|
|
}
|
|
else if (RadioAstronomy::MsgReportAvailableRotators::match(message))
|
|
{
|
|
RadioAstronomy::MsgReportAvailableRotators& report = (RadioAstronomy::MsgReportAvailableRotators&) message;
|
|
updateRotatorList(report.getFeatures());
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void RadioAstronomyGUI::handleInputMessages()
|
|
{
|
|
Message* message;
|
|
|
|
while ((message = getInputMessageQueue()->pop()) != 0)
|
|
{
|
|
if (handleMessage(*message))
|
|
{
|
|
delete message;
|
|
}
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::channelMarkerChangedByCursor()
|
|
{
|
|
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
|
|
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::channelMarkerHighlightedByCursor()
|
|
{
|
|
setHighlighted(m_channelMarker.getHighlighted());
|
|
}
|
|
|
|
// Calculate Tsys0 - i.e. receiver noise temperature when there's no source signal, just unwanted noise
|
|
void RadioAstronomyGUI::updateTSys0()
|
|
{
|
|
double tSys0 = calcTSys0();
|
|
ui->tSys0->setText(QString("%1").arg(round(tSys0)));
|
|
double sigmaT = calcSigmaT(tSys0);
|
|
double sigmaS = calcSigmaS(tSys0);
|
|
ui->sigmaTSys0->setText(QString("%1").arg(sigmaT, 0, 'f', 1));
|
|
ui->sigmaSSys0->setText(QString("%1").arg(sigmaS, 0, 'f', 1));
|
|
}
|
|
|
|
// Estimate of system noise temperature due to all sources of unwanted noise, from user settings
|
|
double RadioAstronomyGUI::calcTSys0() const
|
|
{
|
|
return m_settings.m_tempRX + m_settings.m_tempCMB + m_settings.m_tempGal + m_settings.m_tempSP + m_settings.m_tempAtm;
|
|
}
|
|
|
|
// Calculate measurement time
|
|
double RadioAstronomyGUI::calcTau() const
|
|
{
|
|
return m_settings.m_integration / (m_settings.m_sampleRate / (double)m_settings.m_fftSize);
|
|
}
|
|
|
|
double RadioAstronomyGUI::calcTau(const FFTMeasurement* fft) const
|
|
{
|
|
return fft->m_integration / (fft->m_sampleRate / (double)fft->m_fftSize);
|
|
}
|
|
|
|
// Calculate variation in Tsys due to random noise fluctuations, including receiver gain variations
|
|
// Minimum temp we can reliably detect will be ~5x this
|
|
// Uses practical total-power radiometer equation: https://www.cv.nrao.edu/~sransom/web/Ch3.html#E158
|
|
|
|
double RadioAstronomyGUI::calcSigmaT(double tSys) const
|
|
{
|
|
double tau = calcTau();
|
|
return tSys * sqrt(1.0/(m_settings.m_rfBandwidth * tau) + m_settings.m_gainVariation * m_settings.m_gainVariation);
|
|
}
|
|
|
|
double RadioAstronomyGUI::calcSigmaT(const FFTMeasurement* fft) const
|
|
{
|
|
double tau = calcTau(fft);
|
|
return fft->m_tSys * sqrt(1.0/(fft->m_rfBandwidth * tau) + m_settings.m_gainVariation * m_settings.m_gainVariation);
|
|
}
|
|
|
|
// Calculate variations in flux due to random noise fluctuations, including receiver gain variations
|
|
// Minimum flux we can reliably detect will be ~5x this
|
|
|
|
double RadioAstronomyGUI::calcSigmaS(double tSys) const
|
|
{
|
|
double omegaA = hpbwToSteradians(m_beamWidth);
|
|
double lambda = Astronomy::m_speedOfLight / (double)m_centerFrequency;
|
|
double flux = 2.0 * Astronomy::m_boltzmann * tSys * omegaA / (lambda * lambda); // Should we use Aeff here instead?
|
|
double tau = calcTau();
|
|
double sigma = flux * sqrt(1.0/(m_settings.m_rfBandwidth * tau) + m_settings.m_gainVariation * m_settings.m_gainVariation);
|
|
return Units::wattsPerMetrePerHertzToJansky(sigma);
|
|
}
|
|
|
|
double RadioAstronomyGUI::calcSigmaS(const FFTMeasurement* fft) const
|
|
{
|
|
double omegaA = fft->m_omegaA;
|
|
double lambda = Astronomy::m_speedOfLight / (double)fft->m_centerFrequency;
|
|
double flux = 2.0 * Astronomy::m_boltzmann * fft->m_tSys * omegaA / (lambda * lambda); // Should we use Aeff here instead?
|
|
double tau = calcTau(fft);
|
|
double sigma = flux * sqrt(1.0/(fft->m_rfBandwidth * tau) + m_settings.m_gainVariation * m_settings.m_gainVariation);
|
|
return Units::wattsPerMetrePerHertzToJansky(sigma);
|
|
}
|
|
|
|
// Calculate and display how long a single measurement will take
|
|
void RadioAstronomyGUI::updateIntegrationTime()
|
|
{
|
|
double secs = calcTau();
|
|
if (secs >= 60) {
|
|
ui->integrationTime->setText(QString("%1m").arg(secs/60, 0, 'f', 1));
|
|
} else {
|
|
ui->integrationTime->setText(QString("%1s").arg(secs, 0, 'f', 1));
|
|
}
|
|
updateTSys0();
|
|
}
|
|
|
|
// Limit bandwidth to be less than sample rate
|
|
void RadioAstronomyGUI::updateBWLimits()
|
|
{
|
|
qint64 sr = (qint64) m_settings.m_sampleRate;
|
|
int digits = ceil(log10(sr+1));
|
|
ui->rfBW->setValueRange(true, digits, 1000, sr);
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_deltaFrequency_changed(qint64 value)
|
|
{
|
|
m_channelMarker.setCenterFrequency(value);
|
|
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
|
|
updateAbsoluteCenterFrequency();
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_sampleRate_changed(qint64 value)
|
|
{
|
|
float sr = value;
|
|
m_settings.m_sampleRate = sr;
|
|
updateBWLimits();
|
|
updateIntegrationTime();
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_rfBW_changed(qint64 value)
|
|
{
|
|
float bw = value;
|
|
m_channelMarker.setBandwidth(bw);
|
|
m_settings.m_rfBandwidth = bw;
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_integration_changed(qint64 value)
|
|
{
|
|
m_settings.m_integration = value;
|
|
updateIntegrationTime();
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_recalibrate_toggled(bool checked)
|
|
{
|
|
m_settings.m_recalibrate = checked;
|
|
applySettings();
|
|
if (checked) {
|
|
recalibrate();
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_showCalSettings_clicked()
|
|
{
|
|
RadioAstronomyCalibrationDialog dialog(&m_settings);
|
|
if (dialog.exec() == QDialog::Accepted) {
|
|
applySettings();
|
|
}
|
|
}
|
|
|
|
// Start hot calibration
|
|
void RadioAstronomyGUI::on_startCalHot_clicked()
|
|
{
|
|
if (ui->startStop->isChecked()) {
|
|
ui->startStop->click();
|
|
}
|
|
m_radioAstronomy->getInputMessageQueue()->push(RadioAstronomy::MsgStartCal::create(true));
|
|
ui->startCalHot->setStyleSheet("QToolButton { background-color : green; }");
|
|
}
|
|
|
|
// Start cold calibration
|
|
void RadioAstronomyGUI::on_startCalCold_clicked()
|
|
{
|
|
if (ui->startStop->isChecked()) {
|
|
ui->startStop->click();
|
|
}
|
|
m_radioAstronomy->getInputMessageQueue()->push(RadioAstronomy::MsgStartCal::create(false));
|
|
ui->startCalCold->setStyleSheet("QToolButton { background-color : green; }");
|
|
}
|
|
|
|
// Clear all measurements (but not calibration data)
|
|
void RadioAstronomyGUI::clearData()
|
|
{
|
|
ui->powerTable->setRowCount(0);
|
|
m_powerSeries->clear();
|
|
m_powerPeakSeries->clear();
|
|
m_powerMarkerSeries->clear();
|
|
m_powerTsys0Series->clear();
|
|
m_powerFilteredSeries->clear();
|
|
m_airTemps.clear();
|
|
for (int i = 0; i < RADIOASTRONOMY_SENSORS; i++) {
|
|
m_sensors[i].clear();
|
|
}
|
|
for (int row = 0; row < POWER_MARKER_ROWS; row++)
|
|
{
|
|
for (int col = POWER_MARKER_COL_DATE; col <= POWER_MARKER_COL_DELTA_TO; col++)
|
|
{
|
|
ui->powerMarkerTable->item(row, col)->setText("");
|
|
}
|
|
}
|
|
m_powerM1Valid = false;
|
|
m_powerM2Valid = false;
|
|
|
|
qDeleteAll(m_fftMeasurements);
|
|
m_fftMeasurements.clear();
|
|
m_fftSeries->clear();
|
|
m_fftPeakSeries->clear();
|
|
m_fftMarkerSeries->clear();
|
|
for (int row = 0; row < SPECTRUM_MARKER_ROWS; row++)
|
|
{
|
|
for (int col = SPECTRUM_MARKER_COL_FREQ; col <= SPECTRUM_MARKER_COL_D; col++)
|
|
{
|
|
ui->spectrumMarkerTable->item(row, col)->setText("");
|
|
}
|
|
}
|
|
m_spectrumM1Valid = false;
|
|
m_spectrumM2Valid = false;
|
|
clearLoSMarker("Max");
|
|
clearLoSMarker("M1");
|
|
clearLoSMarker("M2");
|
|
|
|
ui->spectrumIndex->setRange(0, 0);
|
|
ui->spectrumDateTime->setDateTime(QDateTime::currentDateTime());
|
|
ui->powerMean->setText("");
|
|
ui->powerRMS->setText("");
|
|
ui->powerSD->setText("");
|
|
plotPowerVsTimeChart(); // To ensure min/max/peaks are reset
|
|
|
|
create2DImage();
|
|
plotPowerChart();
|
|
|
|
ui->measurementProgress->setValue(0);
|
|
ui->sweepStatus->setText("");
|
|
}
|
|
|
|
// Clear calibration data
|
|
void RadioAstronomyGUI::clearCalData()
|
|
{
|
|
delete m_calHot;
|
|
delete m_calCold;
|
|
delete m_calG;
|
|
m_calHot = nullptr;
|
|
m_calCold = nullptr;
|
|
m_calG = nullptr;
|
|
m_calHotSeries->clear();
|
|
m_calColdSeries->clear();
|
|
ui->calAvgDiff->setText("");
|
|
}
|
|
|
|
// deleteRowsComplete should be called after all rows are deleted
|
|
// Returns if the row being deleted is the currently displayed FFT
|
|
bool RadioAstronomyGUI::deleteRow(int row)
|
|
{
|
|
ui->powerTable->removeRow(row);
|
|
delete m_fftMeasurements[row];
|
|
m_fftMeasurements.removeAt(row);
|
|
return row == ui->spectrumIndex->value();
|
|
}
|
|
|
|
// Updates GUI after rows have been deleted
|
|
void RadioAstronomyGUI::deleteRowsComplete(bool deletedCurrent, int next)
|
|
{
|
|
if (m_fftMeasurements.size() == 0)
|
|
{
|
|
clearData();
|
|
}
|
|
else
|
|
{
|
|
if (deletedCurrent) {
|
|
ui->spectrumIndex->setValue(next);
|
|
}
|
|
plotPowerChart();
|
|
powerAutoscale();
|
|
}
|
|
}
|
|
|
|
// Calculate average difference in hot and cold cal data - so we can easily validate results
|
|
void RadioAstronomyGUI::calcCalAvgDiff()
|
|
{
|
|
if ((m_calHot && m_calCold) && (m_calHot->m_fftSize == m_calCold->m_fftSize))
|
|
{
|
|
Real sum = 0.0f;
|
|
for (int i = 0; i < m_calHot->m_fftSize; i++) {
|
|
sum += CalcDb::dbPower(m_calHot->m_fftData[i]) - CalcDb::dbPower(m_calCold->m_fftData[i]);
|
|
}
|
|
Real avg = sum / m_calHot->m_fftSize;
|
|
ui->calAvgDiff->setText(QString::number(avg, 'f', 1));
|
|
}
|
|
else
|
|
{
|
|
ui->calAvgDiff->setText("");
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::calcCalibrationScaleFactors()
|
|
{
|
|
if (m_calHot)
|
|
{
|
|
delete[] m_calG;
|
|
m_calG = new double[m_calHot->m_fftSize];
|
|
// Calculate scaling factors from FFT mag to temperature
|
|
// FIXME: This assumes cal hot is fixed reference temp - E.g. 50Ohm term
|
|
for (int i = 0; i < m_calHot->m_fftSize; i++) {
|
|
m_calG[i] = (m_settings.m_tCalHot + m_settings.m_tempRX) / m_calHot->m_fftData[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::calibrate()
|
|
{
|
|
if (m_calHotSeries)
|
|
{
|
|
calcCalibrationScaleFactors();
|
|
calcCalTrx();
|
|
calcCalTsp();
|
|
|
|
if (m_settings.m_recalibrate)
|
|
{
|
|
// Apply new calibration to existing measurements
|
|
recalibrate();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply calibration to all existing measurements
|
|
void RadioAstronomyGUI::recalibrate()
|
|
{
|
|
for (int i = 0; i < m_fftMeasurements.size(); i++)
|
|
{
|
|
FFTMeasurement* fft = m_fftMeasurements[i];
|
|
// Recalibrate
|
|
calcFFTTemperatures(fft);
|
|
calcFFTTotalTemperature(fft);
|
|
// Update table
|
|
if (fft->m_tSys != 0.0f) {
|
|
ui->powerTable->item(i, POWER_COL_POWER_DBM)->setData(Qt::DisplayRole, fft->m_totalPowerdBm);
|
|
}
|
|
if (fft->m_temp) {
|
|
updatePowerColumns(i, fft);
|
|
}
|
|
}
|
|
// Update charts
|
|
plotFFTMeasurement();
|
|
plotPowerChart();
|
|
}
|
|
|
|
// Calculate Trx using Y-factor method
|
|
void RadioAstronomyGUI::calcCalTrx()
|
|
{
|
|
if ((m_calHot && m_calCold) && (m_calHot->m_fftSize == m_calCold->m_fftSize))
|
|
{
|
|
// y=Ph/Pc
|
|
double sumH = 0.0;
|
|
double sumC = 0.0;
|
|
for (int i = 0; i < m_calHot->m_fftSize; i++)
|
|
{
|
|
sumH += m_calHot->m_fftData[i];
|
|
sumC += m_calCold->m_fftData[i];
|
|
}
|
|
double y = sumH/sumC;
|
|
// Use y to calculate Trx, which should be the same for both calibration points
|
|
double Trx = (m_settings.m_tCalHot - (m_settings.m_tCalCold * y)) / (y - 1.0);
|
|
ui->calYFactor->setText(QString::number(y, 'f', 2));
|
|
ui->calTrx->setText(QString::number(Trx, 'f', 1));
|
|
}
|
|
else
|
|
{
|
|
ui->calYFactor->setText("");
|
|
ui->calTrx->setText("");
|
|
}
|
|
}
|
|
|
|
// Estimate spillover temperature (This is typically very Az/El depenedent as ground noise will vary)
|
|
void RadioAstronomyGUI::calcCalTsp()
|
|
{
|
|
if (!ui->calTrx->text().isEmpty() && !ui->calTsky->text().isEmpty() && !ui->calYFactor->text().isEmpty())
|
|
{
|
|
double Trx = ui->calTrx->text().toDouble();
|
|
double Tsky = ui->calTsky->text().toDouble();
|
|
double y = ui->calYFactor->text().toDouble();
|
|
double atmosphericAbsorbtion = std::exp(-m_settings.m_zenithOpacity/cos(Units::degreesToRadians(90.0f - m_settings.m_elevation)));
|
|
|
|
double Tsp = (m_settings.m_tCalHot + Trx) / y - (Tsky*atmosphericAbsorbtion) - m_settings.m_tempAtm - Trx;
|
|
|
|
ui->calTsp->setText(QString::number(Tsp, 'f', 1));
|
|
}
|
|
else
|
|
{
|
|
ui->calTsp->setText("");
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_clearData_clicked()
|
|
{
|
|
clearData();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_clearCal_clicked()
|
|
{
|
|
clearCalData();
|
|
}
|
|
|
|
// Save power data in table to a CSV file
|
|
void RadioAstronomyGUI::on_savePowerData_clicked()
|
|
{
|
|
// Get filename to save to
|
|
QFileDialog fileDialog(nullptr, "Select file to save data to", "", "*.csv");
|
|
fileDialog.setAcceptMode(QFileDialog::AcceptSave);
|
|
if (fileDialog.exec())
|
|
{
|
|
QStringList fileNames = fileDialog.selectedFiles();
|
|
if (fileNames.size() > 0)
|
|
{
|
|
QFile file(fileNames[0]);
|
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
|
QMessageBox::critical(this, "Radio Astronomy", QString("Failed to open file %1").arg(fileNames[0]));
|
|
return;
|
|
}
|
|
QTextStream out(&file);
|
|
|
|
// Create a CSV file from the values in the table
|
|
for (int i = 0; i < ui->powerTable->horizontalHeader()->count(); i++)
|
|
{
|
|
QString text = ui->powerTable->horizontalHeaderItem(i)->text();
|
|
out << text << ",";
|
|
}
|
|
out << "\n";
|
|
for (int i = 0; i < ui->powerTable->rowCount(); i++)
|
|
{
|
|
for (int j = 0; j < ui->powerTable->horizontalHeader()->count(); j++)
|
|
{
|
|
out << ui->powerTable->item(i,j)->data(Qt::DisplayRole).toString() << ",";
|
|
}
|
|
out << "\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create a hash mapping from column name to array index
|
|
QHash<QString,int> RadioAstronomyGUI::csvHeadersToHash(QStringList cols)
|
|
{
|
|
QHash<QString,int> hash;
|
|
for (int i = 0; i < cols.size(); i++) {
|
|
hash.insert(cols[i], i);
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
// Get data from column with given name, if available
|
|
QString RadioAstronomyGUI::csvData(QHash<QString,int> hash, QStringList cols, QString col)
|
|
{
|
|
QString s;
|
|
if (hash.contains(col))
|
|
{
|
|
int idx = hash[col];
|
|
if (idx < cols.size()) {
|
|
s = cols[idx];
|
|
}
|
|
}
|
|
return s;
|
|
}
|
|
|
|
bool RadioAstronomyGUI::hasNeededFFTData(QHash<QString,int> hash)
|
|
{
|
|
return hash.contains("FFT Size") && hash.contains("Data");
|
|
}
|
|
|
|
// Write FFTMeasurement to a stream
|
|
void RadioAstronomyGUI::saveFFT(QTextStream& out, const FFTMeasurement* fft)
|
|
{
|
|
out << fft->m_dateTime.toString();
|
|
out << ",";
|
|
out << fft->m_centerFrequency;
|
|
out << ",";
|
|
out << fft->m_sampleRate;
|
|
out << ",";
|
|
out << fft->m_integration;
|
|
out << ",";
|
|
out << fft->m_rfBandwidth;
|
|
out << ",";
|
|
out << fft->m_omegaA;
|
|
out << ",";
|
|
out << fft->m_omegaS;
|
|
out << ",";
|
|
out << fft->m_totalPower;
|
|
out << ",";
|
|
out << fft->m_totalPowerdBFS;
|
|
out << ",";
|
|
out << fft->m_totalPowerdBm;
|
|
out << ",";
|
|
out << fft->m_totalPowerWatts;
|
|
out << ",";
|
|
out << fft->m_tSys;
|
|
out << ",";
|
|
out << fft->m_tSys0;
|
|
out << ",";
|
|
out << fft->m_tSource;
|
|
out << ",";
|
|
out << fft->m_flux;
|
|
out << ",";
|
|
out << fft->m_sigmaT;
|
|
out << ",";
|
|
out << fft->m_sigmaS;
|
|
out << ",";
|
|
out << fft->m_tempMin;
|
|
out << ",";
|
|
out << fft->m_baseline;
|
|
out << ",";
|
|
out << fft->m_ra;
|
|
out << ",";
|
|
out << fft->m_dec;
|
|
out << ",";
|
|
out << fft->m_azimuth;
|
|
out << ",";
|
|
out << fft->m_elevation;
|
|
out << ",";
|
|
out << fft->m_l;
|
|
out << ",";
|
|
out << fft->m_b;
|
|
out << ",";
|
|
out << fft->m_vBCRS;
|
|
out << ",";
|
|
out << fft->m_vLSR;
|
|
out << ",";
|
|
out << fft->m_solarFlux;
|
|
out << ",";
|
|
out << fft->m_airTemp;
|
|
out << ",";
|
|
out << fft->m_skyTemp;
|
|
out << ",";
|
|
out << fft->m_sensor[0];
|
|
out << ",";
|
|
out << fft->m_sensor[1];
|
|
out << ",";
|
|
out << fft->m_fftSize;
|
|
out << ",";
|
|
for (int j = 0; j < fft->m_fftSize; j++)
|
|
{
|
|
out << fft->m_fftData[j];
|
|
out << ",";
|
|
}
|
|
if (fft->m_snr)
|
|
{
|
|
for (int j = 0; j < fft->m_fftSize; j++)
|
|
{
|
|
out << fft->m_snr[j];
|
|
out << ",";
|
|
}
|
|
}
|
|
if (fft->m_temp)
|
|
{
|
|
for (int j = 0; j < fft->m_fftSize; j++)
|
|
{
|
|
out << fft->m_temp[j];
|
|
out << ",";
|
|
}
|
|
}
|
|
out << "\n";
|
|
}
|
|
|
|
// Create a FFTMeasurement from data read from CSV file
|
|
RadioAstronomyGUI::FFTMeasurement* RadioAstronomyGUI::loadFFT(QHash<QString,int> hash, QStringList cols)
|
|
{
|
|
int fftSize = csvData(hash, cols, "FFT Size").toInt();
|
|
int fftDataIdx = hash["Data"];
|
|
if ((fftSize > 0) && (cols.size() >= fftDataIdx + fftSize))
|
|
{
|
|
FFTMeasurement* fft = new FFTMeasurement();
|
|
fft->m_dateTime = QDateTime::fromString(csvData(hash, cols, "Date Time"));
|
|
fft->m_centerFrequency = csvData(hash, cols, "Centre Freq").toLongLong();
|
|
fft->m_sampleRate = csvData(hash, cols, "Sample Rate").toInt();
|
|
fft->m_integration = csvData(hash, cols, "Integration").toInt();
|
|
fft->m_rfBandwidth = csvData(hash, cols, "Bandwidth").toInt();
|
|
fft->m_omegaA = csvData(hash, cols, "OmegaA").toFloat();
|
|
fft->m_omegaS = csvData(hash, cols, "OmegaS").toFloat();
|
|
|
|
fft->m_fftSize = fftSize;
|
|
fft->m_fftData = new Real[fftSize];
|
|
fft->m_db = new Real[fftSize];
|
|
for (int i = 0; i < fftSize; i++)
|
|
{
|
|
fft->m_fftData[i] = cols[fftDataIdx+i].toFloat();
|
|
fft->m_db[i] = (Real)CalcDb::dbPower(fft->m_fftData[i]);
|
|
}
|
|
if (cols.size() >= fftDataIdx + 2*fftSize)
|
|
{
|
|
fft->m_snr = new Real[fftSize];
|
|
for (int i = 0; i < fftSize; i++) {
|
|
fft->m_snr[i] = cols[fftDataIdx+fftSize+i].toFloat();
|
|
}
|
|
if (cols.size() >= fftDataIdx + 3*fftSize)
|
|
{
|
|
fft->m_temp = new Real[fftSize];
|
|
for (int i = 0; i < fftSize; i++) {
|
|
fft->m_temp[i] = cols[fftDataIdx+2*fftSize+i].toFloat();
|
|
}
|
|
}
|
|
}
|
|
fft->m_totalPower = csvData(hash, cols, "Power (FFT)").toFloat();
|
|
fft->m_totalPowerdBFS = csvData(hash, cols, "Power (dBFS)").toFloat();
|
|
fft->m_totalPowerdBm = csvData(hash, cols, "Power (dBm)").toFloat();
|
|
fft->m_totalPowerWatts = csvData(hash, cols, "Power (Watts)").toFloat();
|
|
fft->m_tSys = csvData(hash, cols, "Tsys").toFloat();
|
|
fft->m_tSys0 = csvData(hash, cols, "Tsys0").toFloat();
|
|
fft->m_tSource = csvData(hash, cols, "Tsource").toFloat();
|
|
fft->m_flux = csvData(hash, cols, "Sv").toFloat();
|
|
fft->m_sigmaT = csvData(hash, cols, "SigmaTsys").toFloat();
|
|
fft->m_sigmaS = csvData(hash, cols, "SigmaSsys").toFloat();
|
|
fft->m_tempMin = csvData(hash, cols, "Min Temp").toFloat();
|
|
fft->m_baseline = (RadioAstronomySettings::SpectrumBaseline)csvData(hash, cols, "Baseline").toInt();
|
|
|
|
|
|
fft->m_ra = csvData(hash, cols, "RA").toFloat();
|
|
fft->m_dec = csvData(hash, cols, "Dec").toFloat();
|
|
fft->m_azimuth = csvData(hash, cols, "Azimuth").toFloat();
|
|
fft->m_elevation = csvData(hash, cols, "Elevation").toFloat();
|
|
fft->m_l = csvData(hash, cols, "l").toFloat();
|
|
fft->m_b = csvData(hash, cols, "b").toFloat();
|
|
if ((fft->m_ra != 0.0) || (fft->m_dec != 0.0) || (fft->m_azimuth != 0.0) || (fft->m_elevation != 0.0) || (fft->m_l != 0.0) || (fft->m_b != 0.0)) {
|
|
fft->m_coordsValid = true;
|
|
}
|
|
fft->m_vBCRS = csvData(hash, cols, "vBCRS").toFloat();
|
|
fft->m_vLSR = csvData(hash, cols, "vLSR").toFloat();
|
|
fft->m_solarFlux = csvData(hash, cols, "Solar Flux").toFloat();
|
|
fft->m_airTemp = csvData(hash, cols, "Air Temp").toFloat();
|
|
fft->m_skyTemp = csvData(hash, cols, "Sky Temp").toFloat();
|
|
fft->m_sensor[0] = csvData(hash, cols, "Sensor 1").toFloat();
|
|
fft->m_sensor[1] = csvData(hash, cols, "Sensor 2").toFloat();
|
|
|
|
if (fft->m_rfBandwidth == 0)
|
|
{
|
|
fft->m_rfBandwidth = 0.9 * fft->m_sampleRate; // Older files don't have this column and we need a value for min
|
|
calcFFTTotalPower(fft);
|
|
/*calcFFTMinTemperature(fft);
|
|
calcFFTTotalTemperature(fft);*/
|
|
}
|
|
return fft;
|
|
}
|
|
else
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_saveSpectrumData_clicked()
|
|
{
|
|
// Get filename to save to
|
|
QFileDialog fileDialog(nullptr, "Select file to save data to", "", "*.csv");
|
|
fileDialog.setAcceptMode(QFileDialog::AcceptSave);
|
|
if (fileDialog.exec())
|
|
{
|
|
QStringList fileNames = fileDialog.selectedFiles();
|
|
if (fileNames.size() > 0)
|
|
{
|
|
QFile file(fileNames[0]);
|
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
|
QMessageBox::critical(this, "Radio Astronomy", QString("Failed to open file %1").arg(fileNames[0]));
|
|
return;
|
|
}
|
|
QTextStream out(&file);
|
|
|
|
if (ui->spectrumChartSelect->currentIndex() == 0)
|
|
{
|
|
// Create a CSV file for all the spectrum data
|
|
out << "Date Time,Centre Freq,Sample Rate,Integration,Bandwidth,OmegaA,OmegaS,Power (FFT),Power (dBFS),Power (dBm),Power (Watts),Tsys,Tsys0,Tsource,Sv,SigmaTsys,SigmaSsys,Min Temp,Baseline,RA,Dec,Azimuth,Elevation,l,b,vBCRS,vLSR,Solar Flux,Air Temp,Sky Temp,Sensor 1,Sensor 2,FFT Size,Data\n";
|
|
for (int i = 0; i < m_fftMeasurements.size(); i++) {
|
|
saveFFT(out, m_fftMeasurements[i]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Create a CSV file for calibration data
|
|
out << "Cal,Cal Temp,Date Time,Centre Freq,Sample Rate,Integration,Bandwidth,OmegaA,OmegaS,Power (FFT),Power (dBFS),Power (dBm),Power (Watts),Tsys,Tsys0,Tsource,Sv,SigmaTsys,SigmaSsys,Min Temp,Baseline,RA,Dec,Azimuth,Elevation,l,b,vBCRS,vLSR,Solar Flux,Air Temp,Sky Temp,Sensor 1,Sensor 2,FFT Size,Data\n";
|
|
if (m_calHot)
|
|
{
|
|
out << "Hot,";
|
|
out << m_settings.m_tCalHot;
|
|
out << ",";
|
|
saveFFT(out, m_calHot);
|
|
}
|
|
if (m_calCold)
|
|
{
|
|
out << "Cold,";
|
|
out << m_settings.m_tCalCold;
|
|
out << ",";
|
|
saveFFT(out, m_calCold);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_loadSpectrumData_clicked()
|
|
{
|
|
// Get filename to load from
|
|
QFileDialog fileDialog(nullptr, "Select file to load data from", "", "*.csv");
|
|
fileDialog.setAcceptMode(QFileDialog::AcceptOpen);
|
|
if (fileDialog.exec())
|
|
{
|
|
QStringList fileNames = fileDialog.selectedFiles();
|
|
if (fileNames.size() > 0)
|
|
{
|
|
QFile file(fileNames[0]);
|
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
QMessageBox::critical(this, "Radio Astronomy", QString("Failed to open file %1").arg(fileNames[0]));
|
|
return;
|
|
}
|
|
|
|
// Get column names
|
|
QTextStream in(&file);
|
|
QString header = in.readLine();
|
|
QStringList colNames = header.split(",");
|
|
QHash<QString,int> hash = csvHeadersToHash(colNames);
|
|
|
|
if (ui->spectrumChartSelect->currentIndex() == 0)
|
|
{
|
|
// Load data from CSV file
|
|
if (hasNeededFFTData(hash))
|
|
{
|
|
// Remove old data - we could support multiple series for comparison
|
|
clearData();
|
|
// Read in FFT data from file
|
|
ui->spectrumIndex->blockSignals(true); // Prevent every spectrum from being displayed
|
|
while (!in.atEnd())
|
|
{
|
|
QString row = in.readLine();
|
|
QStringList cols = row.split(",");
|
|
FFTMeasurement* fft = loadFFT(hash, cols);
|
|
if (fft) {
|
|
addFFT(fft, true);
|
|
}
|
|
}
|
|
ui->spectrumIndex->blockSignals(false);
|
|
// Add data from FFT to sensor measurements
|
|
for (int i = 0; i < m_fftMeasurements.size(); i++)
|
|
{
|
|
SensorMeasurement* sm;
|
|
sm = new SensorMeasurement(m_fftMeasurements[i]->m_dateTime, m_fftMeasurements[i]->m_airTemp);
|
|
m_airTemps.append(sm);
|
|
for (int j = 0; j < RADIOASTRONOMY_SENSORS; j++)
|
|
{
|
|
sm = new SensorMeasurement(m_fftMeasurements[i]->m_dateTime, m_fftMeasurements[i]->m_sensor[j]);
|
|
m_sensors[j].append(sm);
|
|
}
|
|
}
|
|
// If we're loading data from scratch, autoscale both axis
|
|
if ((ui->spectrumCenterFreq->value() == 0.0) || m_settings.m_spectrumAutoscale)
|
|
{
|
|
on_spectrumAutoscaleY_clicked();
|
|
on_spectrumAutoscaleX_clicked();
|
|
}
|
|
// Ensure both charts are redrawn fully, as we've disabled some updates/calcs during load
|
|
on_spectrumIndex_valueChanged(m_fftMeasurements.size() - 1); // Don't call setValue, as it already has this value
|
|
plotPowerChart();
|
|
// As signals were blocked above, power axis may not match up with GUI. Manually update
|
|
// Just calling autoscale will not work, as the GUI values may not change
|
|
on_powerStartTime_dateTimeChanged(ui->powerStartTime->dateTime());
|
|
on_powerEndTime_dateTimeChanged(ui->powerEndTime->dateTime());
|
|
on_powerRange_valueChanged(m_settings.m_powerRange);
|
|
on_powerReference_valueChanged(m_settings.m_powerReference);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Load calibration data from CSV file
|
|
if (hasNeededFFTData(hash) && hash.contains("Cal"))
|
|
{
|
|
while (!in.atEnd())
|
|
{
|
|
QString row = in.readLine();
|
|
QStringList cols = row.split(",");
|
|
|
|
QString calName = csvData(hash, cols, "Cal");
|
|
|
|
FFTMeasurement** calp = nullptr;
|
|
FFTMeasurement* cal = nullptr;
|
|
if (calName == "Hot") {
|
|
calp = &m_calHot;
|
|
} else if (calName == "Cold") {
|
|
calp = &m_calCold;
|
|
} else {
|
|
qDebug() << "RadioAstronomyGUI::on_loadSpectrumData_clicked: Skipping unknown calibration " << calName;
|
|
}
|
|
if (calp)
|
|
{
|
|
cal = loadFFT(hash, cols);
|
|
if (cal)
|
|
{
|
|
delete *calp;
|
|
*calp = cal;
|
|
qDebug() << "RadioAstronomyGUI::on_loadSpectrumData_clicked: Loaded calibration " << calName;
|
|
if (calName == "Cold") {
|
|
ui->calTsky->setText(QString::number(cal->m_skyTemp, 'f', 1));
|
|
}
|
|
QString calTempString = csvData(hash, cols, "Cal Temp");
|
|
bool ok;
|
|
double calTemp = calTempString.toDouble(&ok);
|
|
if (ok)
|
|
{
|
|
if (calName == "Cold")
|
|
{
|
|
ui->tCalColdSelect->setCurrentIndex(0);
|
|
ui->tCalCold->setValue(calTemp);
|
|
}
|
|
else
|
|
{
|
|
ui->tCalHotSelect->setCurrentIndex(0);
|
|
ui->tCalHot->setValue(calTemp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
calcCalAvgDiff();
|
|
calibrate();
|
|
plotCalMeasurements();
|
|
}
|
|
else
|
|
{
|
|
QMessageBox::critical(this, "Radio Astronomy", QString("Missing required columns in file %1").arg(fileNames[0]));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerTable_cellDoubleClicked(int row, int column)
|
|
{
|
|
if ((column >= POWER_COL_RA) && (column >= POWER_COL_EL))
|
|
{
|
|
// Display target in Star Tracker
|
|
QList<ObjectPipe*> starTrackerPipes;
|
|
MainCore::instance()->getMessagePipes().getMessagePipes(this, "startracker.display", starTrackerPipes);
|
|
|
|
for (const auto& pipe : starTrackerPipes)
|
|
{
|
|
MessageQueue *messageQueue = qobject_cast<MessageQueue*>(pipe->m_element);
|
|
SWGSDRangel::SWGStarTrackerDisplaySettings *swgSettings = new SWGSDRangel::SWGStarTrackerDisplaySettings();
|
|
QDateTime dt(ui->powerTable->item(row, POWER_COL_DATE)->data(Qt::DisplayRole).toDate(),
|
|
ui->powerTable->item(row, POWER_COL_TIME)->data(Qt::DisplayRole).toTime());
|
|
swgSettings->setDateTime(new QString(dt.toString(Qt::ISODateWithMs)));
|
|
swgSettings->setAzimuth(ui->powerTable->item(row, POWER_COL_AZ)->data(Qt::DisplayRole).toFloat());
|
|
swgSettings->setElevation(ui->powerTable->item(row, POWER_COL_EL)->data(Qt::DisplayRole).toFloat());
|
|
messageQueue->push(MainCore::MsgStarTrackerDisplaySettings::create(m_radioAstronomy, swgSettings));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Display in Spectrometer
|
|
ui->spectrumIndex->setValue(row);
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::onWidgetRolled(QWidget* widget, bool rollDown)
|
|
{
|
|
(void) widget;
|
|
(void) rollDown;
|
|
|
|
getRollupContents()->saveState(m_rollupState);
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::onMenuDialogCalled(const QPoint &p)
|
|
{
|
|
if (m_contextMenuType == ContextMenuChannelSettings)
|
|
{
|
|
BasicChannelSettingsDialog dialog(&m_channelMarker, this);
|
|
dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
|
|
dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
|
|
dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
|
|
dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex);
|
|
dialog.setReverseAPIChannelIndex(m_settings.m_reverseAPIChannelIndex);
|
|
dialog.setDefaultTitle(m_displayedName);
|
|
|
|
if (m_deviceUISet->m_deviceMIMOEngine)
|
|
{
|
|
dialog.setNumberOfStreams(m_radioAstronomy->getNumberOfDeviceStreams());
|
|
dialog.setStreamIndex(m_settings.m_streamIndex);
|
|
}
|
|
|
|
dialog.move(p);
|
|
dialog.exec();
|
|
|
|
m_settings.m_rgbColor = m_channelMarker.getColor().rgb();
|
|
m_settings.m_title = m_channelMarker.getTitle();
|
|
m_settings.m_useReverseAPI = dialog.useReverseAPI();
|
|
m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
|
|
m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
|
|
m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex();
|
|
m_settings.m_reverseAPIChannelIndex = dialog.getReverseAPIChannelIndex();
|
|
|
|
setWindowTitle(m_settings.m_title);
|
|
setTitle(m_channelMarker.getTitle());
|
|
setTitleColor(m_settings.m_rgbColor);
|
|
|
|
if (m_deviceUISet->m_deviceMIMOEngine)
|
|
{
|
|
m_settings.m_streamIndex = dialog.getSelectedStreamIndex();
|
|
m_channelMarker.clearStreamIndexes();
|
|
m_channelMarker.addStreamIndex(m_settings.m_streamIndex);
|
|
updateIndexLabel();
|
|
}
|
|
|
|
applySettings();
|
|
}
|
|
|
|
resetContextMenuType();
|
|
}
|
|
|
|
RadioAstronomyGUI::RadioAstronomyGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) :
|
|
ChannelGUI(parent),
|
|
ui(new Ui::RadioAstronomyGUI),
|
|
m_pluginAPI(pluginAPI),
|
|
m_deviceUISet(deviceUISet),
|
|
m_channelMarker(this),
|
|
m_deviceCenterFrequency(0),
|
|
m_doApplySettings(true),
|
|
m_basebandSampleRate(0),
|
|
m_centerFrequency(0),
|
|
m_tickCount(0),
|
|
m_powerChart(nullptr),
|
|
m_powerSeries(nullptr),
|
|
m_powerXAxis(nullptr),
|
|
m_powerYAxis(nullptr),
|
|
m_powerPeakSeries(nullptr),
|
|
m_powerMarkerSeries(nullptr),
|
|
m_powerTsys0Series(nullptr),
|
|
m_powerGaussianSeries(nullptr),
|
|
m_powerFilteredSeries(nullptr),
|
|
m_powerPeakValid(false),
|
|
m_2DChart(nullptr),
|
|
m_2DXAxis(nullptr),
|
|
m_2DYAxis(nullptr),
|
|
m_2DMapIntensity(nullptr),
|
|
m_sweepIndex(0),
|
|
m_calChart(nullptr),
|
|
m_calXAxis(nullptr),
|
|
m_calYAxis(nullptr),
|
|
m_calHotSeries(nullptr),
|
|
m_calColdSeries(nullptr),
|
|
m_calHot(nullptr),
|
|
m_calCold(nullptr),
|
|
m_calG(nullptr),
|
|
m_fftChart(nullptr),
|
|
m_fftSeries(nullptr),
|
|
m_fftHlineSeries(nullptr),
|
|
m_fftPeakSeries(nullptr),
|
|
m_fftMarkerSeries(nullptr),
|
|
m_fftGaussianSeries(nullptr),
|
|
m_fftLABSeries(nullptr),
|
|
m_fftXAxis(nullptr),
|
|
m_fftYAxis(nullptr),
|
|
m_fftDopplerAxis(nullptr),
|
|
m_powerM1Valid(false),
|
|
m_powerM2Valid(false),
|
|
m_spectrumM1Valid(false),
|
|
m_spectrumM2Valid(false),
|
|
m_coordsValid(false),
|
|
m_ra(0.0f),
|
|
m_dec(0.0f),
|
|
m_azimuth(0.0f),
|
|
m_elevation(0.0f),
|
|
m_l(0.0f),
|
|
m_b(0.0f),
|
|
m_vBCRS(0.0f),
|
|
m_vLSR(0.0f),
|
|
m_solarFlux(0.0f),
|
|
m_beamWidth(5.6f),
|
|
m_lLAB(0.0f),
|
|
m_bLAB(0.0f),
|
|
m_downloadingLAB(false),
|
|
m_window(nullptr),
|
|
m_windowSorted(nullptr),
|
|
m_windowIdx(0),
|
|
m_windowCount(0)
|
|
{
|
|
qDebug("RadioAstronomyGUI::RadioAstronomyGUI");
|
|
setAttribute(Qt::WA_DeleteOnClose, true);
|
|
m_helpURL = "plugins/channelrx/radioastronomy/readme.md";
|
|
RollupContents *rollupContents = getRollupContents();
|
|
ui->setupUi(rollupContents);
|
|
setSizePolicy(rollupContents->sizePolicy());
|
|
rollupContents->arrangeRollups();
|
|
connect(rollupContents, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
|
|
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
|
|
|
|
m_radioAstronomy = reinterpret_cast<RadioAstronomy*>(rxChannel);
|
|
m_radioAstronomy->setMessageQueueToGUI(getInputMessageQueue());
|
|
|
|
connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); // 50 ms
|
|
|
|
m_networkManager = new QNetworkAccessManager();
|
|
QObject::connect(
|
|
m_networkManager,
|
|
&QNetworkAccessManager::finished,
|
|
this,
|
|
&RadioAstronomyGUI::networkManagerFinished
|
|
);
|
|
connect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &RadioAstronomyGUI::downloadFinished);
|
|
|
|
ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03)));
|
|
ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
|
|
ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999);
|
|
|
|
// Need to setValue before calling setValueRange, otherwise valueChanged is called
|
|
// overwriting the default settings (could also blockSignals)
|
|
// Also, set bandwidth before sampleRate
|
|
ui->rfBW->setColorMapper(ColorMapper(ColorMapper::GrayGold));
|
|
ui->rfBW->setValue(m_settings.m_rfBandwidth);
|
|
ui->rfBW->setValueRange(true, 8, 1000, 99999999);
|
|
ui->sampleRate->setColorMapper(ColorMapper(ColorMapper::GrayGold));
|
|
ui->sampleRate->setValue(m_settings.m_sampleRate);
|
|
ui->sampleRate->setValueRange(true, 8, 100000, 99999999);
|
|
ui->integration->setColorMapper(ColorMapper(ColorMapper::GrayGold));
|
|
ui->integration->setValue(m_settings.m_integration);
|
|
ui->integration->setValueRange(true, 7, 1, 99999999);
|
|
|
|
m_channelMarker.blockSignals(true);
|
|
m_channelMarker.setColor(Qt::yellow);
|
|
m_channelMarker.setBandwidth(m_settings.m_rfBandwidth);
|
|
m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset);
|
|
m_channelMarker.setTitle("Radio Astronomy");
|
|
m_channelMarker.blockSignals(false);
|
|
m_channelMarker.setVisible(true); // activate signal on the last setting only
|
|
|
|
setTitleColor(m_channelMarker.getColor());
|
|
m_settings.setChannelMarker(&m_channelMarker);
|
|
m_settings.setRollupState(&m_rollupState);
|
|
|
|
m_deviceUISet->addChannelMarker(&m_channelMarker);
|
|
|
|
connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor()));
|
|
connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor()));
|
|
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
|
|
|
|
// Resize the table using dummy data
|
|
resizePowerTable();
|
|
// Allow user to reorder columns
|
|
ui->powerTable->horizontalHeader()->setSectionsMovable(true);
|
|
// Allow user to sort table by clicking on headers
|
|
ui->powerTable->setSortingEnabled(true);
|
|
// Add context menu to allow hiding/showing of columns
|
|
powerTableMenu = new QMenu(ui->powerTable);
|
|
for (int i = 0; i < ui->powerTable->horizontalHeader()->count(); i++)
|
|
{
|
|
QString text = ui->powerTable->horizontalHeaderItem(i)->text();
|
|
powerTableMenu->addAction(createCheckableItem(text, i, true, SLOT(powerTableColumnSelectMenuChecked())));
|
|
}
|
|
ui->powerTable->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
connect(ui->powerTable->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(powerTableColumnSelectMenu(QPoint)));
|
|
// Get signals when columns change
|
|
connect(ui->powerTable->horizontalHeader(), SIGNAL(sectionMoved(int, int, int)), SLOT(powerTable_sectionMoved(int, int, int)));
|
|
connect(ui->powerTable->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), SLOT(powerTable_sectionResized(int, int, int)));
|
|
ui->powerTable->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
connect(ui->powerTable, SIGNAL(customContextMenuRequested(QPoint)), SLOT(customContextMenuRequested(QPoint)));
|
|
|
|
ui->powerTable->setItemDelegateForColumn(POWER_COL_TIME, new TimeDelegate());
|
|
//ui->powerTable->setItemDelegateForColumn(POWER_COL_POWER, new DecimalDelegate(6));
|
|
ui->powerTable->setItemDelegateForColumn(POWER_COL_POWER_DB, new DecimalDelegate(1));
|
|
ui->powerTable->setItemDelegateForColumn(POWER_COL_POWER_DBM, new DecimalDelegate(1));
|
|
ui->powerTable->setItemDelegateForColumn(POWER_COL_TSYS, new DecimalDelegate(0));
|
|
ui->powerTable->setItemDelegateForColumn(POWER_COL_TSYS0, new DecimalDelegate(0));
|
|
ui->powerTable->setItemDelegateForColumn(POWER_COL_TSOURCE, new DecimalDelegate(0));
|
|
ui->powerTable->setItemDelegateForColumn(POWER_COL_TB, new DecimalDelegate(0));
|
|
ui->powerTable->setItemDelegateForColumn(POWER_COL_TSKY, new DecimalDelegate(0));
|
|
ui->powerTable->setItemDelegateForColumn(POWER_COL_FLUX, new DecimalDelegate(2));
|
|
ui->powerTable->setItemDelegateForColumn(POWER_COL_SIGMA_T, new DecimalDelegate(2));
|
|
ui->powerTable->setItemDelegateForColumn(POWER_COL_SIGMA_S, new DecimalDelegate(1));
|
|
ui->powerTable->setItemDelegateForColumn(POWER_COL_RA, new HMSDelegate());
|
|
ui->powerTable->setItemDelegateForColumn(POWER_COL_DEC, new DMSDelegate());
|
|
ui->powerTable->setItemDelegateForColumn(POWER_COL_GAL_LAT, new DecimalDelegate(0));
|
|
ui->powerTable->setItemDelegateForColumn(POWER_COL_GAL_LON, new DecimalDelegate(0));
|
|
ui->powerTable->setItemDelegateForColumn(POWER_COL_AZ, new DecimalDelegate(0));
|
|
ui->powerTable->setItemDelegateForColumn(POWER_COL_EL, new DecimalDelegate(0));
|
|
ui->powerTable->setItemDelegateForColumn(POWER_COL_VBCRS, new DecimalDelegate(1));
|
|
ui->powerTable->setItemDelegateForColumn(POWER_COL_VLSR, new DecimalDelegate(1));
|
|
ui->powerTable->setItemDelegateForColumn(POWER_COL_AIR_TEMP, new DecimalDelegate(1));
|
|
|
|
resizeSpectrumMarkerTable();
|
|
ui->spectrumMarkerTable->setItemDelegateForColumn(SPECTRUM_MARKER_COL_FREQ, new DecimalDelegate(6));
|
|
ui->spectrumMarkerTable->setItemDelegateForColumn(SPECTRUM_MARKER_COL_VALUE, new DecimalDelegate(1));
|
|
ui->spectrumMarkerTable->setItemDelegateForColumn(SPECTRUM_MARKER_COL_DELTA_X, new DecimalDelegate(6));
|
|
ui->spectrumMarkerTable->setItemDelegateForColumn(SPECTRUM_MARKER_COL_DELTA_Y, new DecimalDelegate(1));
|
|
ui->spectrumMarkerTable->setItemDelegateForColumn(SPECTRUM_MARKER_COL_VR, new DecimalDelegate(2));
|
|
ui->spectrumMarkerTable->setItemDelegateForColumn(SPECTRUM_MARKER_COL_R, new DecimalDelegate(1));
|
|
ui->spectrumMarkerTable->setItemDelegateForColumn(SPECTRUM_MARKER_COL_D, new DecimalDelegate(1));
|
|
ui->spectrumMarkerTable->setItemDelegateForColumn(SPECTRUM_MARKER_COL_R_MIN, new DecimalDelegate(1));
|
|
ui->spectrumMarkerTable->setItemDelegateForColumn(SPECTRUM_MARKER_COL_V, new DecimalDelegate(1));
|
|
|
|
// Create blank marker table
|
|
ui->spectrumMarkerTable->setRowCount(SPECTRUM_MARKER_ROWS); // 1 peak and two markers
|
|
for (int row = 0; row < SPECTRUM_MARKER_ROWS; row++)
|
|
{
|
|
ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_NAME, new QTableWidgetItem());
|
|
ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_FREQ, new QTableWidgetItem());
|
|
ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_VALUE, new QTableWidgetItem());
|
|
ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_DELTA_X, new QTableWidgetItem());
|
|
ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_DELTA_Y, new QTableWidgetItem());
|
|
ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_DELTA_TO, new QTableWidgetItem());
|
|
ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_VR, new QTableWidgetItem());
|
|
ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_R, new QTableWidgetItem());
|
|
ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_D, new QTableWidgetItem());
|
|
// It seems clearing Qt::ItemIsUserCheckable doesn't remove the checkbox, so once set, we always have it
|
|
QTableWidgetItem* item = new QTableWidgetItem();
|
|
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
|
item->setCheckState(Qt::Unchecked);
|
|
ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_PLOT_MAX, item);
|
|
ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_R_MIN, new QTableWidgetItem());
|
|
ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_V, new QTableWidgetItem());
|
|
}
|
|
ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_PEAK, SPECTRUM_MARKER_COL_NAME)->setText("Max");
|
|
ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_M1, SPECTRUM_MARKER_COL_NAME)->setText("M1");
|
|
ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_M2, SPECTRUM_MARKER_COL_NAME)->setText("M2");
|
|
connect(ui->spectrumMarkerTable, &QTableWidget::itemChanged, this, &RadioAstronomyGUI::spectrumMarkerTableItemChanged);
|
|
|
|
resizePowerMarkerTable();
|
|
ui->powerMarkerTable->setItemDelegateForColumn(POWER_MARKER_COL_TIME, new TimeDelegate());
|
|
ui->powerMarkerTable->setItemDelegateForColumn(POWER_MARKER_COL_VALUE, new DecimalDelegate(1));
|
|
ui->powerMarkerTable->setItemDelegateForColumn(POWER_MARKER_COL_DELTA_X, new TimeDeltaDelegate());
|
|
ui->powerMarkerTable->setItemDelegateForColumn(POWER_MARKER_COL_DELTA_Y, new DecimalDelegate(1));
|
|
|
|
// Create blank marker table
|
|
ui->powerMarkerTable->setRowCount(POWER_MARKER_ROWS); // 1 peak and two markers
|
|
for (int row = 0; row < POWER_MARKER_ROWS; row++)
|
|
{
|
|
ui->powerMarkerTable->setItem(row, POWER_MARKER_COL_NAME, new QTableWidgetItem());
|
|
ui->powerMarkerTable->setItem(row, POWER_MARKER_COL_DATE, new QTableWidgetItem());
|
|
ui->powerMarkerTable->setItem(row, POWER_MARKER_COL_TIME, new QTableWidgetItem());
|
|
ui->powerMarkerTable->setItem(row, POWER_MARKER_COL_VALUE, new QTableWidgetItem());
|
|
ui->powerMarkerTable->setItem(row, POWER_MARKER_COL_DELTA_X, new QTableWidgetItem());
|
|
ui->powerMarkerTable->setItem(row, POWER_MARKER_COL_DELTA_Y, new QTableWidgetItem());
|
|
ui->powerMarkerTable->setItem(row, POWER_MARKER_COL_DELTA_TO, new QTableWidgetItem());
|
|
}
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MAX, POWER_MARKER_COL_NAME)->setText("Max");
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MIN, POWER_MARKER_COL_NAME)->setText("Min");
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_M1, POWER_MARKER_COL_NAME)->setText("M1");
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_M2, POWER_MARKER_COL_NAME)->setText("M2");
|
|
|
|
ui->sweepStartDateTime->setMinimumDateTime(QDateTime::currentDateTime());
|
|
ui->spectrumDateTime->setDateTime(QDateTime::currentDateTime());
|
|
|
|
ui->startStop->setStyleSheet("QToolButton { background-color : blue; }");
|
|
|
|
displaySettings();
|
|
makeUIConnections();
|
|
applySettings(true);
|
|
|
|
create2DImage();
|
|
|
|
plotCalSpectrum();
|
|
plotSpectrum();
|
|
plotPowerChart();
|
|
}
|
|
|
|
void RadioAstronomyGUI::customContextMenuRequested(QPoint pos)
|
|
{
|
|
QTableWidgetItem *item = ui->powerTable->itemAt(pos);
|
|
if (item)
|
|
{
|
|
QMenu* tableContextMenu = new QMenu(ui->powerTable);
|
|
connect(tableContextMenu, &QMenu::aboutToHide, tableContextMenu, &QMenu::deleteLater);
|
|
|
|
// Copy cell contents to clipboard
|
|
QAction* copyAction = new QAction("Copy cell", tableContextMenu);
|
|
const QString text = item->text();
|
|
connect(copyAction, &QAction::triggered, this, [text]()->void {
|
|
QClipboard *clipboard = QGuiApplication::clipboard();
|
|
clipboard->setText(text);
|
|
});
|
|
tableContextMenu->addAction(copyAction);
|
|
|
|
// Delete selected rows
|
|
QAction* delAction = new QAction("Delete rows", tableContextMenu);
|
|
connect(delAction, &QAction::triggered, this, [this]()->void {
|
|
QModelIndexList rowIndexes = ui->powerTable->selectionModel()->selectedRows();
|
|
if (rowIndexes.size() > 0)
|
|
{
|
|
// Delete in reverse row order
|
|
std::vector<int> rows;
|
|
foreach (auto rowIndex, rowIndexes) {
|
|
rows.push_back(rowIndex.row());
|
|
}
|
|
std::sort(rows.begin(), rows.end(), std::greater<int>());
|
|
bool deletedCurrent = false;
|
|
int next;
|
|
foreach (auto row, rows) {
|
|
next = row - 1;
|
|
if (deleteRow(row)) {
|
|
deletedCurrent = true;
|
|
}
|
|
}
|
|
deleteRowsComplete(deletedCurrent, next);
|
|
}
|
|
});
|
|
tableContextMenu->addAction(delAction);
|
|
|
|
// Update rows with new Tsys0 and baseline
|
|
QAction* updateTSysAction = new QAction(QString("Update Tsys0 / baseline / %1").arg(QChar(937)), tableContextMenu);
|
|
connect(updateTSysAction, &QAction::triggered, this, [this]()->void {
|
|
QModelIndexList rowIndexes = ui->powerTable->selectionModel()->selectedRows();
|
|
if (rowIndexes.size() > 0)
|
|
{
|
|
foreach (auto rowIndex, rowIndexes)
|
|
{
|
|
int row = rowIndex.row();
|
|
m_fftMeasurements[row]->m_tSys0 = calcTSys0();
|
|
m_fftMeasurements[row]->m_baseline = m_settings.m_spectrumBaseline;
|
|
m_fftMeasurements[row]->m_omegaA = calcOmegaA();
|
|
m_fftMeasurements[row]->m_omegaS = calcOmegaS();
|
|
calcFFTTotalTemperature(m_fftMeasurements[row]);
|
|
updatePowerColumns(row, m_fftMeasurements[row]);
|
|
}
|
|
plotFFTMeasurement();
|
|
}
|
|
});
|
|
tableContextMenu->addAction(updateTSysAction);
|
|
|
|
tableContextMenu->popup(ui->powerTable->viewport()->mapToGlobal(pos));
|
|
}
|
|
}
|
|
|
|
RadioAstronomyGUI::~RadioAstronomyGUI()
|
|
{
|
|
delete ui;
|
|
delete m_calHot;
|
|
delete m_calCold;
|
|
qDeleteAll(m_dataLAB);
|
|
m_dataLAB.clear();
|
|
delete[] m_2DMapIntensity;
|
|
delete[] m_window;
|
|
delete[] m_windowSorted;
|
|
}
|
|
|
|
void RadioAstronomyGUI::blockApplySettings(bool block)
|
|
{
|
|
m_doApplySettings = !block;
|
|
}
|
|
|
|
void RadioAstronomyGUI::applySettings(bool force)
|
|
{
|
|
if (m_doApplySettings)
|
|
{
|
|
RadioAstronomy::MsgConfigureRadioAstronomy* message = RadioAstronomy::MsgConfigureRadioAstronomy::create( m_settings, force);
|
|
m_radioAstronomy->getInputMessageQueue()->push(message);
|
|
}
|
|
}
|
|
|
|
int RadioAstronomyGUI::fftSizeToIndex(int size)
|
|
{
|
|
switch (size)
|
|
{
|
|
case 16:
|
|
return 0;
|
|
case 32:
|
|
return 1;
|
|
case 64:
|
|
return 2;
|
|
case 128:
|
|
return 3;
|
|
case 256:
|
|
return 4;
|
|
case 512:
|
|
return 5;
|
|
case 1024:
|
|
return 6;
|
|
case 2048:
|
|
return 7;
|
|
case 4096:
|
|
return 8;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void RadioAstronomyGUI::displaySettings()
|
|
{
|
|
m_channelMarker.blockSignals(true);
|
|
m_channelMarker.setBandwidth(m_settings.m_rfBandwidth);
|
|
m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset);
|
|
m_channelMarker.setTitle(m_settings.m_title);
|
|
m_channelMarker.blockSignals(false);
|
|
m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only
|
|
|
|
setTitleColor(m_settings.m_rgbColor);
|
|
setWindowTitle(m_channelMarker.getTitle());
|
|
setTitle(m_channelMarker.getTitle());
|
|
|
|
blockApplySettings(true);
|
|
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
|
|
float rfBW = m_settings.m_rfBandwidth; // Save value, as it may be corrupted when setting sampleRate
|
|
ui->sampleRate->setValue(m_settings.m_sampleRate);
|
|
ui->rfBW->setValue(rfBW);
|
|
updateBWLimits();
|
|
|
|
ui->integration->setValue(m_settings.m_integration);
|
|
ui->fftSize->setCurrentIndex(fftSizeToIndex(m_settings.m_fftSize));
|
|
ui->fftWindow->setCurrentIndex((int)m_settings.m_fftWindow);
|
|
ui->filterFreqs->setText(m_settings.m_filterFreqs);
|
|
|
|
int idx = ui->starTracker->findText(m_settings.m_starTracker);
|
|
if (idx != -1) {
|
|
ui->starTracker->setCurrentIndex(idx);
|
|
}
|
|
idx = ui->rotator->findText(m_settings.m_rotator);
|
|
if (idx != -1) {
|
|
ui->rotator->setCurrentIndex(idx);
|
|
}
|
|
|
|
ui->tempRXSelect->setCurrentIndex(0);
|
|
ui->tempRX->setValue(m_settings.m_tempRX);
|
|
ui->tempRXUnitsLabel->setText("K");
|
|
ui->tempCMB->setValue(m_settings.m_tempCMB);
|
|
ui->tempGal->setValue(m_settings.m_tempGal);
|
|
ui->tempGal->setEnabled(!m_settings.m_tempGalLink);
|
|
ui->tempGalLink->setChecked(m_settings.m_tempGalLink);
|
|
ui->tempSP->setValue(m_settings.m_tempSP);
|
|
ui->tempAtm->setValue(m_settings.m_tempAtm);
|
|
ui->tempAtm->setEnabled(!m_settings.m_tempAtmLink);
|
|
ui->tempAtmLink->setChecked(m_settings.m_tempAtmLink);
|
|
ui->tempAir->setValue(m_settings.m_tempAir);
|
|
ui->tempAir->setEnabled(!m_settings.m_tempAirLink);
|
|
ui->tempAirLink->setChecked(m_settings.m_tempAirLink);
|
|
ui->zenithOpacity->setValue(m_settings.m_zenithOpacity);
|
|
ui->elevation->setValue(m_settings.m_elevation);
|
|
ui->elevation->setEnabled(!m_settings.m_elevationLink);
|
|
ui->elevationLink->setChecked(m_settings.m_elevationLink);
|
|
|
|
ui->gainVariation->setValue(m_settings.m_gainVariation);
|
|
ui->sourceType->setCurrentIndex((int)m_settings.m_sourceType);
|
|
ui->omegaS->setValue(m_settings.m_omegaS);
|
|
ui->omegaSUnits->setCurrentIndex((int)m_settings.m_omegaSUnits);
|
|
ui->omegaAUnits->setCurrentIndex((int)m_settings.m_omegaAUnits);
|
|
|
|
ui->recalibrate->setChecked(m_settings.m_recalibrate);
|
|
ui->tCalHot->setValue(m_settings.m_tCalHot);
|
|
ui->tCalCold->setValue(m_settings.m_tCalCold);
|
|
|
|
ui->spectrumAutoscale->setChecked(m_settings.m_spectrumAutoscale);
|
|
ui->spectrumReference->setValue(m_settings.m_spectrumReference);
|
|
ui->spectrumRange->setValue(m_settings.m_spectrumRange);
|
|
FFTMeasurement* fft = currentFFT();
|
|
if (fft) {
|
|
ui->spectrumCenterFreq->setValue(fft->m_centerFrequency/1e6 + m_settings.m_spectrumCenterFreqOffset);
|
|
} else {
|
|
ui->spectrumCenterFreq->setValue(m_centerFrequency/1e6 + m_settings.m_spectrumCenterFreqOffset);
|
|
}
|
|
ui->spectrumSpan->setValue(m_settings.m_spectrumSpan);
|
|
ui->spectrumYUnits->setCurrentIndex((int)m_settings.m_spectrumYScale);
|
|
ui->spectrumBaseline->setCurrentIndex((int)m_settings.m_spectrumBaseline);
|
|
ui->spectrumAutoscaleX->setEnabled(!m_settings.m_spectrumAutoscale);
|
|
ui->spectrumAutoscaleY->setEnabled(!m_settings.m_spectrumAutoscale);
|
|
ui->spectrumReference->setEnabled(!m_settings.m_spectrumAutoscale);
|
|
ui->spectrumRange->setEnabled(!m_settings.m_spectrumAutoscale);
|
|
ui->spectrumCenterFreq->setEnabled(!m_settings.m_spectrumAutoscale);
|
|
ui->spectrumSpan->setEnabled(!m_settings.m_spectrumAutoscale);
|
|
|
|
ui->powerAutoscale->setChecked(m_settings.m_powerAutoscale);
|
|
ui->powerReference->setValue(m_settings.m_powerReference);
|
|
ui->powerRange->setValue(m_settings.m_powerRange);
|
|
ui->powerShowPeak->setChecked(m_settings.m_powerPeaks);
|
|
if (m_powerPeakSeries)
|
|
{
|
|
m_powerPeakSeries->setVisible(m_settings.m_powerPeaks);
|
|
m_powerChart->legend()->markers(m_powerPeakSeries)[0]->setVisible(false);
|
|
}
|
|
ui->powerShowMarker->setChecked(m_settings.m_powerMarkers);
|
|
if (m_powerMarkerSeries)
|
|
{
|
|
m_powerMarkerSeries->setVisible(m_settings.m_powerMarkers);
|
|
m_powerChart->legend()->markers(m_powerMarkerSeries)[0]->setVisible(false);
|
|
}
|
|
ui->powerShowAvg->setChecked(m_settings.m_powerAvg);
|
|
ui->powerChartAvgWidgets->setVisible(m_settings.m_powerAvg);
|
|
ui->powerShowGaussian->setChecked(m_settings.m_powerShowGaussian);
|
|
ui->powerGaussianWidgets->setVisible(m_settings.m_powerShowGaussian);
|
|
if (m_powerGaussianSeries) {
|
|
m_powerGaussianSeries->setVisible(m_settings.m_powerShowGaussian);
|
|
}
|
|
ui->powerShowLegend->setChecked(m_settings.m_powerLegend);
|
|
if (m_powerChart) {
|
|
m_powerChart->legend()->setVisible(m_settings.m_powerLegend);
|
|
}
|
|
ui->powerChartSelect->setCurrentIndex((int)m_settings.m_powerYData);
|
|
ui->powerYUnits->setCurrentIndex(powerYUnitsToIndex(m_settings.m_powerYUnits));
|
|
ui->powerShowTsys0->setChecked(m_settings.m_powerShowTsys0);
|
|
ui->powerShowAirTemp->setChecked(m_settings.m_powerShowAirTemp);
|
|
m_airTemps.clicked(m_settings.m_powerShowAirTemp);
|
|
ui->powerShowSensor1->setChecked(m_settings.m_sensorVisible[0]);
|
|
m_sensors[0].setName(m_settings.m_sensorName[0]);
|
|
m_sensors[0].clicked(m_settings.m_sensorVisible[0]);
|
|
ui->powerShowSensor2->setChecked(m_settings.m_sensorVisible[1]);
|
|
m_sensors[1].setName(m_settings.m_sensorName[1]);
|
|
m_sensors[1].clicked(m_settings.m_sensorVisible[1]);
|
|
ui->powerShowFiltered->setChecked(m_settings.m_powerShowFiltered);
|
|
if (m_powerFilteredSeries) {
|
|
m_powerFilteredSeries->setVisible(m_settings.m_powerShowFiltered);
|
|
}
|
|
ui->powerFilterWidgets->setVisible(m_settings.m_powerShowFiltered);
|
|
ui->powerFilter->setCurrentIndex((int)m_settings.m_powerFilter);
|
|
ui->powerFilterN->setValue(m_settings.m_powerFilterN);
|
|
ui->powerShowMeasurement->setChecked(m_settings.m_powerShowMeasurement);
|
|
if (m_powerSeries) {
|
|
m_powerSeries->setVisible(m_settings.m_powerShowMeasurement);
|
|
}
|
|
|
|
ui->power2DLinkSweep->setChecked(m_settings.m_power2DLinkSweep);
|
|
ui->power2DSweepType->setCurrentIndex((int)m_settings.m_power2DSweepType);
|
|
ui->power2DWidth->setValue(m_settings.m_power2DWidth);
|
|
ui->power2DHeight->setValue(m_settings.m_power2DHeight);
|
|
ui->power2DXMin->setValue(m_settings.m_power2DXMin);
|
|
ui->power2DXMax->setValue(m_settings.m_power2DXMax);
|
|
ui->power2DYMin->setValue(m_settings.m_power2DYMin);
|
|
ui->power2DYMax->setValue(m_settings.m_power2DYMax);
|
|
ui->powerColourAutoscale->setChecked(m_settings.m_powerColourAutoscale);
|
|
ui->powerColourScaleMin->setValue(m_settings.m_powerColourScaleMin);
|
|
ui->powerColourScaleMin->setEnabled(!m_settings.m_powerColourAutoscale);
|
|
ui->powerColourScaleMax->setValue(m_settings.m_powerColourScaleMax);
|
|
ui->powerColourScaleMax->setEnabled(!m_settings.m_powerColourAutoscale);
|
|
ui->powerColourPalette->setCurrentIndex(ui->powerColourPalette->findText(m_settings.m_powerColourPalette));
|
|
|
|
ui->spectrumReverseXAxis->setChecked(m_settings.m_spectrumReverseXAxis);
|
|
ui->spectrumPeak->setChecked(m_settings.m_spectrumPeaks);
|
|
ui->spectrumMarker->setChecked(m_settings.m_spectrumMarkers);
|
|
ui->spectrumTemp->setChecked(m_settings.m_spectrumTemp);
|
|
if (m_fftGaussianSeries) {
|
|
m_fftGaussianSeries->setVisible(m_settings.m_spectrumTemp);
|
|
}
|
|
ui->spectrumShowRefLine->setChecked(m_settings.m_spectrumRefLine);
|
|
if (m_fftHlineSeries)
|
|
{
|
|
m_fftHlineSeries->setVisible(m_settings.m_spectrumRefLine);
|
|
m_fftDopplerAxis->setVisible(m_settings.m_spectrumRefLine);
|
|
}
|
|
ui->spectrumShowLAB->setChecked(m_settings.m_spectrumLAB);
|
|
if (m_fftLABSeries) {
|
|
m_fftLABSeries->setVisible(m_settings.m_spectrumLAB);
|
|
}
|
|
ui->spectrumShowDistance->setChecked(m_settings.m_spectrumDistance);
|
|
updateDistanceColumns();
|
|
ui->spectrumShowLegend->setChecked(m_settings.m_spectrumLegend);
|
|
if (m_fftChart) {
|
|
m_fftChart->legend()->setVisible(m_settings.m_spectrumLegend);
|
|
}
|
|
if (m_calChart) {
|
|
m_calChart->legend()->setVisible(m_settings.m_spectrumLegend);
|
|
}
|
|
|
|
|
|
ui->refFrame->setCurrentIndex((int)m_settings.m_refFrame);
|
|
ui->spectrumLine->setCurrentIndex((int)m_settings.m_line);
|
|
ui->sunDistanceToGC->setValue(m_settings.m_sunDistanceToGC);
|
|
ui->sunOrbitalVelocity->setValue(m_settings.m_sunOrbitalVelocity);
|
|
displaySpectrumLineFrequency();
|
|
updateSpectrumSelect();
|
|
updatePowerSelect();
|
|
|
|
// Updates visibility of widgets
|
|
updateSpectrumMarkerTableVisibility();
|
|
updatePowerMarkerTableVisibility();
|
|
updatePowerChartWidgetsVisibility();
|
|
updateSpectrumChartWidgetsVisibility();
|
|
|
|
updateIntegrationTime();
|
|
|
|
ui->runMode->setCurrentIndex((int)m_settings.m_runMode);
|
|
ui->sweepStartAtTime->setCurrentIndex(m_settings.m_sweepStartAtTime ? 1 : 0);
|
|
ui->sweepStartDateTime->setDateTime(m_settings.m_sweepStartDateTime);
|
|
ui->sweepStartDateTime->setVisible(m_settings.m_sweepStartAtTime);
|
|
ui->sweepType->setCurrentIndex((int)m_settings.m_sweepType);
|
|
ui->sweep1Start->setValue(m_settings.m_sweep1Start);
|
|
ui->sweep1Stop->setValue(m_settings.m_sweep1Stop);
|
|
ui->sweep1Step->setValue(m_settings.m_sweep1Step);
|
|
ui->sweep1Delay->setValue(m_settings.m_sweep1Delay);
|
|
ui->sweep2Start->setValue(m_settings.m_sweep2Start);
|
|
ui->sweep2Stop->setValue(m_settings.m_sweep2Stop);
|
|
ui->sweep2Step->setValue(m_settings.m_sweep2Step);
|
|
ui->sweep2Delay->setValue(m_settings.m_sweep2Delay);
|
|
displayRunModeSettings();
|
|
|
|
updateIndexLabel();
|
|
|
|
// Order and size columns
|
|
QHeaderView *header = ui->powerTable->horizontalHeader();
|
|
for (int i = 0; i < RADIOASTRONOMY_POWERTABLE_COLUMNS; i++)
|
|
{
|
|
bool hidden = m_settings.m_powerTableColumnSizes[i] == 0;
|
|
header->setSectionHidden(i, hidden);
|
|
powerTableMenu->actions().at(i)->setChecked(!hidden);
|
|
if (m_settings.m_powerTableColumnSizes[i] > 0)
|
|
ui->powerTable->setColumnWidth(i, m_settings.m_powerTableColumnSizes[i]);
|
|
header->moveSection(header->visualIndex(i), m_settings.m_powerTableColumnIndexes[i]);
|
|
}
|
|
|
|
getRollupContents()->restoreState(m_rollupState);
|
|
updateAbsoluteCenterFrequency();
|
|
blockApplySettings(false);
|
|
getRollupContents()->arrangeRollups();
|
|
}
|
|
|
|
void RadioAstronomyGUI::leaveEvent(QEvent* event)
|
|
{
|
|
m_channelMarker.setHighlighted(false);
|
|
ChannelGUI::leaveEvent(event);
|
|
}
|
|
|
|
void RadioAstronomyGUI::enterEvent(QEvent* event)
|
|
{
|
|
m_channelMarker.setHighlighted(true);
|
|
ChannelGUI::enterEvent(event);
|
|
}
|
|
|
|
void RadioAstronomyGUI::tick()
|
|
{
|
|
double magsqAvg, magsqPeak;
|
|
int nbMagsqSamples;
|
|
m_radioAstronomy->getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples);
|
|
double powDbAvg = CalcDb::dbPower(magsqAvg);
|
|
|
|
if (m_tickCount % 4 == 0) {
|
|
ui->channelPower->setText(QString::number(powDbAvg, 'f', 1));
|
|
}
|
|
|
|
m_tickCount++;
|
|
}
|
|
|
|
void RadioAstronomyGUI::updateRotatorList(const QList<RadioAstronomySettings::AvailableFeature>& rotators)
|
|
{
|
|
// Update list of rotators
|
|
ui->rotator->blockSignals(true);
|
|
ui->rotator->clear();
|
|
ui->rotator->addItem("None");
|
|
|
|
for (const auto& rotator : rotators)
|
|
{
|
|
QString name = QString("F%1:%2 %3").arg(rotator.m_featureSetIndex).arg(rotator.m_featureIndex).arg(rotator.m_type);
|
|
ui->rotator->addItem(name);
|
|
}
|
|
|
|
// Rotator feature can be created after this plugin, so select it
|
|
// if the chosen rotator appears
|
|
int rotatorIndex = ui->rotator->findText(m_settings.m_rotator);
|
|
|
|
if (rotatorIndex >= 0) {
|
|
ui->rotator->setCurrentIndex(rotatorIndex);
|
|
} else {
|
|
ui->rotator->setCurrentIndex(0); // return to None
|
|
}
|
|
|
|
ui->rotator->blockSignals(false);
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_fftSize_currentIndexChanged(int index)
|
|
{
|
|
m_settings.m_fftSize = 1 << (4+index);
|
|
applySettings();
|
|
updateIntegrationTime();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_fftWindow_currentIndexChanged(int index)
|
|
{
|
|
m_settings.m_fftWindow = (RadioAstronomySettings::FFTWindow)index;
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_filterFreqs_editingFinished()
|
|
{
|
|
m_settings.m_filterFreqs = ui->filterFreqs->text();
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_gainVariation_valueChanged(double value)
|
|
{
|
|
m_settings.m_gainVariation = value;
|
|
applySettings();
|
|
updateTSys0();
|
|
}
|
|
|
|
// Can we allow user to enter text that is automatically looked up on SIMBAD?
|
|
void RadioAstronomyGUI::on_sourceType_currentIndexChanged(int index)
|
|
{
|
|
m_settings.m_sourceType = (RadioAstronomySettings::SourceType)index;
|
|
applySettings();
|
|
if (m_settings.m_sourceType == RadioAstronomySettings::SUN)
|
|
{
|
|
// Mean diameter of Sun in degrees from Earth
|
|
ui->omegaS->setValue(0.53);
|
|
ui->omegaSUnits->setCurrentIndex(0);
|
|
}
|
|
else if (m_settings.m_sourceType == RadioAstronomySettings::CAS_A)
|
|
{
|
|
// Diameter of Cas A in degrees http://simbad.u-strasbg.fr/simbad/sim-id?Ident=Cassiopeia+A
|
|
ui->omegaS->setValue(0.08333);
|
|
ui->omegaSUnits->setCurrentIndex(0);
|
|
}
|
|
bool visible = index == 1 || index >= 3;
|
|
ui->omegaS->setVisible(visible);
|
|
ui->omegaSUnits->setVisible(visible);
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_omegaS_valueChanged(double value)
|
|
{
|
|
m_settings.m_omegaS = value;
|
|
if ((m_settings.m_sourceType == RadioAstronomySettings::SUN) && (value != 0.53)) {
|
|
ui->sourceType->setCurrentIndex((int)RadioAstronomySettings::COMPACT);
|
|
} else if ((m_settings.m_sourceType == RadioAstronomySettings::CAS_A) && (value != 0.08333)) {
|
|
ui->sourceType->setCurrentIndex((int)RadioAstronomySettings::COMPACT);
|
|
}
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::updateOmegaA()
|
|
{
|
|
if (m_settings.m_omegaAUnits == RadioAstronomySettings::DEGREES) {
|
|
ui->omegaA->setText(QString("%1").arg(m_beamWidth, 0, 'f', 1));
|
|
} else {
|
|
ui->omegaA->setText(QString("%1").arg(hpbwToSteradians(m_beamWidth), 0, 'f', 4));
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_omegaAUnits_currentIndexChanged(int index)
|
|
{
|
|
m_settings.m_omegaAUnits = (RadioAstronomySettings::AngleUnits)index;
|
|
updateOmegaA();
|
|
if (m_settings.m_omegaAUnits == RadioAstronomySettings::DEGREES) {
|
|
ui->omegaALabel->setText("HPBW");
|
|
} else {
|
|
ui->omegaALabel->setText(QString("%1<sub>A</sub>").arg(QChar(937)));
|
|
}
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_omegaSUnits_currentIndexChanged(int index)
|
|
{
|
|
m_settings.m_omegaSUnits = (RadioAstronomySettings::AngleUnits)index;
|
|
if ( ( (m_settings.m_sourceType == RadioAstronomySettings::SUN)
|
|
|| (m_settings.m_sourceType == RadioAstronomySettings::CAS_A)
|
|
)
|
|
&& (m_settings.m_omegaSUnits != RadioAstronomySettings::DEGREES)
|
|
)
|
|
{
|
|
ui->sourceType->setCurrentIndex((int)RadioAstronomySettings::COMPACT);
|
|
}
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_starTracker_currentTextChanged(const QString& text)
|
|
{
|
|
m_settings.m_starTracker = text;
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_rotator_currentTextChanged(const QString& text)
|
|
{
|
|
m_settings.m_rotator = text;
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_showSensors_clicked()
|
|
{
|
|
RadioAstronomySensorDialog dialog(&m_settings);
|
|
if (dialog.exec() == QDialog::Accepted)
|
|
{
|
|
m_sensors[0].setName(m_settings.m_sensorName[0]);
|
|
m_sensors[1].setName(m_settings.m_sensorName[1]);
|
|
applySettings();
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::sensorMeasurementReceived(const RadioAstronomy::MsgSensorMeasurement& measurement)
|
|
{
|
|
int sensor = measurement.getSensor();
|
|
double value = measurement.getValue();
|
|
QDateTime dateTime = measurement.getDateTime();
|
|
SensorMeasurement* sm = new SensorMeasurement(dateTime, value);
|
|
m_sensors[sensor].append(sm);
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerChartSelect_currentIndexChanged(int index)
|
|
{
|
|
m_settings.m_powerYData = (RadioAstronomySettings::PowerYData)index;
|
|
ui->powerYUnits->clear();
|
|
switch (m_settings.m_powerYData)
|
|
{
|
|
case RadioAstronomySettings::PY_POWER:
|
|
ui->powerYUnits->addItem("dBFS");
|
|
ui->powerYUnits->addItem("dBm");
|
|
ui->powerYUnits->addItem("Watts");
|
|
break;
|
|
case RadioAstronomySettings::PY_TSYS:
|
|
case RadioAstronomySettings::PY_TSOURCE:
|
|
ui->powerYUnits->addItem("K");
|
|
break;
|
|
case RadioAstronomySettings::PY_FLUX:
|
|
ui->powerYUnits->addItem("SFU");
|
|
ui->powerYUnits->addItem("Jy");
|
|
break;
|
|
case RadioAstronomySettings::PY_2D_MAP:
|
|
ui->powerYUnits->addItem("dBFS");
|
|
ui->powerYUnits->addItem("dBm");
|
|
//ui->powerYUnits->addItem("Watts"); // No watts for now, as range spin boxes can't handle scientific notation
|
|
ui->powerYUnits->addItem("K");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
updatePowerMarkerTableVisibility();
|
|
updatePowerChartWidgetsVisibility();
|
|
plotPowerChart();
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::updatePowerChartWidgetsVisibility()
|
|
{
|
|
bool powerChart;
|
|
if (m_settings.m_powerYData != RadioAstronomySettings::PY_2D_MAP) {
|
|
powerChart = true;
|
|
} else {
|
|
powerChart = false;
|
|
}
|
|
ui->powerShowLegend->setVisible(powerChart);
|
|
ui->powerShowSensor2->setVisible(powerChart);
|
|
ui->powerShowSensor1->setVisible(powerChart);
|
|
ui->powerShowAirTemp->setVisible(powerChart);
|
|
ui->powerShowTsys0->setVisible(powerChart);
|
|
ui->powerShowAvg->setVisible(powerChart);
|
|
ui->powerShowGaussian->setVisible(powerChart);
|
|
ui->powerShowMarker->setVisible(powerChart);
|
|
ui->powerShowPeak->setVisible(powerChart);
|
|
ui->powerScaleWidgets->setVisible(powerChart);
|
|
ui->powerGaussianWidgets->setVisible(powerChart && m_settings.m_powerShowGaussian);
|
|
ui->powerMarkerTableWidgets->setVisible(powerChart && (m_settings.m_powerPeaks || m_settings.m_powerMarkers));
|
|
ui->power2DScaleWidgets->setVisible(!powerChart);
|
|
ui->power2DColourScaleWidgets->setVisible(!powerChart);
|
|
getRollupContents()->arrangeRollups();
|
|
}
|
|
|
|
int RadioAstronomyGUI::powerYUnitsToIndex(RadioAstronomySettings::PowerYUnits units)
|
|
{
|
|
switch (units)
|
|
{
|
|
case RadioAstronomySettings::PY_DBFS:
|
|
return 0;
|
|
case RadioAstronomySettings::PY_DBM:
|
|
return 1;
|
|
case RadioAstronomySettings::PY_WATTS:
|
|
return 2;
|
|
case RadioAstronomySettings::PY_KELVIN:
|
|
return 0;
|
|
case RadioAstronomySettings::PY_SFU:
|
|
return 0;
|
|
case RadioAstronomySettings::PY_JANSKY:
|
|
return 1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerYUnits_currentIndexChanged(int index)
|
|
{
|
|
(void) index;
|
|
|
|
QString text = ui->powerYUnits->currentText();
|
|
if (text == "dBFS")
|
|
{
|
|
m_settings.m_powerYUnits = RadioAstronomySettings::PY_DBFS;
|
|
ui->powerMarkerTable->horizontalHeaderItem(POWER_MARKER_COL_VALUE)->setText("Power (dBFS)");
|
|
ui->powerColourScaleMin->setDecimals(2);
|
|
ui->powerColourScaleMax->setDecimals(2);
|
|
}
|
|
else if (text == "dBm")
|
|
{
|
|
m_settings.m_powerYUnits = RadioAstronomySettings::PY_DBM;
|
|
ui->powerMarkerTable->horizontalHeaderItem(POWER_MARKER_COL_VALUE)->setText("Power (dBm)");
|
|
ui->powerColourScaleMin->setDecimals(2);
|
|
ui->powerColourScaleMax->setDecimals(2);
|
|
}
|
|
else if (text == "Watts")
|
|
{
|
|
m_settings.m_powerYUnits = RadioAstronomySettings::PY_WATTS;
|
|
ui->powerMarkerTable->horizontalHeaderItem(POWER_MARKER_COL_VALUE)->setText("Power (W)");
|
|
}
|
|
else if (text == "K")
|
|
{
|
|
m_settings.m_powerYUnits = RadioAstronomySettings::PY_KELVIN;
|
|
ui->powerMarkerTable->horizontalHeaderItem(POWER_MARKER_COL_VALUE)->setText("Temp (K)");
|
|
ui->powerColourScaleMin->setDecimals(0);
|
|
ui->powerColourScaleMax->setDecimals(0);
|
|
}
|
|
else if (text == "SFU")
|
|
{
|
|
m_settings.m_powerYUnits = RadioAstronomySettings::PY_SFU;
|
|
ui->powerMarkerTable->horizontalHeaderItem(POWER_MARKER_COL_VALUE)->setText("Flux (SFU)");
|
|
}
|
|
else if (text == "Jy")
|
|
{
|
|
m_settings.m_powerYUnits = RadioAstronomySettings::PY_JANSKY;
|
|
ui->powerMarkerTable->horizontalHeaderItem(POWER_MARKER_COL_VALUE)->setText("Flux (Jy)");
|
|
}
|
|
if (text == "dBFS")
|
|
{
|
|
ui->powerColourScaleMinUnits->setText("dB");
|
|
ui->powerColourScaleMaxUnits->setText("dB");
|
|
}
|
|
else
|
|
{
|
|
ui->powerColourScaleMinUnits->setText(text);
|
|
ui->powerColourScaleMaxUnits->setText(text);
|
|
}
|
|
applySettings();
|
|
plotPowerChart();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_spectrumChartSelect_currentIndexChanged(int index)
|
|
{
|
|
updateSpectrumMarkerTableVisibility(); // If this follows updateSpectrumChartWidgetsVisibility, widgets are not redrawn properly.
|
|
updateSpectrumChartWidgetsVisibility(); // when switching from cal to spectrum if table is visible
|
|
if (index == 0)
|
|
{
|
|
if (m_fftChart) {
|
|
ui->spectrumChart->setChart(m_fftChart);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_calChart) {
|
|
ui->spectrumChart->setChart(m_calChart);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::updateSpectrumChartWidgetsVisibility()
|
|
{
|
|
bool fft = ui->spectrumChartSelect->currentIndex() == 0;
|
|
ui->spectrumYUnits->setVisible(fft);
|
|
ui->spectrumScaleWidgets->setVisible(fft);
|
|
ui->spectrumSelectWidgets->setVisible(fft);
|
|
ui->spectrumRefLineWidgets->setVisible(fft && m_settings.m_spectrumRefLine);
|
|
|
|
ui->spectrumGaussianWidgets->setVisible(fft && m_settings.m_spectrumTemp);
|
|
ui->calWidgets->setVisible(!fft);
|
|
ui->recalibrate->setVisible(!fft);
|
|
ui->startCalHot->setVisible(!fft);
|
|
ui->startCalCold->setVisible(!fft);
|
|
ui->clearCal->setVisible(!fft);
|
|
ui->showCalSettings->setVisible(!fft);
|
|
ui->spectrumShowRefLine->setVisible(fft);
|
|
ui->spectrumShowDistance->setVisible(fft);
|
|
ui->spectrumShowLAB->setVisible(fft);
|
|
ui->spectrumTemp->setVisible(fft);
|
|
ui->spectrumMarker->setVisible(fft);
|
|
ui->spectrumPeak->setVisible(fft);
|
|
ui->saveSpectrumChartImages->setVisible(fft);
|
|
|
|
getRollupContents()->arrangeRollups();
|
|
}
|
|
|
|
// Calulate mean, RMS and standard deviation
|
|
// Currently this is for all data - but could make it only for visible data
|
|
void RadioAstronomyGUI::calcAverages()
|
|
{
|
|
qreal sum = 0.0;
|
|
qreal sumSq = 0.0;
|
|
QVector<QPointF> points = m_powerSeries->pointsVector();
|
|
for (int i = 0; i < points.size(); i++)
|
|
{
|
|
QPointF point = points.at(i);
|
|
qreal y = point.y();
|
|
sum += y;
|
|
sumSq += y * y;
|
|
}
|
|
qreal mean = sum / points.size();
|
|
qreal rms = std::sqrt(sumSq / points.size());
|
|
qreal sumSqDiff = 0.0;
|
|
for (int i = 0; i < points.size(); i++)
|
|
{
|
|
QPointF point = points.at(i);
|
|
qreal y = point.y();
|
|
qreal diff = y - mean;
|
|
sumSqDiff += diff * diff;
|
|
}
|
|
qreal sigma = std::sqrt(sumSqDiff / points.size());
|
|
|
|
ui->powerMean->setText(QString::number(mean));
|
|
ui->powerRMS->setText(QString::number(rms));
|
|
ui->powerSD->setText(QString::number(sigma));
|
|
}
|
|
|
|
QRgb RadioAstronomyGUI::intensityToColor(float intensity)
|
|
{
|
|
QRgb c1, c2;
|
|
float scale;
|
|
|
|
if (std::isnan(intensity)) {
|
|
return qRgb(0, 0, 0);
|
|
}
|
|
// Get in range 0-1
|
|
intensity = (intensity - m_settings.m_powerColourScaleMin) / (m_settings.m_powerColourScaleMax - m_settings.m_powerColourScaleMin);
|
|
intensity = std::min(intensity, 1.0f);
|
|
intensity = std::max(intensity, 0.0f);
|
|
|
|
if (m_settings.m_powerColourPalette[0] == 'C')
|
|
{
|
|
// Colour heat map gradient
|
|
if (intensity <= 0.25f)
|
|
{
|
|
c1 = qRgb(0, 0, 0x80); // Navy
|
|
c2 = qRgb(0, 0, 0xff); // Blue
|
|
scale = intensity * 4.0f;
|
|
}
|
|
else if (intensity <= 0.5f)
|
|
{
|
|
c1 = qRgb(0, 0, 0xff); // Blue
|
|
c2 = qRgb(0, 0xff, 0); // Green
|
|
scale = (intensity - 0.25f) * 4.0f;
|
|
}
|
|
else if (intensity <= 0.75f)
|
|
{
|
|
c1 = qRgb(0, 0xff, 0); // Green
|
|
c2 = qRgb(0xff, 0xff, 0); // Yellow
|
|
scale = (intensity - 0.5f) * 4.0f;
|
|
}
|
|
else
|
|
{
|
|
c1 = qRgb(0xff, 0xff, 0); // Yellow
|
|
c2 = qRgb(0xff, 0, 0); // Red
|
|
scale = (intensity - 0.75f) * 4.0f;
|
|
}
|
|
|
|
int r, g, b;
|
|
r = (qRed(c2)-qRed(c1))*scale+qRed(c1);
|
|
g = (qGreen(c2)-qGreen(c1))*scale+qGreen(c1);
|
|
b = (qBlue(c2)-qBlue(c1))*scale+qBlue(c1);
|
|
|
|
return qRgb(r, g, b);
|
|
}
|
|
else
|
|
{
|
|
// Greyscale
|
|
int g = 255 * intensity;
|
|
return qRgb(g, g, g);
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::plot2DChart()
|
|
{
|
|
// Only plot if visible
|
|
if (ui->powerChartSelect->currentIndex() == 4)
|
|
{
|
|
QChart *oldChart = m_2DChart;
|
|
|
|
m_2DChart = new QChart();
|
|
|
|
m_2DChart->layout()->setContentsMargins(0, 0, 0, 0);
|
|
m_2DChart->setMargins(QMargins(1, 1, 1, 1));
|
|
m_2DChart->setTheme(QChart::ChartThemeDark);
|
|
m_2DChart->setTitle("");
|
|
|
|
m_2DXAxis = new QValueAxis();
|
|
m_2DYAxis = new QValueAxis();
|
|
|
|
m_2DXAxis->setGridLineVisible(false);
|
|
m_2DYAxis->setGridLineVisible(false);
|
|
set2DAxisTitles();
|
|
m_2DXAxis->setRange(m_settings.m_power2DXMin, m_settings.m_power2DXMax);
|
|
m_2DYAxis->setRange(m_settings.m_power2DYMin, m_settings.m_power2DYMax);
|
|
|
|
m_2DChart->addAxis(m_2DXAxis, Qt::AlignBottom);
|
|
m_2DChart->addAxis(m_2DYAxis, Qt::AlignLeft);
|
|
|
|
m_2DMap.fill(qRgb(0, 0, 0));
|
|
for (int i = 0; i < m_fftMeasurements.size(); i++) {
|
|
update2DImage(m_fftMeasurements[i], i < m_fftMeasurements.size() - 1);
|
|
}
|
|
if (m_settings.m_powerColourAutoscale) {
|
|
powerColourAutoscale();
|
|
}
|
|
|
|
connect(m_2DChart, SIGNAL(plotAreaChanged(QRectF)), this, SLOT(plotAreaChanged(QRectF)));
|
|
|
|
ui->powerChart->setChart(m_2DChart);
|
|
|
|
delete oldChart;
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::set2DAxisTitles()
|
|
{
|
|
if (m_settings.m_power2DSweepType == RadioAstronomySettings::SWP_LB)
|
|
{
|
|
m_2DXAxis->setTitleText(QString("Galactic longitude (%1)").arg(QChar(0xb0)));
|
|
m_2DYAxis->setTitleText(QString("Galactic latitude (%1)").arg(QChar(0xb0)));
|
|
}
|
|
else
|
|
{
|
|
m_2DXAxis->setTitleText(QString("Azimuth (%1)").arg(QChar(0xb0)));
|
|
m_2DYAxis->setTitleText(QString("Elevation (%1)").arg(QChar(0xb0)));
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::update2DSettingsFromSweep()
|
|
{
|
|
if (m_settings.m_runMode == RadioAstronomySettings::SWEEP)
|
|
{
|
|
ui->power2DSweepType->setCurrentIndex((int)m_settings.m_sweepType);
|
|
|
|
// Calculate width and height of image - 1 pixel per sweep measurement
|
|
float sweep1Start, sweep1Stop;
|
|
sweep1Start = m_settings.m_sweep1Start;
|
|
sweep1Stop = m_settings.m_sweep1Stop;
|
|
// Handle azimuth/l sweep through 0. E.g. 340deg -> 20deg with +vs step, or 20deg -> 340deg with -ve step
|
|
if ((m_settings.m_sweep1Stop < m_settings.m_sweep1Start) && (m_settings.m_sweep1Step > 0)) {
|
|
sweep1Stop = m_settings.m_sweep1Stop + 360.0;
|
|
} else if ((m_settings.m_sweep1Stop > m_settings.m_sweep1Start) && (m_settings.m_sweep1Step < 0)) {
|
|
sweep1Start += 360.0;
|
|
}
|
|
int width = abs((sweep1Stop - sweep1Start) / m_settings.m_sweep1Step) + 1;
|
|
int height = abs((m_settings.m_sweep2Stop - m_settings.m_sweep2Start) / m_settings.m_sweep2Step) + 1;
|
|
ui->power2DWidth->setValue(width);
|
|
ui->power2DHeight->setValue(height);
|
|
|
|
// Subtract/add half a step so that pixels are centred on coordinates
|
|
int start1 = m_settings.m_sweep1Start - m_settings.m_sweep1Step / 2;
|
|
int stop1 = m_settings.m_sweep1Stop + m_settings.m_sweep1Step / 2;
|
|
if (start1 < stop1)
|
|
{
|
|
ui->power2DXMin->setValue(start1);
|
|
ui->power2DXMax->setValue(stop1);
|
|
}
|
|
else
|
|
{
|
|
ui->power2DXMin->setValue(stop1);
|
|
ui->power2DXMax->setValue(start1);
|
|
}
|
|
|
|
int start2 = m_settings.m_sweep2Start - m_settings.m_sweep2Step / 2;
|
|
int stop2 = m_settings.m_sweep2Stop + m_settings.m_sweep2Step / 2;
|
|
if (start2 < stop2)
|
|
{
|
|
ui->power2DYMin->setValue(start2);
|
|
ui->power2DYMax->setValue(stop2);
|
|
}
|
|
else
|
|
{
|
|
ui->power2DYMin->setValue(stop2);
|
|
ui->power2DYMax->setValue(start2);
|
|
}
|
|
|
|
m_sweepIndex = 0;
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::create2DImage()
|
|
{
|
|
// Intensity array holds power/temperature values which are then colourised in to a QImage
|
|
delete m_2DMapIntensity;
|
|
int size = m_settings.m_power2DWidth * m_settings.m_power2DHeight;
|
|
if (size > 0)
|
|
{
|
|
m_2DMapIntensity = new float[size];
|
|
for (int i = 0; i < size; i++) {
|
|
m_2DMapIntensity[i] = NAN;
|
|
}
|
|
m_2DMapMin = std::numeric_limits<float>::max();
|
|
m_2DMapMax = -std::numeric_limits<float>::max();
|
|
QImage map(m_settings.m_power2DWidth, m_settings.m_power2DHeight, QImage::Format_ARGB32);
|
|
map.fill(qRgb(0, 0, 0));
|
|
m_2DMap = map;
|
|
}
|
|
else
|
|
{
|
|
m_2DMapIntensity = nullptr;
|
|
m_2DMap = QImage();
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::update2DImage(FFTMeasurement* fft, bool skipCalcs)
|
|
{
|
|
if (m_2DMap.width() > 0)
|
|
{
|
|
int x, y;
|
|
if (m_settings.m_power2DSweepType == RadioAstronomySettings::SWP_OFFSET)
|
|
{
|
|
y = fft->m_sweepIndex / m_2DMap.width();
|
|
x = fft->m_sweepIndex % m_2DMap.width();
|
|
|
|
if (m_settings.m_sweep2Step >= 0) {
|
|
y = m_2DMap.height() - 1 - y;
|
|
}
|
|
if (m_settings.m_sweep1Step < 0) {
|
|
x = m_2DMap.width() - 1 - x;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_settings.m_power2DSweepType == RadioAstronomySettings::SWP_LB)
|
|
{
|
|
x = (int)round(fft->m_l);
|
|
y = (int)round(fft->m_b);
|
|
}
|
|
else
|
|
{
|
|
x = (int)round(fft->m_azimuth);
|
|
y = (int)round(fft->m_elevation);
|
|
}
|
|
|
|
// Map coordinates to pixels
|
|
float xRange = m_settings.m_power2DXMax - m_settings.m_power2DXMin;
|
|
float yRange = m_settings.m_power2DYMax - m_settings.m_power2DYMin;
|
|
|
|
x = (x - m_settings.m_power2DXMin) * m_settings.m_power2DWidth / xRange;
|
|
y = (y - m_settings.m_power2DYMin) * m_settings.m_power2DHeight / yRange;
|
|
|
|
if (yRange >= 0) {
|
|
y = m_2DMap.height() - 1 - y;
|
|
}
|
|
if (xRange < 0) {
|
|
x = m_2DMap.width() - 1 - x;
|
|
}
|
|
}
|
|
|
|
if ((x >= 0) && (x < m_2DMap.width()) && (y >= 0) && (y < m_2DMap.height()))
|
|
{
|
|
float intensity;
|
|
switch (m_settings.m_powerYUnits)
|
|
{
|
|
case RadioAstronomySettings::PY_DBFS:
|
|
intensity = fft->m_totalPowerdBFS;
|
|
break;
|
|
case RadioAstronomySettings::PY_DBM:
|
|
intensity = fft->m_totalPowerdBm;
|
|
break;
|
|
case RadioAstronomySettings::PY_WATTS:
|
|
intensity = fft->m_totalPowerWatts;
|
|
break;
|
|
case RadioAstronomySettings::PY_KELVIN:
|
|
intensity = fft->m_tSys;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
float newMin = std::min(m_2DMapMin, intensity);
|
|
float newMax = std::max(m_2DMapMax, intensity);
|
|
bool rescale = false;
|
|
if ((newMin != m_2DMapMin) || (newMax != m_2DMapMax)) {
|
|
rescale = m_settings.m_powerColourAutoscale;
|
|
}
|
|
m_2DMapMin = newMin;
|
|
m_2DMapMax = newMax;
|
|
m_2DMapIntensity[y*m_2DMap.width()+x] = intensity;
|
|
m_2DMap.setPixel(x, y, intensityToColor(intensity));
|
|
if (rescale && !skipCalcs) {
|
|
powerColourAutoscale();
|
|
}
|
|
if (m_2DChart && !skipCalcs) {
|
|
plotAreaChanged(m_2DChart->plotArea());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::recolour2DImage()
|
|
{
|
|
for (int y = 0; y < m_2DMap.height(); y++)
|
|
{
|
|
for (int x = 0; x < m_2DMap.width(); x++) {
|
|
m_2DMap.setPixel(x, y, intensityToColor(m_2DMapIntensity[y*m_2DMap.width()+x]));
|
|
}
|
|
}
|
|
if (m_2DChart) {
|
|
plotAreaChanged(m_2DChart->plotArea());
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::plotAreaChanged(const QRectF& plotArea)
|
|
{
|
|
if (ui->powerChartSelect->currentIndex() == 4)
|
|
{
|
|
int width = static_cast<int>(plotArea.width());
|
|
int height = static_cast<int>(plotArea.height());
|
|
int viewW = static_cast<int>(ui->powerChart->width());
|
|
int viewH = static_cast<int>(ui->powerChart->height());
|
|
|
|
// Scale the image to fit plot area
|
|
QImage image = m_2DMap.scaled(QSize(width, height), Qt::IgnoreAspectRatio);
|
|
QImage translated(viewW, viewH, QImage::Format_ARGB32);
|
|
translated.fill(Qt::black);
|
|
QPainter painter(&translated);
|
|
painter.drawImage(plotArea.topLeft(), image);
|
|
|
|
m_2DChart->setPlotAreaBackgroundBrush(translated);
|
|
m_2DChart->setPlotAreaBackgroundVisible(true);
|
|
}
|
|
}
|
|
|
|
// Find min and max coordinates from existing data
|
|
void RadioAstronomyGUI::power2DAutoscale()
|
|
{
|
|
if (m_fftMeasurements.size() > 0)
|
|
{
|
|
float minX = std::numeric_limits<float>::max();
|
|
float maxX = -std::numeric_limits<float>::max();
|
|
float minY = std::numeric_limits<float>::max();
|
|
float maxY = -std::numeric_limits<float>::max();
|
|
|
|
for (int i = 0; i < m_fftMeasurements.size(); i++)
|
|
{
|
|
FFTMeasurement* fft = m_fftMeasurements[i];
|
|
float x, y;
|
|
if (m_settings.m_power2DSweepType == RadioAstronomySettings::SWP_LB)
|
|
{
|
|
x = fft->m_l;
|
|
y = fft->m_b;
|
|
}
|
|
else
|
|
{
|
|
x = fft->m_azimuth;
|
|
y = fft->m_elevation;
|
|
}
|
|
minX = std::min(minX, x);
|
|
maxX = std::max(maxX, x);
|
|
minY = std::min(minY, y);
|
|
maxY = std::max(maxY, y);
|
|
}
|
|
|
|
// Adjust so pixels are centered
|
|
float xAdjust = (maxX - minX) / m_2DMap.width() / 2;
|
|
float yAdjust = (maxY - minY) / m_2DMap.height() / 2;
|
|
|
|
ui->power2DXMin->setValue(minX - xAdjust);
|
|
ui->power2DXMax->setValue(maxX + xAdjust);
|
|
ui->power2DYMin->setValue(minY - yAdjust);
|
|
ui->power2DYMax->setValue(maxY + xAdjust);
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_power2DAutoscale_clicked()
|
|
{
|
|
power2DAutoscale();
|
|
plot2DChart();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_power2DLinkSweep_toggled(bool checked)
|
|
{
|
|
m_settings.m_power2DLinkSweep = checked;
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_power2DSweepType_currentIndexChanged(int index)
|
|
{
|
|
m_settings.m_power2DSweepType = (RadioAstronomySettings::SweepType)index;
|
|
applySettings();
|
|
plot2DChart();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_power2DWidth_valueChanged(int value)
|
|
{
|
|
m_settings.m_power2DWidth = value;
|
|
applySettings();
|
|
create2DImage();
|
|
plot2DChart();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_power2DHeight_valueChanged(int value)
|
|
{
|
|
m_settings.m_power2DHeight = value;
|
|
applySettings();
|
|
create2DImage();
|
|
plot2DChart();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_power2DXMin_valueChanged(double value)
|
|
{
|
|
m_settings.m_power2DXMin = value;
|
|
applySettings();
|
|
if (m_2DXAxis)
|
|
{
|
|
m_2DXAxis->setMin(m_settings.m_power2DXMin);
|
|
plot2DChart();
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_power2DXMax_valueChanged(double value)
|
|
{
|
|
m_settings.m_power2DXMax = value;
|
|
applySettings();
|
|
if (m_2DXAxis)
|
|
{
|
|
m_2DXAxis->setMax(m_settings.m_power2DXMax);
|
|
plot2DChart();
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_power2DYMin_valueChanged(double value)
|
|
{
|
|
m_settings.m_power2DYMin = value;
|
|
applySettings();
|
|
if (m_2DYAxis)
|
|
{
|
|
m_2DYAxis->setMin(m_settings.m_power2DYMin);
|
|
plot2DChart();
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_power2DYMax_valueChanged(double value)
|
|
{
|
|
m_settings.m_power2DYMax = value;
|
|
applySettings();
|
|
if (m_2DYAxis)
|
|
{
|
|
m_2DYAxis->setMax(m_settings.m_power2DYMax);
|
|
plot2DChart();
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::powerColourAutoscale()
|
|
{
|
|
int width = m_2DMap.width();
|
|
int height = m_2DMap.height();
|
|
float newMin = std::numeric_limits<float>::max();
|
|
float newMax = -std::numeric_limits<float>::max();
|
|
for (int i = 0; i < width * height; i++)
|
|
{
|
|
if (!std::isnan(m_2DMapIntensity[i]))
|
|
{
|
|
newMin = std::min(newMin, m_2DMapIntensity[i]);
|
|
newMax = std::max(newMax, m_2DMapIntensity[i]);
|
|
}
|
|
}
|
|
|
|
if ((newMin != ui->powerColourScaleMin->value()) || (newMax != ui->powerColourScaleMax->value()))
|
|
{
|
|
ui->powerColourScaleMin->setValue(std::floor(newMin * 10.0) / 10.0);
|
|
ui->powerColourScaleMax->setValue(std::ceil(newMax * 10.0) / 10.0);
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerColourAutoscale_toggled(bool checked)
|
|
{
|
|
m_settings.m_powerColourAutoscale = checked;
|
|
applySettings();
|
|
if (m_settings.m_powerColourAutoscale) {
|
|
powerColourAutoscale();
|
|
}
|
|
ui->powerColourScaleMin->setEnabled(!m_settings.m_powerColourAutoscale);
|
|
ui->powerColourScaleMax->setEnabled(!m_settings.m_powerColourAutoscale);
|
|
}
|
|
|
|
void RadioAstronomyGUI::updatePowerColourScaleStep()
|
|
{
|
|
float diff = abs(m_settings.m_powerColourScaleMax - m_settings.m_powerColourScaleMin);
|
|
double step = (diff <= 1.0f) ? 0.1 : 1.0f;
|
|
ui->powerColourScaleMin->setSingleStep(step);
|
|
ui->powerColourScaleMax->setSingleStep(step);
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerColourScaleMin_valueChanged(double value)
|
|
{
|
|
m_settings.m_powerColourScaleMin = value;
|
|
updatePowerColourScaleStep();
|
|
applySettings();
|
|
recolour2DImage();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerColourScaleMax_valueChanged(double value)
|
|
{
|
|
m_settings.m_powerColourScaleMax = value;
|
|
updatePowerColourScaleStep();
|
|
applySettings();
|
|
recolour2DImage();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerColourPalette_currentIndexChanged(int index)
|
|
{
|
|
(void) index;
|
|
m_settings.m_powerColourPalette = ui->powerColourPalette->currentText();
|
|
applySettings();
|
|
recolour2DImage();
|
|
}
|
|
|
|
void RadioAstronomyGUI::plotPowerChart()
|
|
{
|
|
if (ui->powerChartSelect->currentIndex() == 4) {
|
|
plot2DChart();
|
|
} else {
|
|
plotPowerVsTimeChart();
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::plotPowerVsTimeChart()
|
|
{
|
|
QChart *oldChart = m_powerChart;
|
|
|
|
m_powerChart = new QChart();
|
|
|
|
m_powerChart->layout()->setContentsMargins(0, 0, 0, 0);
|
|
m_powerChart->setMargins(QMargins(1, 1, 1, 1));
|
|
m_powerChart->setTheme(QChart::ChartThemeDark);
|
|
|
|
m_powerChart->legend()->setAlignment(Qt::AlignRight);
|
|
m_powerChart->legend()->setVisible(m_settings.m_powerLegend);
|
|
|
|
// Create measurement data series
|
|
m_powerSeries = new QLineSeries();
|
|
m_powerSeries->setVisible(m_settings.m_powerShowMeasurement);
|
|
connect(m_powerSeries, &QXYSeries::clicked, this, &RadioAstronomyGUI::powerSeries_clicked);
|
|
|
|
// Plot peak info
|
|
m_powerPeakSeries = new QScatterSeries();
|
|
m_powerPeakSeries->setName("Peak");
|
|
m_powerPeakSeries->setPointLabelsVisible(true);
|
|
m_powerPeakSeries->setPointLabelsFormat("@yPoint"); // Qt can't display dates, so omit @xPoint
|
|
m_powerPeakSeries->setMarkerSize(5);
|
|
m_powerPeakSeries->setVisible(m_settings.m_powerPeaks);
|
|
|
|
// Markers
|
|
m_powerMarkerSeries = new QScatterSeries();
|
|
m_powerMarkerSeries->setName("Marker");
|
|
m_powerMarkerSeries->setPointLabelsVisible(true);
|
|
m_powerMarkerSeries->setPointLabelsFormat("@yPoint");
|
|
m_powerMarkerSeries->setMarkerSize(5);
|
|
m_powerMarkerSeries->setVisible(m_settings.m_powerMarkers);
|
|
|
|
// Noise
|
|
m_powerTsys0Series = new QLineSeries();
|
|
m_powerTsys0Series->setName("Tsys0");
|
|
m_powerTsys0Series->setVisible(m_settings.m_powerShowTsys0);
|
|
|
|
// Air temperature
|
|
m_airTemps.init("Air temp", m_settings.m_powerShowAirTemp);
|
|
|
|
// Gaussian
|
|
m_powerGaussianSeries = new QLineSeries();
|
|
m_powerGaussianSeries->setName("Gaussian fit");
|
|
m_powerGaussianSeries->setVisible(m_settings.m_powerShowGaussian);
|
|
|
|
// Filtered measurement
|
|
m_powerFilteredSeries = new QLineSeries();
|
|
m_powerFilteredSeries->setName("Filtered");
|
|
m_powerFilteredSeries->setVisible(m_settings.m_powerShowFiltered);
|
|
plotPowerFiltered();
|
|
|
|
// Sensors
|
|
for (int i = 0; i < RADIOASTRONOMY_SENSORS; i++) {
|
|
m_sensors[i].init(m_settings.m_sensorName[i], m_settings.m_sensorVisible[i]);
|
|
}
|
|
|
|
// Reset min/max and peaks
|
|
m_powerMin = std::numeric_limits<double>::max();
|
|
m_powerMax = -std::numeric_limits<double>::max();
|
|
m_powerPeakValid = false;
|
|
|
|
// Create X axis
|
|
m_powerXAxis = new QDateTimeAxis();
|
|
int rows = ui->powerTable->rowCount();
|
|
QString dateTimeFormat = "hh:mm:ss";
|
|
m_powerXAxisSameDay = true;
|
|
if (rows > 1)
|
|
{
|
|
QDate start = ui->powerTable->item(0, POWER_COL_DATE)->data(Qt::DisplayRole).toDate();
|
|
QDate end = ui->powerTable->item(rows-1, POWER_COL_DATE)->data(Qt::DisplayRole).toDate();
|
|
if (start != end)
|
|
{
|
|
dateTimeFormat = QString("%1 hh:mm").arg(QLocale::system().dateFormat(QLocale::ShortFormat));
|
|
m_powerXAxisSameDay = false;
|
|
}
|
|
}
|
|
m_powerXAxis->setFormat(dateTimeFormat);
|
|
m_powerXAxis->setRange(ui->powerStartTime->dateTime(), ui->powerEndTime->dateTime());
|
|
ui->powerStartTime->setDisplayFormat(dateTimeFormat);
|
|
ui->powerEndTime->setDisplayFormat(dateTimeFormat);
|
|
|
|
// Create Y axis
|
|
m_powerYAxis = new QValueAxis();
|
|
|
|
m_powerXAxis->setTitleText("Time");
|
|
calcPowerChartTickCount(size().width());
|
|
switch (m_settings.m_powerYData)
|
|
{
|
|
case RadioAstronomySettings::PY_POWER:
|
|
m_powerSeries->setName("Measurement");
|
|
switch (m_settings.m_powerYUnits)
|
|
{
|
|
case RadioAstronomySettings::PY_DBFS:
|
|
m_powerYAxis->setTitleText("Power (dBFS)");
|
|
break;
|
|
case RadioAstronomySettings::PY_DBM:
|
|
m_powerYAxis->setTitleText("Power (dBm)");
|
|
break;
|
|
case RadioAstronomySettings::PY_WATTS:
|
|
m_powerYAxis->setTitleText("Power (Watts)");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case RadioAstronomySettings::PY_TSYS:
|
|
m_powerSeries->setName("Tsys");
|
|
m_powerYAxis->setTitleText("Tsys (K)");
|
|
break;
|
|
case RadioAstronomySettings::PY_TSOURCE:
|
|
m_powerSeries->setName("Tsource");
|
|
m_powerYAxis->setTitleText("Tsource (K)");
|
|
break;
|
|
case RadioAstronomySettings::PY_FLUX:
|
|
m_powerSeries->setName("Flux density");
|
|
switch (m_settings.m_powerYUnits)
|
|
{
|
|
case RadioAstronomySettings::PY_SFU:
|
|
m_powerYAxis->setTitleText("Flux density (SFU)");
|
|
break;
|
|
case RadioAstronomySettings::PY_JANSKY:
|
|
m_powerYAxis->setTitleText("Flux density (Jy)");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
m_powerChart->addAxis(m_powerXAxis, Qt::AlignBottom);
|
|
m_powerChart->addAxis(m_powerYAxis, Qt::AlignLeft);
|
|
m_powerChart->addAxis(m_airTemps.yAxis(), Qt::AlignRight);
|
|
for (int i = 0; i < RADIOASTRONOMY_SENSORS; i++) {
|
|
m_powerChart->addAxis(m_sensors[i].yAxis(), Qt::AlignRight);
|
|
}
|
|
|
|
// Add data to series and calculate peaks
|
|
for (int i = 0; i < m_fftMeasurements.size(); i++) {
|
|
addToPowerSeries(m_fftMeasurements[i], i < (m_fftMeasurements.size() - 1));
|
|
}
|
|
m_airTemps.addAllToSeries();
|
|
for (int i = 0; i < RADIOASTRONOMY_SENSORS; i++) {
|
|
m_sensors[i].addAllToSeries();
|
|
}
|
|
|
|
m_powerChart->addSeries(m_powerSeries);
|
|
m_powerSeries->attachAxis(m_powerXAxis);
|
|
m_powerSeries->attachAxis(m_powerYAxis);
|
|
|
|
m_powerChart->addSeries(m_powerTsys0Series);
|
|
m_powerTsys0Series->attachAxis(m_powerXAxis);
|
|
m_powerTsys0Series->attachAxis(m_powerYAxis);
|
|
|
|
m_powerChart->addSeries(m_powerGaussianSeries);
|
|
m_powerGaussianSeries->attachAxis(m_powerXAxis);
|
|
m_powerGaussianSeries->attachAxis(m_powerYAxis);
|
|
|
|
m_airTemps.addToChart(m_powerChart, m_powerXAxis);
|
|
for (int i = 0; i < RADIOASTRONOMY_SENSORS; i++) {
|
|
m_sensors[i].addToChart(m_powerChart, m_powerXAxis);
|
|
}
|
|
|
|
m_powerChart->addSeries(m_powerFilteredSeries);
|
|
m_powerFilteredSeries->attachAxis(m_powerXAxis);
|
|
m_powerFilteredSeries->attachAxis(m_powerYAxis);
|
|
|
|
m_powerChart->addSeries(m_powerPeakSeries);
|
|
m_powerPeakSeries->attachAxis(m_powerXAxis);
|
|
m_powerPeakSeries->attachAxis(m_powerYAxis);
|
|
|
|
m_powerChart->addSeries(m_powerMarkerSeries);
|
|
m_powerMarkerSeries->attachAxis(m_powerXAxis);
|
|
m_powerMarkerSeries->attachAxis(m_powerYAxis);
|
|
|
|
// Dark theme only has 5 colours for series, so use an extra unique colour (purple)
|
|
QPen pen(QColor(qRgb(146, 65, 146)), 2, Qt::SolidLine);
|
|
m_sensors[1].setPen(pen);
|
|
|
|
// Don't have peaks and markers in legend
|
|
m_powerChart->legend()->markers(m_powerPeakSeries)[0]->setVisible(false);
|
|
m_powerChart->legend()->markers(m_powerMarkerSeries)[0]->setVisible(false);
|
|
|
|
ui->powerChart->setChart(m_powerChart);
|
|
|
|
delete oldChart;
|
|
}
|
|
|
|
void RadioAstronomyGUI::calCompletetReceived(const RadioAstronomy::MsgCalComplete& measurement)
|
|
{
|
|
bool hot = measurement.getHot();
|
|
int size = measurement.getSize();
|
|
Real* data = measurement.getCal();
|
|
|
|
FFTMeasurement* fft = new FFTMeasurement();
|
|
if (hot)
|
|
{
|
|
delete m_calHot;
|
|
m_calHot = fft;
|
|
ui->startCalHot->setStyleSheet("QToolButton { background: none; }");
|
|
}
|
|
else
|
|
{
|
|
delete m_calCold;
|
|
m_calCold = fft;
|
|
ui->startCalCold->setStyleSheet("QToolButton { background: none; }");
|
|
}
|
|
fft->m_fftData = data;
|
|
fft->m_fftSize = size;
|
|
fft->m_dateTime = measurement.getDateTime();
|
|
fft->m_centerFrequency = m_centerFrequency;
|
|
fft->m_sampleRate = m_settings.m_sampleRate;
|
|
fft->m_integration = m_settings.m_integration;
|
|
fft->m_rfBandwidth = m_settings.m_rfBandwidth;
|
|
fft->m_omegaA = calcOmegaA();
|
|
fft->m_omegaS = calcOmegaS();
|
|
fft->m_coordsValid = m_coordsValid;
|
|
fft->m_ra = m_ra;
|
|
fft->m_dec = m_dec;
|
|
fft->m_azimuth = m_azimuth;
|
|
fft->m_elevation = m_elevation;
|
|
fft->m_l = m_l;
|
|
fft->m_b = m_b;
|
|
fft->m_vBCRS = m_vBCRS;
|
|
fft->m_vLSR = m_vLSR;
|
|
fft->m_solarFlux = m_solarFlux;
|
|
fft->m_airTemp = m_airTemps.lastValue();
|
|
fft->m_skyTemp = m_skyTemp;
|
|
for (int i = 0; i < RADIOASTRONOMY_SENSORS; i++) {
|
|
fft->m_sensor[i] = m_sensors[i].lastValue();
|
|
}
|
|
fft->m_tSys0 = calcTSys0();
|
|
fft->m_baseline = m_settings.m_spectrumBaseline;
|
|
|
|
if (!hot) {
|
|
ui->calTsky->setText(QString::number(m_skyTemp, 'f', 1));
|
|
}
|
|
|
|
calcFFTTotalPower(fft);
|
|
calcCalAvgDiff();
|
|
calibrate();
|
|
calcFFTTemperatures(fft);
|
|
calcFFTTotalTemperature(fft);
|
|
plotCalMeasurements();
|
|
}
|
|
|
|
void RadioAstronomyGUI::plotCalMeasurements()
|
|
{
|
|
m_calHotSeries->clear();
|
|
m_calColdSeries->clear();
|
|
|
|
if (m_calHot || m_calCold)
|
|
{
|
|
float minVal = std::numeric_limits<float>::max();
|
|
float maxVal = -std::numeric_limits<float>::max();
|
|
|
|
double size;
|
|
double sampleRate;
|
|
double centerFrequency;
|
|
if (m_calCold && m_calHot)
|
|
{
|
|
size = (double)std::min(m_calCold->m_fftSize, m_calHot->m_fftSize);
|
|
sampleRate = (double)m_calCold->m_sampleRate;
|
|
centerFrequency = (double)m_calCold->m_centerFrequency;
|
|
}
|
|
else if (m_calCold)
|
|
{
|
|
size = (double)m_calCold->m_fftSize;
|
|
sampleRate = (double)m_calCold->m_sampleRate;
|
|
centerFrequency = (double)m_calCold->m_centerFrequency;
|
|
}
|
|
else
|
|
{
|
|
size = (double)m_calHot->m_fftSize;
|
|
sampleRate = (double)m_calHot->m_sampleRate;
|
|
centerFrequency = (double)m_calHot->m_centerFrequency;
|
|
}
|
|
|
|
double binRes = sampleRate / size;
|
|
double startFreq = centerFrequency - sampleRate / 2.0;
|
|
double freq = startFreq;
|
|
for (int i = 0; i < size; i++)
|
|
{
|
|
float value;
|
|
bool hotValid = m_calHot && (i < m_calHot->m_fftSize);
|
|
bool coldValid = m_calCold && (i < m_calCold->m_fftSize);
|
|
|
|
if (hotValid)
|
|
{
|
|
value = CalcDb::dbPower(m_calHot->m_fftData[i]);
|
|
m_calHotSeries->append(freq / 1e6, value);
|
|
minVal = std::min(minVal, value);
|
|
maxVal = std::max(maxVal, value);
|
|
}
|
|
if (coldValid)
|
|
{
|
|
value = CalcDb::dbPower(m_calCold->m_fftData[i]);
|
|
m_calColdSeries->append(freq / 1e6, value);
|
|
minVal = std::min(minVal, value);
|
|
maxVal = std::max(maxVal, value);
|
|
}
|
|
freq += binRes;
|
|
}
|
|
|
|
m_calYAxis->setRange(minVal, maxVal);
|
|
|
|
double startFreqMHz = centerFrequency/1e6 - sampleRate/ 1e6 / 2.0;
|
|
double endFreqMHz = centerFrequency/1e6 + sampleRate/ 1e6 / 2.0;
|
|
m_calXAxis->setRange(startFreqMHz, endFreqMHz);
|
|
m_calXAxis->setReverse(m_settings.m_spectrumReverseXAxis);
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::plotCalSpectrum()
|
|
{
|
|
QChart *oldChart = m_calChart;
|
|
|
|
m_calChart = new QChart();
|
|
|
|
m_calChart->layout()->setContentsMargins(0, 0, 0, 0);
|
|
m_calChart->setMargins(QMargins(1, 1, 1, 1));
|
|
m_calChart->setTheme(QChart::ChartThemeDark);
|
|
|
|
m_calChart->legend()->setAlignment(Qt::AlignRight);
|
|
m_calChart->legend()->setVisible(m_settings.m_spectrumLegend);
|
|
|
|
m_calHotSeries = new QLineSeries();
|
|
m_calHotSeries->setName("Hot");
|
|
m_calColdSeries = new QLineSeries();
|
|
m_calColdSeries->setName("Cold");
|
|
|
|
m_calXAxis = new QValueAxis();
|
|
m_calYAxis = new QValueAxis();
|
|
|
|
m_calChart->addAxis(m_calXAxis, Qt::AlignBottom);
|
|
m_calChart->addAxis(m_calYAxis, Qt::AlignLeft);
|
|
|
|
m_calXAxis->setTitleText("Frequency (MHz)");
|
|
calcSpectrumChartTickCount(m_calXAxis, size().width());
|
|
m_calYAxis->setTitleText("Power (dBFS)");
|
|
|
|
m_calChart->addSeries(m_calHotSeries);
|
|
m_calHotSeries->attachAxis(m_calXAxis);
|
|
m_calHotSeries->attachAxis(m_calYAxis);
|
|
|
|
m_calChart->addSeries(m_calColdSeries);
|
|
m_calColdSeries->attachAxis(m_calXAxis);
|
|
m_calColdSeries->attachAxis(m_calYAxis);
|
|
|
|
plotCalMeasurements();
|
|
|
|
ui->spectrumChart->setChart(m_calChart);
|
|
|
|
delete oldChart;
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_spectrumReference_valueChanged(double value)
|
|
{
|
|
m_settings.m_spectrumReference = value;
|
|
spectrumUpdateYRange();
|
|
if (!m_settings.m_spectrumAutoscale) {
|
|
applySettings();
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_spectrumRange_valueChanged(double value)
|
|
{
|
|
m_settings.m_spectrumRange = value;
|
|
if (m_settings.m_spectrumRange <= 1.0)
|
|
{
|
|
ui->spectrumRange->setSingleStep(0.1);
|
|
ui->spectrumRange->setDecimals(2);
|
|
ui->spectrumReference->setDecimals(2);
|
|
}
|
|
else
|
|
{
|
|
ui->spectrumRange->setSingleStep(1.0);
|
|
ui->spectrumRange->setDecimals(1);
|
|
ui->spectrumReference->setDecimals(1);
|
|
}
|
|
spectrumUpdateYRange();
|
|
if (!m_settings.m_spectrumAutoscale) {
|
|
applySettings();
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_spectrumSpan_valueChanged(double value)
|
|
{
|
|
m_settings.m_spectrumSpan = value;
|
|
spectrumUpdateXRange();
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_spectrumCenterFreq_valueChanged(double value)
|
|
{
|
|
double offset;
|
|
FFTMeasurement* fft = currentFFT();
|
|
if (fft) {
|
|
offset = value - fft->m_centerFrequency/1e6;
|
|
} else {
|
|
offset = value - m_centerFrequency/1e6;
|
|
}
|
|
m_settings.m_spectrumCenterFreqOffset = offset;
|
|
spectrumUpdateXRange();
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::spectrumUpdateXRange(FFTMeasurement* fft)
|
|
{
|
|
if (!fft) {
|
|
fft = currentFFT();
|
|
}
|
|
if (m_fftXAxis && fft)
|
|
{
|
|
double startFreqMHz = fft->m_centerFrequency/1e6 - m_settings.m_spectrumSpan / 2.0;
|
|
double endFreqMHz = fft->m_centerFrequency/1e6 + m_settings.m_spectrumSpan / 2.0;
|
|
m_fftXAxis->setRange(startFreqMHz + m_settings.m_spectrumCenterFreqOffset, endFreqMHz + m_settings.m_spectrumCenterFreqOffset);
|
|
double lineFreqMHz = ui->spectrumLineFrequency->value();
|
|
double lineFreq = lineFreqMHz * 1e6;
|
|
double startFreq = fft->m_centerFrequency + m_settings.m_spectrumSpan*1e6 / 2.0 + m_settings.m_spectrumCenterFreqOffset*1e6;
|
|
double endFreq = fft->m_centerFrequency - m_settings.m_spectrumSpan*1e6 / 2.0 + m_settings.m_spectrumCenterFreqOffset*1e6;
|
|
m_fftDopplerAxis->setRange(dopplerToVelocity(lineFreq, startFreq, fft),
|
|
dopplerToVelocity(lineFreq, endFreq, fft));
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::spectrumUpdateYRange(FFTMeasurement* fft)
|
|
{
|
|
if (!fft) {
|
|
fft = currentFFT();
|
|
}
|
|
if (m_fftYAxis && fft) {
|
|
m_fftYAxis->setRange(m_settings.m_spectrumReference - m_settings.m_spectrumRange, m_settings.m_spectrumReference);
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::spectrumAutoscale()
|
|
{
|
|
if (m_settings.m_spectrumAutoscale)
|
|
{
|
|
on_spectrumAutoscaleX_clicked();
|
|
on_spectrumAutoscaleY_clicked();
|
|
}
|
|
}
|
|
|
|
// Scale Y axis according to min and max values
|
|
void RadioAstronomyGUI::on_spectrumAutoscale_toggled(bool checked)
|
|
{
|
|
m_settings.m_spectrumAutoscale = checked;
|
|
ui->spectrumAutoscaleX->setEnabled(!m_settings.m_spectrumAutoscale);
|
|
ui->spectrumAutoscaleY->setEnabled(!m_settings.m_spectrumAutoscale);
|
|
ui->spectrumReference->setEnabled(!m_settings.m_spectrumAutoscale);
|
|
ui->spectrumRange->setEnabled(!m_settings.m_spectrumAutoscale);
|
|
ui->spectrumCenterFreq->setEnabled(!m_settings.m_spectrumAutoscale);
|
|
ui->spectrumSpan->setEnabled(!m_settings.m_spectrumAutoscale);
|
|
spectrumAutoscale();
|
|
applySettings();
|
|
}
|
|
|
|
// Get minimum and maximum values in a series
|
|
// Assumes minVal and maxVal are initialised
|
|
static bool seriesMinAndMax(QLineSeries* series, qreal& minVal, qreal& maxVal)
|
|
{
|
|
QVector<QPointF> points = series->pointsVector();
|
|
for (int i = 0; i < points.size(); i++)
|
|
{
|
|
qreal power = points[i].y();
|
|
minVal = std::min(minVal, power);
|
|
maxVal = std::max(maxVal, power);
|
|
}
|
|
return points.size() > 0;
|
|
}
|
|
|
|
// Scale Y axis according to min and max values
|
|
void RadioAstronomyGUI::on_spectrumAutoscaleY_clicked()
|
|
{
|
|
double minVal = std::numeric_limits<double>::max();
|
|
double maxVal = -std::numeric_limits<double>::max();
|
|
bool v0 = false, v1 = false;
|
|
if (m_fftSeries) {
|
|
v0 = seriesMinAndMax(m_fftSeries, minVal, maxVal);
|
|
}
|
|
if (m_fftLABSeries && m_settings.m_spectrumLAB) {
|
|
v1 = seriesMinAndMax(m_fftLABSeries, minVal, maxVal);
|
|
}
|
|
if (v0 || v1)
|
|
{
|
|
double range = (maxVal - minVal) * 1.2; // 20% wider than signal range, for space for markers
|
|
range = std::max(0.1, range); // Don't be smaller than minimum value we can set in GUI
|
|
ui->spectrumRange->setValue(range); // Call before setting reference, so number of decimals are adjusted
|
|
ui->spectrumReference->setValue(maxVal + range * 0.15);
|
|
}
|
|
}
|
|
|
|
// Scale X axis according to min and max values
|
|
void RadioAstronomyGUI::on_spectrumAutoscaleX_clicked()
|
|
{
|
|
FFTMeasurement* fft = currentFFT();
|
|
if (fft)
|
|
{
|
|
ui->spectrumSpan->setValue(fft->m_sampleRate/1e6);
|
|
ui->spectrumCenterFreq->setValue(fft->m_centerFrequency/1e6);
|
|
}
|
|
else
|
|
{
|
|
ui->spectrumSpan->setValue(m_basebandSampleRate/1e6);
|
|
ui->spectrumCenterFreq->setValue(m_centerFrequency/1e6);
|
|
}
|
|
}
|
|
|
|
RadioAstronomyGUI::FFTMeasurement* RadioAstronomyGUI::currentFFT()
|
|
{
|
|
int index = ui->spectrumIndex->value();
|
|
if ((index >= 0) && (index < m_fftMeasurements.size())) {
|
|
return m_fftMeasurements[index];
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_spectrumYUnits_currentIndexChanged(int index)
|
|
{
|
|
(void) index;
|
|
|
|
QString text = ui->spectrumYUnits->currentText();
|
|
if (text == "dBFS")
|
|
{
|
|
m_settings.m_spectrumYScale = RadioAstronomySettings::SY_DBFS;
|
|
ui->spectrumMarkerTable->horizontalHeaderItem(SPECTRUM_MARKER_COL_VALUE)->setText("Power (dBFS)");
|
|
}
|
|
else if (text == "SNR")
|
|
{
|
|
m_settings.m_spectrumYScale = RadioAstronomySettings::SY_SNR;
|
|
ui->spectrumMarkerTable->horizontalHeaderItem(SPECTRUM_MARKER_COL_VALUE)->setText("SNR");
|
|
}
|
|
else if (text == "dBm")
|
|
{
|
|
m_settings.m_spectrumYScale = RadioAstronomySettings::SY_DBM;
|
|
ui->spectrumMarkerTable->horizontalHeaderItem(SPECTRUM_MARKER_COL_VALUE)->setText("Power (dBm)");
|
|
}
|
|
else if (text == "Tsys K")
|
|
{
|
|
m_settings.m_spectrumYScale = RadioAstronomySettings::SY_TSYS;
|
|
ui->spectrumMarkerTable->horizontalHeaderItem(SPECTRUM_MARKER_COL_VALUE)->setText("Tsys (K)");
|
|
}
|
|
else
|
|
{
|
|
m_settings.m_spectrumYScale = RadioAstronomySettings::SY_TSOURCE;
|
|
ui->spectrumMarkerTable->horizontalHeaderItem(SPECTRUM_MARKER_COL_VALUE)->setText("Tsource (K)");
|
|
}
|
|
plotFFTMeasurement();
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_spectrumBaseline_currentIndexChanged(int index)
|
|
{
|
|
m_settings.m_spectrumBaseline = (RadioAstronomySettings::SpectrumBaseline)index;
|
|
plotFFTMeasurement();
|
|
if ((m_settings.m_powerYData == RadioAstronomySettings::PY_TSOURCE) || (m_settings.m_powerYData == RadioAstronomySettings::PY_FLUX)) {
|
|
plotPowerChart();
|
|
}
|
|
applySettings();
|
|
}
|
|
|
|
// Convert frequency shift to velocity in km/s (+ve approaching)
|
|
static double lineDopplerVelocity(double centre, double f)
|
|
{
|
|
return Astronomy::dopplerToVelocity(f, centre) / 1000.0f;
|
|
}
|
|
|
|
// Convert frequency shift to velocity (+ve receeding - which seems to be the astronomical convention)
|
|
double RadioAstronomyGUI::dopplerToVelocity(double centre, double f, FFTMeasurement *fft)
|
|
{
|
|
double v = lineDopplerVelocity(centre, f);
|
|
// Adjust in to selected reference frame
|
|
switch (m_settings.m_refFrame)
|
|
{
|
|
case RadioAstronomySettings::BCRS:
|
|
v -= fft->m_vBCRS;
|
|
break;
|
|
case RadioAstronomySettings::LSR:
|
|
v -= fft->m_vLSR;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
// Make +ve receeding
|
|
return -v;
|
|
}
|
|
|
|
// Replot current FFT
|
|
void RadioAstronomyGUI::plotFFTMeasurement()
|
|
{
|
|
plotFFTMeasurement(ui->spectrumIndex->value());
|
|
}
|
|
|
|
// Plot FFT with specified index
|
|
void RadioAstronomyGUI::plotFFTMeasurement(int index)
|
|
{
|
|
if (index < m_fftMeasurements.size())
|
|
{
|
|
FFTMeasurement *fft = m_fftMeasurements[index];
|
|
|
|
m_fftSeries->clear();
|
|
m_fftHlineSeries->clear();
|
|
m_fftGaussianSeries->clear();
|
|
m_fftLABSeries->clear();
|
|
m_fftPeakSeries->clear();
|
|
|
|
double binRes = fft->m_sampleRate / (double)fft->m_fftSize;
|
|
double startFreq = fft->m_centerFrequency - fft->m_sampleRate / 2.0;
|
|
|
|
// Plot reference spectral line and Doppler axis
|
|
plotRefLine(fft);
|
|
|
|
// Plot Gaussian for temp estimation
|
|
plotTempGaussian(startFreq, binRes, fft->m_fftSize);
|
|
|
|
// Plot LAB reference data
|
|
if ( fft->m_coordsValid && m_settings.m_spectrumLAB
|
|
&& ( (m_settings.m_spectrumYScale == RadioAstronomySettings::SY_TSYS)
|
|
|| (m_settings.m_spectrumYScale == RadioAstronomySettings::SY_TSOURCE)
|
|
)
|
|
)
|
|
{
|
|
plotLAB(fft->m_l, fft->m_b, m_beamWidth);
|
|
}
|
|
|
|
if ( ((m_settings.m_spectrumYScale == RadioAstronomySettings::SY_SNR) && !fft->m_snr)
|
|
|| ((m_settings.m_spectrumYScale == RadioAstronomySettings::SY_DBM) && !fft->m_temp)
|
|
|| ((m_settings.m_spectrumYScale == RadioAstronomySettings::SY_TSYS) && !fft->m_temp)
|
|
|| ((m_settings.m_spectrumYScale == RadioAstronomySettings::SY_TSOURCE) && !fft->m_temp)
|
|
)
|
|
{
|
|
m_fftChart->setTitle("No cal data: Run calibration or set units to dBFS.");
|
|
}
|
|
else
|
|
{
|
|
m_fftChart->setTitle("");
|
|
|
|
if (fft->m_coordsValid)
|
|
{
|
|
m_fftChart->setTitle(QString("RA: %1 Dec: %2 l: %3%7 b: %4%7 Az: %5%7 El: %6%7")
|
|
.arg(Units::decimalHoursToHoursMinutesAndSeconds(fft->m_ra))
|
|
.arg(Units::decimalDegreesToDegreeMinutesAndSeconds(fft->m_dec))
|
|
.arg(QString::number(fft->m_l, 'f', 1))
|
|
.arg(QString::number(fft->m_b, 'f', 1))
|
|
.arg(QString::number(fft->m_azimuth, 'f', 0))
|
|
.arg(QString::number(fft->m_elevation, 'f', 0))
|
|
.arg(QChar(0xb0)));
|
|
}
|
|
|
|
int peakIdx = 0;
|
|
double peakValue = -std::numeric_limits<double>::max();
|
|
|
|
double freq = startFreq; // Main spectrum seems to use bin midpoint - this uses lowest frequency, so we're tone at centre freq appears in centre of plot
|
|
|
|
// Plot power/temp
|
|
for (int i = 0; i < fft->m_fftSize; i++)
|
|
{
|
|
qreal value;
|
|
switch (m_settings.m_spectrumYScale)
|
|
{
|
|
case RadioAstronomySettings::SY_DBFS:
|
|
value = fft->m_db[i];
|
|
break;
|
|
case RadioAstronomySettings::SY_SNR:
|
|
value = fft->m_snr[i];
|
|
break;
|
|
case RadioAstronomySettings::SY_DBM:
|
|
value = Astronomy::noisePowerdBm(fft->m_temp[i], fft->m_sampleRate/(double)fft->m_fftSize);
|
|
break;
|
|
case RadioAstronomySettings::SY_TSYS:
|
|
value = fft->m_temp[i];
|
|
break;
|
|
case RadioAstronomySettings::SY_TSOURCE:
|
|
switch (m_settings.m_spectrumBaseline)
|
|
{
|
|
case RadioAstronomySettings::SBL_TSYS0:
|
|
value = fft->m_temp[i] - fft->m_tSys0;
|
|
break;
|
|
case RadioAstronomySettings::SBL_TMIN:
|
|
value = fft->m_temp[i] - fft->m_tempMin;
|
|
break;
|
|
case RadioAstronomySettings::SBL_CAL_COLD:
|
|
if (m_calCold) {
|
|
value = m_calG[i] * (fft->m_fftData[i] - m_calCold->m_fftData[i]);
|
|
} else {
|
|
value = 0.0;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
if (value > peakValue)
|
|
{
|
|
peakValue = value;
|
|
peakIdx = i;
|
|
}
|
|
|
|
m_fftSeries->append(freq / 1e6, value);
|
|
freq += binRes;
|
|
}
|
|
|
|
double startFreqMHz = fft->m_centerFrequency/1e6 - m_settings.m_spectrumSpan / 2.0;
|
|
|
|
spectrumUpdateXRange(fft);
|
|
spectrumUpdateYRange(fft);
|
|
m_fftXAxis->setReverse(m_settings.m_spectrumReverseXAxis);
|
|
|
|
switch (m_settings.m_spectrumYScale)
|
|
{
|
|
case RadioAstronomySettings::SY_DBFS:
|
|
m_fftYAxis->setTitleText("Power (dBFS)");
|
|
break;
|
|
case RadioAstronomySettings::SY_SNR:
|
|
m_fftYAxis->setTitleText("SNR");
|
|
break;
|
|
case RadioAstronomySettings::SY_DBM:
|
|
m_fftYAxis->setTitleText("Power (dBm)");
|
|
break;
|
|
case RadioAstronomySettings::SY_TSYS:
|
|
m_fftYAxis->setTitleText("Tsys (K)");
|
|
break;
|
|
case RadioAstronomySettings::SY_TSOURCE:
|
|
m_fftYAxis->setTitleText("Tsource (K)");
|
|
break;
|
|
}
|
|
|
|
// Plot peaks
|
|
if (m_settings.m_spectrumPeaks)
|
|
{
|
|
double peakFreqMHz = (startFreq + peakIdx * binRes) / 1e6;
|
|
double peakFreq = peakFreqMHz * 1e6;
|
|
|
|
m_fftPeakSeries->append(peakFreqMHz, peakValue);
|
|
|
|
ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_PEAK, SPECTRUM_MARKER_COL_FREQ)->setData(Qt::DisplayRole, peakFreqMHz);
|
|
ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_PEAK, SPECTRUM_MARKER_COL_VALUE)->setData(Qt::DisplayRole, peakValue);
|
|
|
|
calcVrAndDistanceToPeak(peakFreq, fft, SPECTRUM_MARKER_ROW_PEAK);
|
|
}
|
|
|
|
// Update markers to track current data
|
|
if (m_spectrumM1Valid)
|
|
{
|
|
m_fftMarkerSeries->clear();
|
|
int idx;
|
|
idx = (m_spectrumM1X - startFreqMHz) / (binRes/1e6);
|
|
if ((idx >= 0) && (idx < m_fftSeries->count()))
|
|
{
|
|
m_spectrumM1Y = m_fftSeries->at(idx).y();
|
|
ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_M1, SPECTRUM_MARKER_COL_VALUE)->setData(Qt::DisplayRole, m_spectrumM1Y);
|
|
m_fftMarkerSeries->append(m_spectrumM1X, m_spectrumM1Y);
|
|
calcVrAndDistanceToPeak(m_spectrumM1X*1e6, fft, SPECTRUM_MARKER_ROW_M1);
|
|
}
|
|
if (m_spectrumM2Valid)
|
|
{
|
|
idx = (m_spectrumM2X - startFreqMHz) / (binRes/1e6);
|
|
if (idx < m_fftSeries->count())
|
|
{
|
|
m_spectrumM2Y = m_fftSeries->at(idx).y();
|
|
ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_M2, SPECTRUM_MARKER_COL_VALUE)->setData(Qt::DisplayRole, m_spectrumM2Y);
|
|
m_fftMarkerSeries->append(m_spectrumM2X, m_spectrumM2Y);
|
|
calcVrAndDistanceToPeak(m_spectrumM2X*1e6, fft, SPECTRUM_MARKER_ROW_M2);
|
|
calcSpectrumMarkerDelta();
|
|
}
|
|
}
|
|
}
|
|
|
|
spectrumAutoscale();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool RadioAstronomyGUI::losMarkerEnabled(const QString& name)
|
|
{
|
|
if (m_settings.m_spectrumDistance && m_settings.m_spectrumRefLine)
|
|
{
|
|
if (name == "Max") {
|
|
return m_settings.m_spectrumPeaks;
|
|
} else if (name == "M1") {
|
|
return m_settings.m_spectrumMarkers;
|
|
} else {
|
|
return m_settings.m_spectrumMarkers;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void RadioAstronomyGUI::showLoSMarker(const QString& name)
|
|
{
|
|
if (losMarkerEnabled(name))
|
|
{
|
|
if (name == "Max") {
|
|
showLoSMarker(SPECTRUM_MARKER_ROW_PEAK);
|
|
} else if (name == "M1") {
|
|
showLoSMarker(SPECTRUM_MARKER_ROW_M1);
|
|
} else {
|
|
showLoSMarker(SPECTRUM_MARKER_ROW_M2);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::showLoSMarker(int row)
|
|
{
|
|
bool ok;
|
|
float d = ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_D)->data(Qt::DisplayRole).toFloat(&ok);
|
|
if (ok)
|
|
{
|
|
FFTMeasurement *fft = currentFFT();
|
|
if (fft)
|
|
{
|
|
QString name = ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_NAME)->text();
|
|
updateLoSMarker(name, fft->m_l, fft->m_b, d);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::updateLoSMarker(const QString& name, float l, float b, float d)
|
|
{
|
|
// Send to Star Tracker
|
|
QList<ObjectPipe*> starTrackerPipes;
|
|
MainCore::instance()->getMessagePipes().getMessagePipes(this, "startracker.display", starTrackerPipes);
|
|
|
|
for (const auto& pipe : starTrackerPipes)
|
|
{
|
|
MessageQueue *messageQueue = qobject_cast<MessageQueue*>(pipe->m_element);
|
|
SWGSDRangel::SWGStarTrackerDisplayLoSSettings *swgSettings = new SWGSDRangel::SWGStarTrackerDisplayLoSSettings();
|
|
swgSettings->setName(new QString(name));
|
|
swgSettings->setL(l);
|
|
swgSettings->setB(b);
|
|
swgSettings->setD(d);
|
|
messageQueue->push(MainCore::MsgStarTrackerDisplayLoSSettings::create(m_radioAstronomy, swgSettings));
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::clearLoSMarker(const QString& name)
|
|
{
|
|
// Set d to 0 to clear
|
|
updateLoSMarker(name, 0.0f, 0.0f, 0.0f);
|
|
}
|
|
|
|
void RadioAstronomyGUI::calcVrAndDistanceToPeak(double freq, FFTMeasurement *fft, int row)
|
|
{
|
|
double lineFreq = ui->spectrumLineFrequency->value() * 1e6;
|
|
|
|
// Calculate radial velocity (along line-of-sight) from Doppler shift
|
|
double vR = dopplerToVelocity(lineFreq, freq, fft);
|
|
ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_VR)->setData(Qt::DisplayRole, vR);
|
|
|
|
// Tangent point method only valid for Galactic quadrants I (0-90) and IV (270-360)
|
|
if ((fft->m_l < 90.0) || (fft->m_l > 270.0))
|
|
{
|
|
// Calculate minimum distance to Galactic centre along line of sight (tangential radius)
|
|
double rMin = m_settings.m_sunDistanceToGC * sin(Units::degreesToRadians(fft->m_l));
|
|
ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_R_MIN)->setData(Qt::DisplayRole, rMin);
|
|
|
|
// Calculate orbital velocity at tangent/minimum point
|
|
// This is the velocity to plot for the rotation curve
|
|
double w0 = m_settings.m_sunOrbitalVelocity / m_settings.m_sunDistanceToGC;
|
|
double vOrb = vR + rMin * w0;
|
|
ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_V)->setData(Qt::DisplayRole, vOrb);
|
|
}
|
|
else
|
|
{
|
|
ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_R_MIN)->setText("");
|
|
ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_V)->setText("");
|
|
}
|
|
|
|
// Calculate distance of HI cloud (as indicated by a peak) to Sun and Galactic center
|
|
double r, d1, d2;
|
|
int solutions = calcDistanceToPeak(vR, fft->m_l, fft->m_b, r, d1, d2);
|
|
if (solutions == 0)
|
|
{
|
|
ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_R)->setText("");
|
|
ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_D)->setText("");
|
|
}
|
|
else if (solutions == 1)
|
|
{
|
|
ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_R)->setData(Qt::DisplayRole, r);
|
|
ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_D)->setData(Qt::DisplayRole, d1);
|
|
}
|
|
else
|
|
{
|
|
ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_R)->setData(Qt::DisplayRole, r);
|
|
ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_D)->setText(QString("%1/%2").arg(QString::number(d1, 'f', 1)).arg(QString::number(d2, 'f', 1)));
|
|
}
|
|
|
|
// Send to Star Tracker
|
|
QString name = ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_NAME)->text();
|
|
if (losMarkerEnabled(name))
|
|
{
|
|
if ((solutions == 0) || std::isnan(d1))
|
|
{
|
|
updateLoSMarker(name, fft->m_l, fft->m_b, 0.0f);
|
|
}
|
|
else if (solutions == 1)
|
|
{
|
|
updateLoSMarker(name, fft->m_l, fft->m_b, d1);
|
|
}
|
|
else
|
|
{
|
|
bool plotMax = ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_PLOT_MAX)->checkState() == Qt::Checked;
|
|
if ((plotMax && (d1 > d2)) || (!plotMax && (d1 < d2))) {
|
|
updateLoSMarker(name, fft->m_l, fft->m_b, d1);
|
|
} else {
|
|
updateLoSMarker(name, fft->m_l, fft->m_b, d2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int RadioAstronomyGUI::calcDistanceToPeak(double vr, float l, float b, double& r, double &d1, double &d2)
|
|
{
|
|
// Radio Astronomy 4th edition - Burke - p343
|
|
double r0 = m_settings.m_sunDistanceToGC; // Distance of Sun to Galactic centre in kpc
|
|
double v0 = m_settings.m_sunOrbitalVelocity; // Orbital velocity of the Sun around Galactic centre in km/s
|
|
double w0 = v0/r0; // Angular velocity of the Sun
|
|
double gl = Units::degreesToRadians(l);
|
|
double gb = Units::degreesToRadians(b);
|
|
double w = vr/(r0*sin(gl)*cos(gb))+w0;
|
|
r = v0/w; // Assume constant v, regardless of distance from GC - Dark matter magic - Not valid <1kpc from GC
|
|
if (r < 0) {
|
|
return 0;
|
|
}
|
|
// https://en.wikipedia.org/wiki/Solution_of_triangles#Two_sides_and_non-included_angle_given_(SSA)
|
|
double beta = gl;
|
|
double d = sin(beta)*r0/r;
|
|
if (d > 1.0) {
|
|
// No solutions
|
|
return 0;
|
|
}
|
|
if ((r <= r0) && (beta >= M_PI/2.0)) {
|
|
return 0;
|
|
}
|
|
double gamma = asin(d);
|
|
double alpha1 = M_PI - beta - gamma;
|
|
d1 = r * sin(alpha1)/sin(beta); // Distance from Sun to peak
|
|
if (r < r0)
|
|
{
|
|
double alpha2 = M_PI - beta - (M_PI-gamma);
|
|
d2 = r * sin(alpha2)/sin(beta); // Distance from Sun to peak
|
|
return 2;
|
|
}
|
|
else
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::spectrumMarkerTableItemChanged(QTableWidgetItem *item)
|
|
{
|
|
if (item->column() == SPECTRUM_MARKER_COL_PLOT_MAX) {
|
|
// Plot max checkbox clicked
|
|
calcDistances();
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::calcDistances()
|
|
{
|
|
double freq;
|
|
bool ok;
|
|
FFTMeasurement* fft = currentFFT();
|
|
if (fft)
|
|
{
|
|
freq = ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_PEAK, SPECTRUM_MARKER_COL_FREQ)->data(Qt::DisplayRole).toDouble(&ok);
|
|
if (ok) {
|
|
calcVrAndDistanceToPeak(freq*1e6, fft, SPECTRUM_MARKER_ROW_PEAK);
|
|
}
|
|
freq = ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_M1, SPECTRUM_MARKER_COL_FREQ)->data(Qt::DisplayRole).toDouble(&ok);
|
|
if (ok) {
|
|
calcVrAndDistanceToPeak(freq*1e6, fft, SPECTRUM_MARKER_ROW_M1);
|
|
}
|
|
freq = ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_M2, SPECTRUM_MARKER_COL_FREQ)->data(Qt::DisplayRole).toDouble(&ok);
|
|
if (ok) {
|
|
calcVrAndDistanceToPeak(freq*1e6, fft, SPECTRUM_MARKER_ROW_M2);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::plotRefLine(FFTMeasurement *fft)
|
|
{
|
|
double lineFreqMHz = ui->spectrumLineFrequency->value();
|
|
double lineFreq = lineFreqMHz * 1e6;
|
|
QString refFrame[] = {"Topocentric", "Barycentric", "LSR"};
|
|
m_fftDopplerAxis->setTitleText(QString("%1 radial velocity (km/s - +ve receeding)").arg(refFrame[m_settings.m_refFrame]));
|
|
m_fftHlineSeries->setName(QString("%1 line").arg(ui->spectrumLine->currentText()));
|
|
m_fftHlineSeries->append(0.0f, -200.0f); // For dB
|
|
m_fftHlineSeries->append(0.0f, 10000.0f); // For temp can be >1e6?
|
|
double startFreq = fft->m_centerFrequency + m_settings.m_spectrumSpan*1e6 / 2.0 + m_settings.m_spectrumCenterFreqOffset*1e6;
|
|
double endFreq = fft->m_centerFrequency - m_settings.m_spectrumSpan*1e6 / 2.0 + m_settings.m_spectrumCenterFreqOffset*1e6;
|
|
m_fftDopplerAxis->setRange(dopplerToVelocity(lineFreq, startFreq, fft),
|
|
dopplerToVelocity(lineFreq, endFreq, fft));
|
|
m_fftDopplerAxis->setReverse(!m_settings.m_spectrumReverseXAxis);
|
|
m_fftDopplerAxis->setVisible(m_settings.m_spectrumRefLine);
|
|
}
|
|
|
|
void RadioAstronomyGUI::plotTempGaussian(double startFreq, double freqStep, int steps)
|
|
{
|
|
m_fftGaussianSeries->clear();
|
|
double f0 = ui->spectrumGaussianFreq->value() * 1e6;
|
|
double a = ui->spectrumGaussianAmp->value();
|
|
double floor = ui->spectrumGaussianFloor->value();
|
|
double fwhm = ui->spectrumGaussianFWHM->value();
|
|
double fwhm_sq = fwhm*fwhm;
|
|
double freq = startFreq;
|
|
for (int i = 0; i < steps; i++)
|
|
{
|
|
double fd = freq - f0;
|
|
double g = a * std::exp(-4.0*M_LN2*fd*fd/fwhm_sq) + floor;
|
|
m_fftGaussianSeries->append(freq/1e6, g);
|
|
freq += freqStep;
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::calcFFTPower(FFTMeasurement* fft)
|
|
{
|
|
// Convert linear to dB
|
|
for (int i = 0; i < fft->m_fftSize; i++) {
|
|
fft->m_db[i] = CalcDb::dbPower(fft->m_fftData[i]);
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::calcFFTTotalPower(FFTMeasurement* fft)
|
|
{
|
|
double total = 0.0;
|
|
for (int i = 0; i < fft->m_fftSize; i++) {
|
|
total += fft->m_fftData[i];
|
|
}
|
|
fft->m_totalPower = total;
|
|
fft->m_totalPowerdBFS = CalcDb::dbPower(total);
|
|
}
|
|
|
|
void RadioAstronomyGUI::calcFFTTemperatures(FFTMeasurement* fft)
|
|
{
|
|
if (m_calCold && !fft->m_snr) {
|
|
fft->m_snr = new Real[fft->m_fftSize];
|
|
}
|
|
if (m_calG && !fft->m_temp) {
|
|
fft->m_temp = new Real[fft->m_fftSize];
|
|
}
|
|
for (int i = 0; i < fft->m_fftSize; i++)
|
|
{
|
|
if (fft->m_snr && m_calCold)
|
|
{
|
|
// Calculate SNR (relative to cold cal)
|
|
fft->m_snr[i] = fft->m_fftData[i] / m_calCold->m_fftData[i];
|
|
}
|
|
if (m_calG)
|
|
{
|
|
// Calculate temperature using scaling from hot cal
|
|
fft->m_temp[i] = m_calG[i] * fft->m_fftData[i];
|
|
// Calculate temperature using linear interpolation from hot/cold cal
|
|
//fft->m_temp[i] = m_calG[i] * (fft->m_fftData[i] - m_calCold->m_fftData[i]) + m_settings.m_tCalCold;
|
|
//fft->m_temp[i] = std::max(fft->m_temp[i], 0.0f); // Can't have negative temperatures
|
|
}
|
|
}
|
|
calcFFTMinTemperature(fft);
|
|
}
|
|
|
|
void RadioAstronomyGUI::calcFFTMinTemperature(FFTMeasurement* fft)
|
|
{
|
|
fft->m_tempMin = 0;
|
|
if (fft->m_temp)
|
|
{
|
|
// Select minimum from within band. 95% of that to account for a little bit of inband rolloff
|
|
float tempMin = std::numeric_limits<float>::max();
|
|
double pc = 0.95 * fft->m_rfBandwidth / (double)fft->m_sampleRate;
|
|
int count = (int)(pc * fft->m_fftSize);
|
|
int start = (fft->m_fftSize - count) / 2;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
int idx = i + start;
|
|
tempMin = std::min(tempMin, fft->m_temp[idx]);
|
|
}
|
|
if (tempMin != std::numeric_limits<float>::max()) {
|
|
fft->m_tempMin = tempMin;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Estimate Tsource by subtracting selected baseline
|
|
double RadioAstronomyGUI::calcTSource(FFTMeasurement *fft) const
|
|
{
|
|
switch (fft->m_baseline)
|
|
{
|
|
case RadioAstronomySettings::SBL_TSYS0:
|
|
return fft->m_tSys - fft->m_tSys0;
|
|
case RadioAstronomySettings::SBL_TMIN:
|
|
return fft->m_tSys - fft->m_tempMin;
|
|
case RadioAstronomySettings::SBL_CAL_COLD:
|
|
if (m_calCold) {
|
|
return fft->m_tSys - m_calCold->m_tSys;
|
|
}
|
|
break;
|
|
}
|
|
return fft->m_tSys;
|
|
}
|
|
|
|
// Calculate spectral flux density of source
|
|
double RadioAstronomyGUI::calcFlux(double Ta, const FFTMeasurement *fft) const
|
|
{
|
|
// Factor of 2 here assumes single polarization
|
|
// See equation 5.13 and 5.20 in Radio Astronomy 4th edition - Burke
|
|
double lambda = Astronomy::m_speedOfLight / (double)fft->m_centerFrequency;
|
|
return 2.0 * Astronomy::m_boltzmann * Ta * fft->m_omegaA / (lambda * lambda);
|
|
}
|
|
|
|
void RadioAstronomyGUI::calcFFTTotalTemperature(FFTMeasurement* fft)
|
|
{
|
|
if (fft->m_temp)
|
|
{
|
|
double tempSum = 0.0;
|
|
for (int i = 0; i < fft->m_fftSize; i++) {
|
|
tempSum += fft->m_temp[i];
|
|
}
|
|
|
|
// Convert from temperature to power in Watts and dBm
|
|
Real bw = fft->m_sampleRate/(Real)fft->m_fftSize;
|
|
fft->m_totalPowerWatts = Astronomy::m_boltzmann * tempSum * bw;
|
|
fft->m_totalPowerdBm = Astronomy::noisePowerdBm(tempSum, bw);
|
|
fft->m_tSys = tempSum/fft->m_fftSize;
|
|
|
|
// Esimate source temperature
|
|
fft->m_tSource = calcTSource(fft);
|
|
|
|
// Calculate error due to thermal noise and gain variation
|
|
fft->m_sigmaT = calcSigmaT(fft);
|
|
fft->m_sigmaS = calcSigmaS(fft);
|
|
|
|
// Calculate spectral flux density of source
|
|
fft->m_flux = calcFlux(fft->m_tSource, fft);
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::updatePowerColumns(int row, FFTMeasurement* fft)
|
|
{
|
|
ui->powerTable->item(row, POWER_COL_TSYS)->setData(Qt::DisplayRole, fft->m_tSys);
|
|
ui->powerTable->item(row, POWER_COL_TSYS0)->setData(Qt::DisplayRole, fft->m_tSys0);
|
|
ui->powerTable->item(row, POWER_COL_TSOURCE)->setData(Qt::DisplayRole, fft->m_tSource);
|
|
if (m_settings.m_sourceType != RadioAstronomySettings::UNKNOWN) {
|
|
ui->powerTable->item(row, POWER_COL_TB)->setData(Qt::DisplayRole, fft->m_tSource/beamFillingFactor());
|
|
} else {
|
|
ui->powerTable->item(row, POWER_COL_TB)->setText("");
|
|
}
|
|
ui->powerTable->item(row, POWER_COL_FLUX)->setData(Qt::DisplayRole, Units::wattsPerMetrePerHertzToJansky(fft->m_flux));
|
|
ui->powerTable->item(row, POWER_COL_SIGMA_T)->setData(Qt::DisplayRole, fft->m_sigmaT);
|
|
ui->powerTable->item(row, POWER_COL_SIGMA_S)->setData(Qt::DisplayRole, fft->m_sigmaS);
|
|
ui->powerTable->item(row, POWER_COL_OMEGA_A)->setData(Qt::DisplayRole, fft->m_omegaA);
|
|
ui->powerTable->item(row, POWER_COL_OMEGA_S)->setData(Qt::DisplayRole, fft->m_omegaS);
|
|
}
|
|
|
|
void RadioAstronomyGUI::addFFT(FFTMeasurement *fft, bool skipCalcs)
|
|
{
|
|
m_fftMeasurements.append(fft);
|
|
|
|
powerMeasurementReceived(fft, skipCalcs); // Call before ui->spectrumIndex->setValue, so table row is valid
|
|
update2DImage(fft, skipCalcs);
|
|
|
|
ui->spectrumIndex->setRange(0, m_fftMeasurements.size() - 1);
|
|
if ((ui->spectrumIndex->value() == m_fftMeasurements.size() - 2) || (m_fftMeasurements.size() == 1)) {
|
|
ui->spectrumIndex->setValue(m_fftMeasurements.size() - 1);
|
|
}
|
|
if ( m_fftMeasurements.size() == 1)
|
|
{
|
|
// Force drawing for first measurement
|
|
on_spectrumIndex_valueChanged(0);
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::fftMeasurementReceived(const RadioAstronomy::MsgFFTMeasurement& measurement)
|
|
{
|
|
FFTMeasurement *fft = new FFTMeasurement();
|
|
fft->m_fftData = measurement.getFFT();
|
|
fft->m_fftSize = measurement.getSize();
|
|
fft->m_dateTime = measurement.getDateTime();
|
|
fft->m_centerFrequency = m_centerFrequency;
|
|
fft->m_sampleRate = m_settings.m_sampleRate;
|
|
fft->m_integration = m_settings.m_integration;
|
|
fft->m_rfBandwidth = m_settings.m_rfBandwidth;
|
|
fft->m_omegaA = calcOmegaA();
|
|
fft->m_omegaS = calcOmegaS();
|
|
fft->m_coordsValid = m_coordsValid;
|
|
fft->m_ra = m_ra;
|
|
fft->m_dec = m_dec;
|
|
fft->m_azimuth = m_azimuth;
|
|
fft->m_elevation = m_elevation;
|
|
fft->m_l = m_l;
|
|
fft->m_b = m_b;
|
|
fft->m_vBCRS = m_vBCRS;
|
|
fft->m_vLSR = m_vLSR;
|
|
fft->m_solarFlux = m_solarFlux;
|
|
fft->m_airTemp = m_airTemps.lastValue();
|
|
fft->m_skyTemp = m_skyTemp;
|
|
for (int i = 0; i < RADIOASTRONOMY_SENSORS; i++) {
|
|
fft->m_sensor[i] = m_sensors[i].lastValue();
|
|
}
|
|
fft->m_db = new Real[fft->m_fftSize];
|
|
fft->m_sweepIndex = m_sweepIndex++;
|
|
fft->m_tSys0 = calcTSys0();
|
|
fft->m_baseline = m_settings.m_spectrumBaseline;
|
|
|
|
calcFFTPower(fft);
|
|
calcFFTTotalPower(fft);
|
|
calcFFTTemperatures(fft);
|
|
calcFFTTotalTemperature(fft);
|
|
addFFT(fft);
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_spectrumIndex_valueChanged(int value)
|
|
{
|
|
if (value < m_fftMeasurements.size())
|
|
{
|
|
plotFFTMeasurement(value);
|
|
|
|
// Highlight in table
|
|
ui->powerTable->selectRow(value);
|
|
ui->powerTable->scrollTo(ui->powerTable->model()->index(value, 0));
|
|
ui->spectrumDateTime->setDateTime(m_fftMeasurements[value]->m_dateTime);
|
|
|
|
// Display target in Star Tracker
|
|
QList<ObjectPipe*> starTrackerPipes;
|
|
MainCore::instance()->getMessagePipes().getMessagePipes(this, "startracker.display", starTrackerPipes);
|
|
|
|
for (const auto& pipe : starTrackerPipes)
|
|
{
|
|
MessageQueue *messageQueue = qobject_cast<MessageQueue*>(pipe->m_element);
|
|
SWGSDRangel::SWGStarTrackerDisplaySettings *swgSettings = new SWGSDRangel::SWGStarTrackerDisplaySettings();
|
|
swgSettings->setDateTime(new QString(m_fftMeasurements[value]->m_dateTime.toString(Qt::ISODateWithMs)));
|
|
swgSettings->setAzimuth(m_fftMeasurements[value]->m_azimuth);
|
|
swgSettings->setElevation(m_fftMeasurements[value]->m_elevation);
|
|
messageQueue->push(MainCore::MsgStarTrackerDisplaySettings::create(m_radioAstronomy, swgSettings));
|
|
}
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::plotSpectrum()
|
|
{
|
|
QChart *oldChart = m_fftChart;
|
|
|
|
m_fftChart = new QChart();
|
|
|
|
m_fftChart->layout()->setContentsMargins(0, 0, 0, 0);
|
|
m_fftChart->setMargins(QMargins(1, 1, 1, 1));
|
|
m_fftChart->setTheme(QChart::ChartThemeDark);
|
|
|
|
m_fftChart->legend()->setAlignment(Qt::AlignRight);
|
|
m_fftChart->legend()->setVisible(m_settings.m_spectrumLegend);
|
|
|
|
m_fftSeries = new QLineSeries();
|
|
m_fftSeries->setName("Measurement");
|
|
connect(m_fftSeries, &QXYSeries::clicked, this, &RadioAstronomyGUI::spectrumSeries_clicked);
|
|
|
|
// Plot vertical reference spectral line
|
|
m_fftHlineSeries = new QLineSeries();
|
|
m_fftHlineSeries->setName(QString("%1 line").arg(ui->spectrumLine->currentText()));
|
|
m_fftHlineSeries->setVisible(m_settings.m_spectrumRefLine);
|
|
|
|
// Plot peak info
|
|
m_fftPeakSeries = new QScatterSeries();
|
|
m_fftPeakSeries->setPointLabelsVisible(true);
|
|
m_fftPeakSeries->setMarkerSize(5);
|
|
m_fftPeakSeries->setName("Max");
|
|
|
|
// Markers
|
|
m_fftMarkerSeries = new QScatterSeries();
|
|
m_fftMarkerSeries->setPointLabelsVisible(true);
|
|
m_fftMarkerSeries->setMarkerSize(5);
|
|
m_fftMarkerSeries->setName("Markers");
|
|
|
|
// Gaussian
|
|
m_fftGaussianSeries = new QLineSeries();
|
|
m_fftGaussianSeries->setName("Gaussian fit");
|
|
m_fftGaussianSeries->setVisible(m_settings.m_spectrumTemp);
|
|
|
|
m_fftLABSeries = new QLineSeries();
|
|
m_fftLABSeries->setName("LAB reference");
|
|
m_fftLABSeries->setVisible(m_settings.m_spectrumLAB);
|
|
|
|
m_fftXAxis = new QValueAxis();
|
|
m_fftYAxis = new QValueAxis();
|
|
m_fftDopplerAxis = new QValueAxis();
|
|
|
|
m_fftChart->addAxis(m_fftXAxis, Qt::AlignBottom);
|
|
m_fftChart->addAxis(m_fftYAxis, Qt::AlignLeft);
|
|
m_fftChart->addAxis(m_fftDopplerAxis, Qt::AlignTop);
|
|
|
|
m_fftXAxis->setTitleText("Frequency (MHz)");
|
|
calcSpectrumChartTickCount(m_fftXAxis, size().width());
|
|
calcSpectrumChartTickCount(m_fftDopplerAxis, size().width());
|
|
m_fftYAxis->setTitleText("Power");
|
|
|
|
m_fftChart->addSeries(m_fftSeries);
|
|
m_fftSeries->attachAxis(m_fftXAxis);
|
|
m_fftSeries->attachAxis(m_fftYAxis);
|
|
|
|
m_fftChart->addSeries(m_fftHlineSeries);
|
|
//m_fftHlineSeries->attachAxis(m_fftXAxis);
|
|
m_fftHlineSeries->attachAxis(m_fftDopplerAxis);
|
|
m_fftHlineSeries->attachAxis(m_fftYAxis);
|
|
|
|
m_fftChart->addSeries(m_fftGaussianSeries);
|
|
m_fftGaussianSeries->attachAxis(m_fftXAxis);
|
|
m_fftGaussianSeries->attachAxis(m_fftYAxis);
|
|
|
|
m_fftChart->addSeries(m_fftLABSeries);
|
|
//m_fftLABSeries->attachAxis(m_fftXAxis);
|
|
m_fftLABSeries->attachAxis(m_fftDopplerAxis);
|
|
m_fftLABSeries->attachAxis(m_fftYAxis);
|
|
|
|
m_fftChart->addSeries(m_fftPeakSeries);
|
|
m_fftPeakSeries->attachAxis(m_fftXAxis);
|
|
m_fftPeakSeries->attachAxis(m_fftYAxis);
|
|
|
|
m_fftChart->addSeries(m_fftMarkerSeries);
|
|
m_fftMarkerSeries->attachAxis(m_fftXAxis);
|
|
m_fftMarkerSeries->attachAxis(m_fftYAxis);
|
|
|
|
// Don't have peaks and markers in legend
|
|
m_fftChart->legend()->markers(m_fftPeakSeries)[0]->setVisible(false);
|
|
m_fftChart->legend()->markers(m_fftMarkerSeries)[0]->setVisible(false);
|
|
|
|
ui->spectrumChart->setChart(m_fftChart);
|
|
|
|
delete oldChart;
|
|
}
|
|
|
|
// Calculate galactic background temperature based on center frequency
|
|
void RadioAstronomyGUI::calcGalacticBackgroundTemp()
|
|
{
|
|
// https://arxiv.org/ftp/arxiv/papers/1912/1912.12699.pdf - page 6
|
|
// 17.1, 25.2 and 54.8 K for the 10th, 50th and 90th percentile of the all-sky distribution
|
|
// See also ITU-R P.372-7 section 6
|
|
// If this is used for cold calibration, we don't want to use the higher value
|
|
double temp = 25.2 * std::pow(m_centerFrequency/408000000.0, -2.75);
|
|
ui->tempGal->setValue(temp);
|
|
}
|
|
|
|
// Calculate athmospheric noise temperature based on air temperature, zenith opacity and elevation
|
|
void RadioAstronomyGUI::calcAtmosphericTemp()
|
|
{
|
|
float el = m_settings.m_elevation;
|
|
if (m_settings.m_elevation < 1.0f) {
|
|
el = 1.0f; // Avoid divide by 0 and limit max value to match ITU-R P.372-7 figure 5
|
|
}
|
|
double temp = Units::celsiusToKelvin(m_settings.m_tempAir) * (1.0 - std::exp(-m_settings.m_zenithOpacity/cos(Units::degreesToRadians(90.0f - el))));
|
|
ui->tempAtm->setValue(temp);
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_tempRXSelect_currentIndexChanged(int value)
|
|
{
|
|
if (value == 0)
|
|
{
|
|
// T_RX
|
|
ui->tempRX->setValue(m_settings.m_tempRX);
|
|
ui->tempRXUnitsLabel->setText("K");
|
|
}
|
|
else
|
|
{
|
|
// NF
|
|
ui->tempRX->setValue(Units::noiseTempToNoiseFigureTo(m_settings.m_tempRX));
|
|
ui->tempRXUnitsLabel->setText("dB");
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_tempRX_valueChanged(double value)
|
|
{
|
|
if (ui->tempRXSelect->currentIndex() == 0) {
|
|
m_settings.m_tempRX = value;
|
|
} else {
|
|
m_settings.m_tempRX = Units::noiseFigureToNoiseTemp(value);
|
|
}
|
|
updateTSys0();
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_tempCMB_valueChanged(double value)
|
|
{
|
|
m_settings.m_tempCMB = value;
|
|
updateTSys0();
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_tempGal_valueChanged(double value)
|
|
{
|
|
m_settings.m_tempGal = value;
|
|
updateTSys0();
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_tempSP_valueChanged(double value)
|
|
{
|
|
m_settings.m_tempSP = value;
|
|
updateTSys0();
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_tempAtm_valueChanged(double value)
|
|
{
|
|
m_settings.m_tempAtm = value;
|
|
updateTSys0();
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_tempAir_valueChanged(double value)
|
|
{
|
|
m_settings.m_tempAir = value;
|
|
if (m_settings.m_tempAtmLink) {
|
|
calcAtmosphericTemp();
|
|
}
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_zenithOpacity_valueChanged(double value)
|
|
{
|
|
m_settings.m_zenithOpacity = value;
|
|
if (m_settings.m_tempAtmLink) {
|
|
calcAtmosphericTemp();
|
|
}
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_elevation_valueChanged(double value)
|
|
{
|
|
m_settings.m_elevation = value;
|
|
if (m_settings.m_tempAtmLink) {
|
|
calcAtmosphericTemp();
|
|
}
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_elevationLink_toggled(bool checked)
|
|
{
|
|
m_settings.m_elevationLink = checked;
|
|
ui->elevation->setValue(m_elevation);
|
|
ui->elevation->setEnabled(!m_settings.m_elevationLink);
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_tempAtmLink_toggled(bool checked)
|
|
{
|
|
m_settings.m_tempAtmLink = checked;
|
|
ui->tempAtm->setEnabled(!m_settings.m_tempAtmLink);
|
|
if (checked) {
|
|
calcAtmosphericTemp();
|
|
}
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_tempAirLink_toggled(bool checked)
|
|
{
|
|
m_settings.m_tempAirLink = checked;
|
|
ui->tempAir->setEnabled(!m_settings.m_tempAirLink);
|
|
if (checked)
|
|
{
|
|
ui->tempAir->setValue(m_airTemps.lastValue());
|
|
calcAtmosphericTemp();
|
|
}
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_tempGalLink_toggled(bool checked)
|
|
{
|
|
m_settings.m_tempGalLink = checked;
|
|
if (checked) {
|
|
calcGalacticBackgroundTemp();
|
|
}
|
|
ui->tempGal->setEnabled(!m_settings.m_tempGalLink);
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_tCalHotSelect_currentIndexChanged(int value)
|
|
{
|
|
if (value == 0)
|
|
{
|
|
// Thot
|
|
ui->tCalHot->setValue(m_settings.m_tCalHot);
|
|
ui->tCalHotUnitsLabel->setText("K");
|
|
}
|
|
else
|
|
{
|
|
// Phot
|
|
double power = Astronomy::noisePowerdBm(m_settings.m_tCalHot, m_settings.m_sampleRate);
|
|
ui->tCalHot->setValue(power);
|
|
ui->tCalHotUnitsLabel->setText("dBm");
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_tCalHot_valueChanged(double value)
|
|
{
|
|
double temp;
|
|
if (ui->tCalHotSelect->currentIndex() == 0) {
|
|
temp = value;
|
|
} else {
|
|
temp = Astronomy::noiseTemp(value, m_settings.m_sampleRate);
|
|
}
|
|
m_settings.m_tCalHot = (float)temp;
|
|
calibrate();
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_tCalColdSelect_currentIndexChanged(int value)
|
|
{
|
|
if (value == 0)
|
|
{
|
|
// Tcold
|
|
ui->tCalCold->setValue(m_settings.m_tCalCold);
|
|
ui->tCalColdUnitsLabel->setText("K");
|
|
}
|
|
else
|
|
{
|
|
// Pcold
|
|
double power = Astronomy::noisePowerdBm(m_settings.m_tCalCold, m_settings.m_sampleRate);
|
|
ui->tCalCold->setValue(power);
|
|
ui->tCalColdUnitsLabel->setText("dBm");
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_tCalCold_valueChanged(double value)
|
|
{
|
|
double temp;
|
|
if (ui->tCalColdSelect->currentIndex() == 0) {
|
|
temp = value;
|
|
} else {
|
|
temp = Astronomy::noiseTemp(value, m_settings.m_sampleRate);
|
|
}
|
|
m_settings.m_tCalCold = (float)temp;
|
|
calibrate();
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_spectrumLine_currentIndexChanged(int value)
|
|
{
|
|
m_settings.m_line = (RadioAstronomySettings::Line)value;
|
|
displaySpectrumLineFrequency();
|
|
plotFFTMeasurement();
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::displaySpectrumLineFrequency()
|
|
{
|
|
switch (m_settings.m_line)
|
|
{
|
|
case RadioAstronomySettings::HI:
|
|
ui->spectrumLineFrequency->setValue(Astronomy::m_hydrogenLineFrequency / 1e6);
|
|
ui->spectrumLineFrequency->setEnabled(false);
|
|
break;
|
|
case RadioAstronomySettings::OH:
|
|
ui->spectrumLineFrequency->setValue(Astronomy::m_hydroxylLineFrequency / 1e6);
|
|
ui->spectrumLineFrequency->setEnabled(false);
|
|
break;
|
|
case RadioAstronomySettings::DI:
|
|
ui->spectrumLineFrequency->setValue(Astronomy::m_deuteriumLineFrequency / 1e6);
|
|
ui->spectrumLineFrequency->setEnabled(false);
|
|
break;
|
|
case RadioAstronomySettings::CUSTOM_LINE:
|
|
ui->spectrumLineFrequency->setValue(m_settings.m_lineCustomFrequency / 1e6);
|
|
ui->spectrumLineFrequency->setEnabled(true);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_spectrumLineFrequency_valueChanged(double value)
|
|
{
|
|
m_settings.m_lineCustomFrequency = value * 1e6;
|
|
plotFFTMeasurement();
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_refFrame_currentIndexChanged(int value)
|
|
{
|
|
m_settings.m_refFrame = (RadioAstronomySettings::RefFrame)value;
|
|
plotFFTMeasurement();
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_sunDistanceToGC_valueChanged(double value)
|
|
{
|
|
m_settings.m_sunDistanceToGC = value;
|
|
applySettings();
|
|
calcDistances();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_sunOrbitalVelocity_valueChanged(double value)
|
|
{
|
|
m_settings.m_sunOrbitalVelocity = value;
|
|
applySettings();
|
|
calcDistances();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_savePowerChartImage_clicked()
|
|
{
|
|
QFileDialog fileDialog(nullptr, "Select file to save image to", "", "*.png;*.jpg;*.jpeg;*.bmp;*.ppm;*.xbm;*.xpm");
|
|
fileDialog.setAcceptMode(QFileDialog::AcceptSave);
|
|
if (fileDialog.exec())
|
|
{
|
|
QStringList fileNames = fileDialog.selectedFiles();
|
|
if (fileNames.size() > 0)
|
|
{
|
|
QImage image(ui->powerChart->size(), QImage::Format_ARGB32);
|
|
image.fill(Qt::transparent);
|
|
QPainter painter(&image);
|
|
ui->powerChart->render(&painter);
|
|
if (!image.save(fileNames[0])) {
|
|
QMessageBox::critical(this, "Radio Astronomy", QString("Failed to save image to %1").arg(fileNames[0]));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_saveSpectrumChartImage_clicked()
|
|
{
|
|
QFileDialog fileDialog(nullptr, "Select file to save image to", "", "*.png;*.jpg;*.jpeg;*.bmp;*.ppm;*.xbm;*.xpm");
|
|
fileDialog.setAcceptMode(QFileDialog::AcceptSave);
|
|
if (fileDialog.exec())
|
|
{
|
|
QStringList fileNames = fileDialog.selectedFiles();
|
|
if (fileNames.size() > 0)
|
|
{
|
|
QImage image(ui->spectrumChart->size(), QImage::Format_ARGB32);
|
|
image.fill(Qt::transparent);
|
|
QPainter painter(&image);
|
|
ui->spectrumChart->render(&painter);
|
|
if (!image.save(fileNames[0])) {
|
|
QMessageBox::critical(this, "Radio Astronomy", QString("Failed to save image to %1").arg(fileNames[0]));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_saveSpectrumChartImages_clicked()
|
|
{
|
|
if (m_fftMeasurements.size() > 1)
|
|
{
|
|
// Get filename of animation file
|
|
QFileDialog fileDialog(nullptr, "Select file to save animation to", "", "*.png");
|
|
fileDialog.setAcceptMode(QFileDialog::AcceptSave);
|
|
if (fileDialog.exec())
|
|
{
|
|
QStringList fileNames = fileDialog.selectedFiles();
|
|
if (fileNames.size() > 0)
|
|
{
|
|
// Create animation file
|
|
APNG apng(m_fftMeasurements.size());
|
|
|
|
// Plot each FFT to a temp .png file (in memory) then append to animation file
|
|
for (int i = 0; i < m_fftMeasurements.size(); i++)
|
|
{
|
|
plotFFTMeasurement(i);
|
|
QApplication::processEvents(); // To get chart title to be updated
|
|
QImage image(ui->spectrumChart->size(), QImage::Format_ARGB32);
|
|
image.fill(Qt::transparent);
|
|
QPainter painter(&image);
|
|
ui->spectrumChart->render(&painter);
|
|
apng.addImage(image);
|
|
}
|
|
if (!apng.save(fileNames[0])) {
|
|
QMessageBox::critical(this, "Radio Astronomy", QString("Failed to write to file %1").arg(fileNames[0]));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_spectrumReverseXAxis_toggled(bool checked)
|
|
{
|
|
m_settings.m_spectrumReverseXAxis = checked;
|
|
applySettings();
|
|
if (ui->spectrumChartSelect->currentIndex() == 0) {
|
|
plotFFTMeasurement();
|
|
} else {
|
|
m_calXAxis->setReverse(m_settings.m_spectrumReverseXAxis);
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerShowPeak_toggled(bool checked)
|
|
{
|
|
m_settings.m_powerPeaks = checked;
|
|
updatePowerMarkerTableVisibility();
|
|
applySettings();
|
|
if (m_powerPeakSeries)
|
|
{
|
|
m_powerPeakSeries->setVisible(checked);
|
|
if (checked) {
|
|
m_powerChart->legend()->markers(m_powerPeakSeries)[0]->setVisible(false);
|
|
}
|
|
}
|
|
getRollupContents()->arrangeRollups();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_spectrumPeak_toggled(bool checked)
|
|
{
|
|
m_settings.m_spectrumPeaks = checked;
|
|
updateSpectrumMarkerTableVisibility();
|
|
plotFFTMeasurement();
|
|
applySettings();
|
|
if (m_fftChart)
|
|
{
|
|
if (checked) {
|
|
m_fftChart->legend()->markers(m_fftPeakSeries)[0]->setVisible(false);
|
|
showLoSMarker("Max");
|
|
} else {
|
|
clearLoSMarker("Max");
|
|
}
|
|
}
|
|
getRollupContents()->arrangeRollups();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerShowMarker_toggled(bool checked)
|
|
{
|
|
m_settings.m_powerMarkers = checked;
|
|
updatePowerMarkerTableVisibility();
|
|
applySettings();
|
|
if (m_powerMarkerSeries)
|
|
{
|
|
m_powerMarkerSeries->setVisible(checked);
|
|
if (checked) {
|
|
m_powerChart->legend()->markers(m_powerMarkerSeries)[0]->setVisible(false);
|
|
}
|
|
}
|
|
updatePowerSelect();
|
|
getRollupContents()->arrangeRollups();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerShowAvg_toggled(bool checked)
|
|
{
|
|
m_settings.m_powerAvg = checked;
|
|
applySettings();
|
|
ui->powerChartAvgWidgets->setVisible(checked);
|
|
getRollupContents()->arrangeRollups();
|
|
if (checked) {
|
|
calcAverages();
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerShowLegend_toggled(bool checked)
|
|
{
|
|
m_settings.m_powerLegend = checked;
|
|
applySettings();
|
|
if (m_powerChart)
|
|
{
|
|
if (checked) {
|
|
m_powerChart->legend()->show();
|
|
} else {
|
|
m_powerChart->legend()->hide();
|
|
}
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerShowTsys0_toggled(bool checked)
|
|
{
|
|
m_settings.m_powerShowTsys0 = checked;
|
|
applySettings();
|
|
if (m_powerTsys0Series) {
|
|
m_powerTsys0Series->setVisible(checked);
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerShowAirTemp_toggled(bool checked)
|
|
{
|
|
m_settings.m_powerShowAirTemp = checked;
|
|
applySettings();
|
|
m_airTemps.clicked(checked);
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerShowSensor1_toggled(bool checked)
|
|
{
|
|
m_settings.m_sensorVisible[0] = checked;
|
|
applySettings();
|
|
m_sensors[0].clicked(checked);
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerShowSensor2_toggled(bool checked)
|
|
{
|
|
m_settings.m_sensorVisible[1] = checked;
|
|
applySettings();
|
|
m_sensors[1].clicked(checked);
|
|
}
|
|
|
|
void RadioAstronomyGUI::updatePowerMarkerTableVisibility()
|
|
{
|
|
ui->powerMarkerTableWidgets->setVisible(m_settings.m_powerPeaks || m_settings.m_powerMarkers);
|
|
if (m_settings.m_powerPeaks)
|
|
{
|
|
ui->powerMarkerTable->showRow(POWER_MARKER_ROW_PEAK_MAX);
|
|
ui->powerMarkerTable->showRow(POWER_MARKER_ROW_PEAK_MIN);
|
|
}
|
|
else
|
|
{
|
|
ui->powerMarkerTable->hideRow(POWER_MARKER_ROW_PEAK_MAX);
|
|
ui->powerMarkerTable->hideRow(POWER_MARKER_ROW_PEAK_MIN);
|
|
}
|
|
if (m_settings.m_powerMarkers)
|
|
{
|
|
ui->powerMarkerTable->showRow(POWER_MARKER_ROW_M1);
|
|
ui->powerMarkerTable->showRow(POWER_MARKER_ROW_M2);
|
|
}
|
|
else
|
|
{
|
|
ui->powerMarkerTable->hideRow(POWER_MARKER_ROW_M1);
|
|
ui->powerMarkerTable->hideRow(POWER_MARKER_ROW_M2);
|
|
}
|
|
ui->powerMarkerTableWidgets->updateGeometry(); // Without this, widgets aren't resized properly
|
|
}
|
|
|
|
void RadioAstronomyGUI::updateSpectrumMarkerTableVisibility()
|
|
{
|
|
bool fft = ui->spectrumChartSelect->currentIndex() == 0;
|
|
ui->spectrumMarkerTableWidgets->setVisible(fft && (m_settings.m_spectrumPeaks || m_settings.m_spectrumMarkers));
|
|
if (m_settings.m_spectrumPeaks)
|
|
{
|
|
ui->spectrumMarkerTable->showRow(SPECTRUM_MARKER_ROW_PEAK);
|
|
}
|
|
else
|
|
{
|
|
ui->spectrumMarkerTable->hideRow(SPECTRUM_MARKER_ROW_PEAK);
|
|
}
|
|
if (m_settings.m_spectrumMarkers)
|
|
{
|
|
ui->spectrumMarkerTable->showRow(SPECTRUM_MARKER_ROW_M1);
|
|
ui->spectrumMarkerTable->showRow(SPECTRUM_MARKER_ROW_M2);
|
|
}
|
|
else
|
|
{
|
|
ui->spectrumMarkerTable->hideRow(SPECTRUM_MARKER_ROW_M1);
|
|
ui->spectrumMarkerTable->hideRow(SPECTRUM_MARKER_ROW_M2);
|
|
}
|
|
ui->spectrumMarkerTableWidgets->updateGeometry(); // Without this, widgets aren't resized properly
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_spectrumMarker_toggled(bool checked)
|
|
{
|
|
m_settings.m_spectrumMarkers = checked;
|
|
applySettings();
|
|
updateSpectrumMarkerTableVisibility();
|
|
m_fftMarkerSeries->setVisible(checked);
|
|
if (checked)
|
|
{
|
|
m_fftChart->legend()->markers(m_fftMarkerSeries)[0]->setVisible(false);
|
|
showLoSMarker("M1");
|
|
showLoSMarker("M2");
|
|
}
|
|
else
|
|
{
|
|
clearLoSMarker("M1");
|
|
clearLoSMarker("M2");
|
|
}
|
|
updateSpectrumSelect();
|
|
getRollupContents()->arrangeRollups();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_spectrumTemp_toggled(bool checked)
|
|
{
|
|
m_settings.m_spectrumTemp = checked;
|
|
applySettings();
|
|
ui->spectrumGaussianWidgets->setVisible(checked);
|
|
m_fftGaussianSeries->setVisible(checked);
|
|
updateSpectrumSelect();
|
|
getRollupContents()->arrangeRollups();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_spectrumShowLegend_toggled(bool checked)
|
|
{
|
|
m_settings.m_spectrumLegend = checked;
|
|
applySettings();
|
|
if (m_fftChart)
|
|
{
|
|
m_fftChart->legend()->setVisible(checked);
|
|
m_calChart->legend()->setVisible(checked);
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_spectrumShowRefLine_toggled(bool checked)
|
|
{
|
|
m_settings.m_spectrumRefLine = checked;
|
|
applySettings();
|
|
ui->spectrumRefLineWidgets->setVisible(checked);
|
|
if (m_fftHlineSeries)
|
|
{
|
|
m_fftHlineSeries->setVisible(m_settings.m_spectrumRefLine);
|
|
m_fftDopplerAxis->setVisible(m_settings.m_spectrumRefLine);
|
|
}
|
|
updateDistanceColumns();
|
|
getRollupContents()->arrangeRollups();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_spectrumShowLAB_toggled(bool checked)
|
|
{
|
|
m_settings.m_spectrumLAB = checked;
|
|
applySettings();
|
|
m_fftLABSeries->setVisible(m_settings.m_spectrumLAB);
|
|
if (m_settings.m_spectrumLAB) {
|
|
plotLAB(); // Replot incase data needs to be downloaded
|
|
}
|
|
spectrumAutoscale();
|
|
}
|
|
|
|
void RadioAstronomyGUI::updateDistanceColumns()
|
|
{
|
|
if (m_settings.m_spectrumDistance && m_settings.m_spectrumRefLine)
|
|
{
|
|
ui->spectrumMarkerTable->showColumn(SPECTRUM_MARKER_COL_R);
|
|
ui->spectrumMarkerTable->showColumn(SPECTRUM_MARKER_COL_D);
|
|
ui->spectrumMarkerTable->showColumn(SPECTRUM_MARKER_COL_PLOT_MAX);
|
|
ui->spectrumMarkerTable->showColumn(SPECTRUM_MARKER_COL_R_MIN);
|
|
ui->spectrumMarkerTable->showColumn(SPECTRUM_MARKER_COL_V);
|
|
showLoSMarker("Max");
|
|
showLoSMarker("M1");
|
|
showLoSMarker("M2");
|
|
ui->sunDistanceToGCLine->setVisible(true);
|
|
ui->sunDistanceToGCLabel->setVisible(true);
|
|
ui->sunDistanceToGC->setVisible(true);
|
|
ui->sunDistanceToGCUnits->setVisible(true);
|
|
ui->sunOrbitalVelocityLine->setVisible(true);
|
|
ui->sunOrbitalVelocityLabel->setVisible(true);
|
|
ui->sunOrbitalVelocity->setVisible(true);
|
|
ui->sunOrbitalVelocityUnits->setVisible(true);
|
|
}
|
|
else
|
|
{
|
|
ui->spectrumMarkerTable->hideColumn(SPECTRUM_MARKER_COL_R);
|
|
ui->spectrumMarkerTable->hideColumn(SPECTRUM_MARKER_COL_D);
|
|
ui->spectrumMarkerTable->hideColumn(SPECTRUM_MARKER_COL_PLOT_MAX);
|
|
ui->spectrumMarkerTable->hideColumn(SPECTRUM_MARKER_COL_R_MIN);
|
|
ui->spectrumMarkerTable->hideColumn(SPECTRUM_MARKER_COL_V);
|
|
clearLoSMarker("Max");
|
|
clearLoSMarker("M1");
|
|
clearLoSMarker("M2");
|
|
ui->sunDistanceToGCLine->setVisible(false);
|
|
ui->sunDistanceToGCLabel->setVisible(false);
|
|
ui->sunDistanceToGC->setVisible(false);
|
|
ui->sunDistanceToGCUnits->setVisible(false);
|
|
ui->sunOrbitalVelocityLine->setVisible(false);
|
|
ui->sunOrbitalVelocityLabel->setVisible(false);
|
|
ui->sunOrbitalVelocity->setVisible(false);
|
|
ui->sunOrbitalVelocityUnits->setVisible(false);
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_spectrumShowDistance_toggled(bool checked)
|
|
{
|
|
m_settings.m_spectrumDistance = checked;
|
|
applySettings();
|
|
if (m_settings.m_spectrumDistance && !m_settings.m_spectrumRefLine) {
|
|
ui->spectrumShowRefLine->setChecked(true);
|
|
}
|
|
updateDistanceColumns();
|
|
}
|
|
|
|
// point isn't necessarily a point in the series - may be interpolated
|
|
void RadioAstronomyGUI::powerSeries_clicked(const QPointF &point)
|
|
{
|
|
QString selection = ui->powerSelect->currentText();
|
|
if (selection.startsWith("M"))
|
|
{
|
|
if (selection == "M1")
|
|
{
|
|
// Place marker 1
|
|
m_powerM1X = point.x();
|
|
m_powerM1Y = point.y();
|
|
if (m_powerM1Valid) {
|
|
m_powerMarkerSeries->replace(0, m_powerM1X, m_powerM1Y);
|
|
} else {
|
|
m_powerMarkerSeries->insert(0, QPointF(m_powerM1X, m_powerM1Y));
|
|
}
|
|
m_powerM1Valid = true;
|
|
QDateTime dt = QDateTime::fromMSecsSinceEpoch(m_powerM1X);
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_M1, POWER_MARKER_COL_DATE)->setData(Qt::DisplayRole, dt.date());
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_M1, POWER_MARKER_COL_TIME)->setData(Qt::DisplayRole, dt.time());
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_M1, POWER_MARKER_COL_VALUE)->setData(Qt::DisplayRole, m_powerM1Y);
|
|
calcPowerMarkerDelta();
|
|
}
|
|
else if (selection == "M2")
|
|
{
|
|
// Place marker 2
|
|
m_powerM2X = point.x();
|
|
m_powerM2Y = point.y();
|
|
if (m_powerM2Valid) {
|
|
m_powerMarkerSeries->replace(1, m_powerM2X, m_powerM2Y);
|
|
} else {
|
|
m_powerMarkerSeries->insert(1, QPointF(m_powerM2X, m_powerM2Y));
|
|
}
|
|
m_powerM2Valid = true;
|
|
QDateTime dt = QDateTime::fromMSecsSinceEpoch(m_powerM2X);
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_M2, POWER_MARKER_COL_DATE)->setData(Qt::DisplayRole, dt.date());
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_M2, POWER_MARKER_COL_TIME)->setData(Qt::DisplayRole, dt.time());
|
|
ui->powerMarkerTable->item(POWER_MARKER_ROW_M2, POWER_MARKER_COL_VALUE)->setData(Qt::DisplayRole, m_powerM2Y);
|
|
calcPowerMarkerDelta();
|
|
}
|
|
}
|
|
else if (selection == "Gaussian")
|
|
{
|
|
// Fit a Gaussian assuming point clicked is the peak
|
|
ui->powerGaussianCenter->setDateTime(QDateTime::fromMSecsSinceEpoch(point.x()));
|
|
// Calculate noise floor - take average of lowest 10%
|
|
qreal floor = calcSeriesFloor(m_powerSeries);
|
|
ui->powerGaussianFloor->setValue(floor);
|
|
// Set amplitude to achieve selected point
|
|
ui->powerGaussianAmp->setValue(point.y() - floor);
|
|
}
|
|
else
|
|
{
|
|
if (m_fftMeasurements.size() > 1)
|
|
{
|
|
// Select row closest to clicked data
|
|
QDateTime dt = QDateTime::fromMSecsSinceEpoch(point.x());
|
|
int i = 0;
|
|
while ((i < m_fftMeasurements.size()) && (dt > m_fftMeasurements[i]->m_dateTime)) {
|
|
i++;
|
|
}
|
|
if (i < m_fftMeasurements.size()) {
|
|
ui->spectrumIndex->setValue(i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
qreal RadioAstronomyGUI::calcSeriesFloor(QXYSeries *series, int percent)
|
|
{
|
|
QList<qreal> minValues;
|
|
double count = series->count() * percent / 100.0;
|
|
for (int i = 0; i < series->count(); i++)
|
|
{
|
|
qreal y = series->at(i).y();
|
|
if (minValues.size() < count)
|
|
{
|
|
minValues.append(y);
|
|
std::sort(minValues.begin(), minValues.end());
|
|
}
|
|
else if (y < minValues.last())
|
|
{
|
|
minValues.append(y);
|
|
std::sort(minValues.begin(), minValues.end());
|
|
}
|
|
}
|
|
qreal sum = std::accumulate(minValues.begin(), minValues.end(), 0.0);
|
|
return sum / minValues.size();
|
|
}
|
|
|
|
void RadioAstronomyGUI::updateSpectrumSelect()
|
|
{
|
|
ui->spectrumSelect->clear();
|
|
if (m_settings.m_spectrumMarkers)
|
|
{
|
|
ui->spectrumSelect->addItem("M1");
|
|
ui->spectrumSelect->addItem("M2");
|
|
}
|
|
if (m_settings.m_spectrumTemp)
|
|
{
|
|
ui->spectrumSelect->addItem("Gaussian");
|
|
}
|
|
bool visible = ui->spectrumSelect->count() != 0;
|
|
ui->spectrumSelectLabel->setVisible(visible);
|
|
ui->spectrumSelect->setVisible(visible);
|
|
}
|
|
|
|
void RadioAstronomyGUI::updatePowerSelect()
|
|
{
|
|
ui->powerSelect->clear();
|
|
if (m_settings.m_powerMarkers || m_settings.m_powerShowGaussian) {
|
|
ui->powerSelect->addItem("Row");
|
|
}
|
|
if (m_settings.m_powerMarkers)
|
|
{
|
|
ui->powerSelect->addItem("M1");
|
|
ui->powerSelect->addItem("M2");
|
|
}
|
|
if (m_settings.m_powerShowGaussian) {
|
|
ui->powerSelect->addItem("Gaussian");
|
|
}
|
|
bool visible = ui->powerSelect->count() != 0;
|
|
ui->powerSelectLabel->setVisible(visible);
|
|
ui->powerSelect->setVisible(visible);
|
|
}
|
|
|
|
void RadioAstronomyGUI::spectrumSeries_clicked(const QPointF &point)
|
|
{
|
|
QString selection = ui->spectrumSelect->currentText();
|
|
if (selection.startsWith("M"))
|
|
{
|
|
FFTMeasurement *fft = currentFFT();
|
|
if (selection == "M1")
|
|
{
|
|
m_spectrumM1X = point.x();
|
|
m_spectrumM1Y = point.y();
|
|
m_spectrumM1Valid = true;
|
|
ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_M1, SPECTRUM_MARKER_COL_FREQ)->setData(Qt::DisplayRole, m_spectrumM1X);
|
|
ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_M1, SPECTRUM_MARKER_COL_VALUE)->setData(Qt::DisplayRole, m_spectrumM1Y);
|
|
calcVrAndDistanceToPeak(m_spectrumM1X*1e6, fft, SPECTRUM_MARKER_ROW_M1);
|
|
}
|
|
else if (selection == "M2")
|
|
{
|
|
m_spectrumM2X = point.x();
|
|
m_spectrumM2Y = point.y();
|
|
m_spectrumM2Valid = true;
|
|
ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_M2, SPECTRUM_MARKER_COL_FREQ)->setData(Qt::DisplayRole, m_spectrumM2X);
|
|
ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_M2, SPECTRUM_MARKER_COL_VALUE)->setData(Qt::DisplayRole, m_spectrumM2Y);
|
|
calcVrAndDistanceToPeak(m_spectrumM2X*1e6, fft, SPECTRUM_MARKER_ROW_M2);
|
|
}
|
|
calcSpectrumMarkerDelta();
|
|
|
|
m_fftMarkerSeries->clear();
|
|
if (m_spectrumM1Valid) {
|
|
m_fftMarkerSeries->append(m_spectrumM1X, m_spectrumM1Y);
|
|
}
|
|
if (m_spectrumM2Valid) {
|
|
m_fftMarkerSeries->append(m_spectrumM2X, m_spectrumM2Y);
|
|
}
|
|
}
|
|
else if (selection == "Gaussian")
|
|
{
|
|
ui->spectrumGaussianFreq->setValue(point.x());
|
|
// Calculate noise floor - take average of lowest 10%
|
|
qreal floor = calcSeriesFloor(m_fftSeries);
|
|
ui->spectrumGaussianFloor->setValue(floor);
|
|
// Set amplitude to achieve selected point
|
|
ui->spectrumGaussianAmp->setValue(point.y() - floor);
|
|
plotFFTMeasurement();
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_spectrumGaussianFreq_valueChanged(double value)
|
|
{
|
|
(void) value;
|
|
calcFWHM();
|
|
plotFFTMeasurement();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_spectrumGaussianAmp_valueChanged(double value)
|
|
{
|
|
(void) value;
|
|
plotFFTMeasurement();
|
|
calcColumnDensity();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_spectrumGaussianFloor_valueChanged(double value)
|
|
{
|
|
(void) value;
|
|
plotFFTMeasurement();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_spectrumGaussianFWHM_valueChanged(double fFWHM)
|
|
{
|
|
double c = Astronomy::m_speedOfLight;
|
|
double k = Astronomy::m_boltzmann;
|
|
double m = Astronomy::m_hydrogenMass;
|
|
double f0 = ui->spectrumGaussianFreq->value() * 1e6;
|
|
double vTurb = ui->spectrumGaussianTurb->value() * 1e3; // RSM turbulent velocties - Convert to m/s
|
|
const double bf = 2.0*sqrt(M_LN2); // Convert from Doppler parameter to FWHM
|
|
|
|
double fr = fFWHM * c / (bf*f0);
|
|
double frs = fr*fr;
|
|
|
|
double T = m * (frs-vTurb*vTurb) / (2.0*k);
|
|
|
|
ui->spectrumTemperature->blockSignals(true);
|
|
ui->spectrumTemperature->setValue(T);
|
|
ui->spectrumTemperature->blockSignals(false);
|
|
plotFFTMeasurement();
|
|
calcColumnDensity();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_spectrumGaussianTurb_valueChanged(double value)
|
|
{
|
|
(void) value;
|
|
calcFWHM();
|
|
plotFFTMeasurement();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_spectrumTemperature_valueChanged(double value)
|
|
{
|
|
(void) value;
|
|
calcFWHM();
|
|
plotFFTMeasurement();
|
|
}
|
|
|
|
void RadioAstronomyGUI::calcFWHM()
|
|
{
|
|
double c = Astronomy::m_speedOfLight;
|
|
double k = Astronomy::m_boltzmann;
|
|
double m = Astronomy::m_hydrogenMass;
|
|
double f0 = ui->spectrumGaussianFreq->value() * 1e6;
|
|
double vTurb = ui->spectrumGaussianTurb->value() * 1e3; // RSM turbulent velocties - Convert to m/s
|
|
double T = ui->spectrumTemperature->value();
|
|
const double bf = 2.0*sqrt(M_LN2); // Convert from Doppler parameter to FWHM
|
|
|
|
double fFWHM = bf * f0/c * sqrt((2*k*T)/m + vTurb * vTurb);
|
|
|
|
ui->spectrumGaussianFWHM->blockSignals(true);
|
|
ui->spectrumGaussianFWHM->setValue(fFWHM);
|
|
ui->spectrumGaussianFWHM->blockSignals(false);
|
|
calcColumnDensity();
|
|
}
|
|
|
|
// Assumes optically thin
|
|
void RadioAstronomyGUI::calcColumnDensity()
|
|
{
|
|
double f0 = ui->spectrumLineFrequency->value() * 1e6;
|
|
double f = f0 + ui->spectrumGaussianFWHM->value() / 2.0;
|
|
double v = lineDopplerVelocity(f0, f) * 2.0;
|
|
double a = ui->spectrumGaussianAmp->value();
|
|
double integratedIntensity = v * a;
|
|
double columnDensity = 1.81e18 * integratedIntensity;
|
|
ui->columnDensity->setText(QString::number(columnDensity, 'g', 2));
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerShowGaussian_clicked(bool checked)
|
|
{
|
|
m_settings.m_powerShowGaussian = checked;
|
|
applySettings();
|
|
ui->powerGaussianWidgets->setVisible(checked);
|
|
m_powerGaussianSeries->setVisible(checked);
|
|
updatePowerSelect();
|
|
getRollupContents()->arrangeRollups();
|
|
update();
|
|
}
|
|
|
|
void RadioAstronomyGUI::plotPowerGaussian()
|
|
{
|
|
m_powerGaussianSeries->clear();
|
|
double dt0 = ui->powerGaussianCenter->dateTime().toMSecsSinceEpoch();
|
|
double a = ui->powerGaussianAmp->value();
|
|
double floor = ui->powerGaussianFloor->value();
|
|
double fwhm = ui->powerGaussianFWHM->value() * 1000; // Convert from s to ms
|
|
double fwhm_sq = fwhm*fwhm;
|
|
qint64 dt = m_powerXAxis->min().toMSecsSinceEpoch();
|
|
qint64 end = m_powerXAxis->max().toMSecsSinceEpoch();
|
|
int steps = 256;
|
|
qint64 step = (end - dt) / steps;
|
|
for (int i = 0; i < steps; i++)
|
|
{
|
|
double fd = dt - dt0;
|
|
double g = a * std::exp(-4.0*M_LN2*fd*fd/fwhm_sq) + floor;
|
|
m_powerGaussianSeries->append(dt, g);
|
|
dt += step;
|
|
}
|
|
}
|
|
|
|
// Calculate antenna HPBW from Sun's FWHM time
|
|
void RadioAstronomyGUI::calcHPBWFromFWHM()
|
|
{
|
|
double fwhmSeconds = ui->powerGaussianFWHM->value();
|
|
double sunDegPerSecond = 360.0/(24.0*60.0*60.0);
|
|
double hpbwDeg = fwhmSeconds * sunDegPerSecond;
|
|
ui->powerGaussianHPBW->setValue(hpbwDeg);
|
|
}
|
|
|
|
// Calculate Sun's FWHM time for anntena HPBW
|
|
void RadioAstronomyGUI::calcFHWMFromHPBW()
|
|
{
|
|
double hpwmDeg = ui->powerGaussianHPBW->value();
|
|
double sunDegPerSecond = 360.0/(24.0*60.0*60.0);
|
|
double fwhmSeconds = hpwmDeg / sunDegPerSecond;
|
|
ui->powerGaussianFWHM->setValue(fwhmSeconds);
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerGaussianCenter_dateTimeChanged(QDateTime dateTime)
|
|
{
|
|
(void) dateTime;
|
|
plotPowerGaussian();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerGaussianAmp_valueChanged(double value)
|
|
{
|
|
(void) value;
|
|
plotPowerGaussian();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerGaussianFloor_valueChanged(double value)
|
|
{
|
|
(void) value;
|
|
plotPowerGaussian();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerGaussianFWHM_valueChanged(double value)
|
|
{
|
|
(void) value;
|
|
plotPowerGaussian();
|
|
ui->powerGaussianHPBW->blockSignals(true);
|
|
calcHPBWFromFWHM();
|
|
ui->powerGaussianHPBW->blockSignals(false);
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerGaussianHPBW_valueChanged(double value)
|
|
{
|
|
(void) value;
|
|
calcFHWMFromHPBW();
|
|
ui->powerGaussianFWHM->blockSignals(true);
|
|
plotPowerGaussian();
|
|
ui->powerGaussianFWHM->blockSignals(false);
|
|
}
|
|
|
|
void RadioAstronomyGUI::addToPowerFilter(qreal x, qreal y)
|
|
{
|
|
// Add data to circular buffer
|
|
m_window[m_windowIdx] = y;
|
|
m_windowIdx = (m_windowIdx + 1) % m_settings.m_powerFilterN;
|
|
if (m_windowCount < m_settings.m_powerFilterN) {
|
|
m_windowCount++;
|
|
}
|
|
|
|
// Filter
|
|
if (m_settings.m_powerFilter == RadioAstronomySettings::FILT_MOVING_AVERAGE)
|
|
{
|
|
// Moving average
|
|
qreal sum = 0.0;
|
|
for (int i = 0; i < m_windowCount; i++) {
|
|
sum += m_window[i];
|
|
}
|
|
qreal mean = sum / m_windowCount;
|
|
y = mean;
|
|
}
|
|
else
|
|
{
|
|
// Median
|
|
std::partial_sort_copy(m_window, m_window + m_windowCount, m_windowSorted, m_windowSorted + m_windowCount);
|
|
qreal median;
|
|
if ((m_windowCount & 1) == 1) {
|
|
median = m_windowSorted[m_windowCount / 2];
|
|
} else {
|
|
median = (m_windowSorted[m_windowCount / 2 - 1] + m_windowSorted[m_windowCount / 2]) / 2.0;
|
|
}
|
|
y = median;
|
|
}
|
|
|
|
// Add to series for chart
|
|
m_powerFilteredSeries->append(x, y);
|
|
}
|
|
|
|
void RadioAstronomyGUI::plotPowerFiltered()
|
|
{
|
|
delete[] m_window;
|
|
delete[] m_windowSorted;
|
|
m_window = new qreal[m_settings.m_powerFilterN];
|
|
m_windowSorted = new qreal[m_settings.m_powerFilterN];
|
|
m_windowIdx = 0;
|
|
m_windowCount = 0;
|
|
|
|
m_powerFilteredSeries->clear();
|
|
QVector<QPointF> powerSeries = m_powerSeries->pointsVector();
|
|
for (int i = 0; i < powerSeries.size(); i++)
|
|
{
|
|
QPointF point = powerSeries.at(i);
|
|
addToPowerFilter(point.x(), point.y());
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerShowFiltered_clicked(bool checked)
|
|
{
|
|
m_settings.m_powerShowFiltered = checked;
|
|
applySettings();
|
|
ui->powerFilterWidgets->setVisible(checked);
|
|
m_powerFilteredSeries->setVisible(checked);
|
|
getRollupContents()->arrangeRollups();
|
|
update();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerFilter_currentIndexChanged(int index)
|
|
{
|
|
m_settings.m_powerFilter = (RadioAstronomySettings::PowerFilter)index;
|
|
applySettings();
|
|
plotPowerFiltered();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerFilterN_valueChanged(int value)
|
|
{
|
|
m_settings.m_powerFilterN = value;
|
|
applySettings();
|
|
plotPowerFiltered();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_powerShowMeasurement_clicked(bool checked)
|
|
{
|
|
m_settings.m_powerShowMeasurement = checked;
|
|
applySettings();
|
|
m_powerSeries->setVisible(checked);
|
|
}
|
|
|
|
RadioAstronomyGUI::LABData* RadioAstronomyGUI::parseLAB(QFile* file, float l, float b)
|
|
{
|
|
LABData *data = new LABData();
|
|
data->read(file, l, b);
|
|
m_dataLAB.append(data);
|
|
return data;
|
|
}
|
|
|
|
void RadioAstronomyGUI::plotLAB()
|
|
{
|
|
int index = ui->spectrumIndex->value();
|
|
if (index < m_fftMeasurements.size())
|
|
{
|
|
FFTMeasurement *fft = m_fftMeasurements[index];
|
|
plotLAB(fft->m_l, fft->m_b, m_beamWidth);
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::plotLAB(float l, float b, float beamWidth)
|
|
{
|
|
// Assume a beamwidth >1deg
|
|
l = round(l);
|
|
b = round(b);
|
|
|
|
// Check if we already have the data in memory
|
|
LABData* data = nullptr;
|
|
for (int i = 0; i < m_dataLAB.size(); i++)
|
|
{
|
|
if ((m_dataLAB[i]->m_l == l) && (m_dataLAB[i]->m_b == b))
|
|
{
|
|
data = m_dataLAB[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!data)
|
|
{
|
|
// Try to open previously downloaded data
|
|
QString filenameLAB = HttpDownloadManager::downloadDir() + "/" + QString("lab_l_%1_b_%2.txt").arg(l).arg(b);
|
|
QFile file(filenameLAB);
|
|
if (file.open(QIODevice::ReadOnly))
|
|
{
|
|
qDebug() << "RadioAstronomyGUI::plotLAB: Using cached file: " << filenameLAB;
|
|
data = parseLAB(&file, l, b);
|
|
}
|
|
else
|
|
{
|
|
// Only download one file at a time, so we don't overload the server
|
|
if (!m_downloadingLAB)
|
|
{
|
|
m_downloadingLAB = true;
|
|
m_lLAB = l;
|
|
m_bLAB = b;
|
|
m_filenameLAB = filenameLAB;
|
|
|
|
// Request data be generated via web server
|
|
QNetworkRequest request(QUrl("https://www.astro.uni-bonn.de/hisurvey/euhou/LABprofile/index.php"));
|
|
request.setRawHeader("Content-Type", "application/x-www-form-urlencoded");
|
|
|
|
QUrlQuery params;
|
|
params.addQueryItem("coordinates", "lb");
|
|
params.addQueryItem("ral", QString::number(l));
|
|
params.addQueryItem("decb", QString::number(b));
|
|
params.addQueryItem("beam", QString::number(beamWidth));
|
|
params.addQueryItem("vmin", "-100.0" );
|
|
params.addQueryItem("vmax", "100.0" );
|
|
params.addQueryItem("search", "Search data" );
|
|
|
|
m_networkManager->post(request, params.query(QUrl::FullyEncoded).toUtf8());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (data)
|
|
{
|
|
data->toSeries(m_fftLABSeries);
|
|
spectrumAutoscale();
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::networkManagerFinished(QNetworkReply *reply)
|
|
{
|
|
QNetworkReply::NetworkError replyError = reply->error();
|
|
|
|
if (replyError)
|
|
{
|
|
qWarning() << "RadioAstronomyGUI::networkManagerFinished:"
|
|
<< " error(" << (int) replyError
|
|
<< "): " << replyError
|
|
<< ": " << reply->errorString();
|
|
m_downloadingLAB = false;
|
|
}
|
|
else
|
|
{
|
|
QString answer = reply->readAll();
|
|
QRegExp re("a href=\\\"download.php([^\"]*)\"");
|
|
if (re.indexIn(answer) != -1)
|
|
{
|
|
QString filename = re.capturedTexts()[1];
|
|
qDebug() << "RadioAstronomyGUI: Downloading LAB reference data: " << filename;
|
|
m_dlm.download(QUrl("https://www.astro.uni-bonn.de/hisurvey/euhou/LABprofile/download.php" + filename), m_filenameLAB);
|
|
}
|
|
else
|
|
{
|
|
qDebug() << "RadioAstronomyGUI::networkManagerFinished - No filename found: " << answer;
|
|
m_downloadingLAB = false;
|
|
}
|
|
}
|
|
|
|
reply->deleteLater();
|
|
}
|
|
|
|
void RadioAstronomyGUI::downloadFinished(const QString& filename, bool success)
|
|
{
|
|
if (success)
|
|
{
|
|
QFile file(filename);
|
|
if (file.open(QIODevice::ReadOnly))
|
|
{
|
|
LABData *data = parseLAB(&file, m_lLAB, m_bLAB);
|
|
file.close();
|
|
// Check if the data we've downloaded is for the current FFT being displayed
|
|
int index = ui->spectrumIndex->value();
|
|
if (index < m_fftMeasurements.size())
|
|
{
|
|
FFTMeasurement *fft = m_fftMeasurements[index];
|
|
if (m_lLAB == fft->m_l && m_bLAB == fft->m_b)
|
|
{
|
|
data->toSeries(m_fftLABSeries);
|
|
spectrumAutoscale();
|
|
m_downloadingLAB = false;
|
|
}
|
|
else
|
|
{
|
|
// Try ploting for current FFT (as we only allow one download at a time, so may have been skipped)
|
|
m_downloadingLAB = false;
|
|
plotLAB(fft->m_l, fft->m_b, m_beamWidth);
|
|
// Don't clear m_downloadingLAB after this point
|
|
}
|
|
}
|
|
} else {
|
|
qDebug() << "RadioAstronomyGUI::downloadFinished: Failed to open downloaded file: " << filename;
|
|
m_downloadingLAB = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
qDebug() << "RadioAstronomyGUI::downloadFinished: Failed to download: " << filename;
|
|
m_downloadingLAB = false;
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::displayRunModeSettings()
|
|
{
|
|
bool sweep = m_settings.m_runMode == RadioAstronomySettings::SWEEP;
|
|
ui->sweep1CoordLabel->setVisible(sweep);
|
|
ui->sweepType->setVisible(sweep);
|
|
ui->sweep1StartLabel->setVisible(sweep);
|
|
ui->sweep1Start->setVisible(sweep);
|
|
ui->sweep1StopLabel->setVisible(sweep);
|
|
ui->sweep1Stop->setVisible(sweep);
|
|
ui->sweep1StepLabel->setVisible(sweep);
|
|
ui->sweep1Step->setVisible(sweep);
|
|
ui->sweep1DelayLabel->setVisible(sweep);
|
|
ui->sweep1Delay->setVisible(sweep);
|
|
ui->sweep2CoordLabel->setVisible(sweep);
|
|
ui->sweep2StartLabel->setVisible(sweep);
|
|
ui->sweep2Start->setVisible(sweep);
|
|
ui->sweep2StopLabel->setVisible(sweep);
|
|
ui->sweep2Stop->setVisible(sweep);
|
|
ui->sweep2StepLabel->setVisible(sweep);
|
|
ui->sweep2Step->setVisible(sweep);
|
|
ui->sweep2DelayLabel->setVisible(sweep);
|
|
ui->sweep2Delay->setVisible(sweep);
|
|
ui->sweepStatus->setVisible(sweep);
|
|
ui->runLayout->activate(); // Needed otherwise height of rollup doesn't seem to be reduced
|
|
ui->statusLayout->activate(); // going from sweep to single/continuous
|
|
getRollupContents()->arrangeRollups();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_runMode_currentIndexChanged(int index)
|
|
{
|
|
m_settings.m_runMode = (RadioAstronomySettings::RunMode)index;
|
|
applySettings();
|
|
displayRunModeSettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_sweepType_currentIndexChanged(int index)
|
|
{
|
|
m_settings.m_sweepType = (RadioAstronomySettings::SweepType)index;
|
|
if ((index == 0) || (index == 2))
|
|
{
|
|
ui->sweep1CoordLabel->setText("Az");
|
|
ui->sweep2CoordLabel->setText("El");
|
|
}
|
|
else if (index == 1)
|
|
{
|
|
ui->sweep1CoordLabel->setText("l");
|
|
ui->sweep2CoordLabel->setText("b");
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_sweep1Start_valueChanged(double value)
|
|
{
|
|
m_settings.m_sweep1Start = value;
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_sweep1Stop_valueChanged(double value)
|
|
{
|
|
m_settings.m_sweep1Stop = value;
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_sweep1Step_valueChanged(double value)
|
|
{
|
|
m_settings.m_sweep1Step = value;
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_sweep1Delay_valueChanged(double value)
|
|
{
|
|
m_settings.m_sweep1Delay = value;
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_sweep2Start_valueChanged(double value)
|
|
{
|
|
m_settings.m_sweep2Start = value;
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_sweep2Stop_valueChanged(double value)
|
|
{
|
|
m_settings.m_sweep2Stop = value;
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_sweep2Step_valueChanged(double value)
|
|
{
|
|
m_settings.m_sweep2Step = value;
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_sweep2Delay_valueChanged(double value)
|
|
{
|
|
m_settings.m_sweep2Delay = value;
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_sweepStartAtTime_currentIndexChanged(int index)
|
|
{
|
|
m_settings.m_sweepStartAtTime = ui->sweepStartAtTime->currentIndex() == 1;
|
|
ui->sweepStartDateTime->setVisible(index == 1);
|
|
getRollupContents()->arrangeRollups();
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_sweepStartDateTime_dateTimeChanged(const QDateTime& dateTime)
|
|
{
|
|
m_settings.m_sweepStartDateTime = dateTime;
|
|
applySettings();
|
|
}
|
|
|
|
void RadioAstronomyGUI::on_startStop_clicked(bool checked)
|
|
{
|
|
if (checked)
|
|
{
|
|
ui->startStop->setStyleSheet("QToolButton { background-color : green; }");
|
|
applySettings();
|
|
if (m_settings.m_power2DLinkSweep)
|
|
{
|
|
update2DSettingsFromSweep();
|
|
create2DImage();
|
|
}
|
|
m_radioAstronomy->getInputMessageQueue()->push(RadioAstronomy::MsgStartSweep::create());
|
|
}
|
|
else
|
|
{
|
|
m_radioAstronomy->getInputMessageQueue()->push(RadioAstronomy::MsgStopSweep::create());
|
|
if (m_settings.m_runMode != RadioAstronomySettings::SWEEP) {
|
|
ui->startStop->setStyleSheet("QToolButton { background-color : blue; }");
|
|
}
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::calcPowerChartTickCount(int width)
|
|
{
|
|
// These values should probably be dependent on the font used
|
|
if (m_powerXAxis) {
|
|
if (m_powerXAxisSameDay) {
|
|
m_powerXAxis->setTickCount(width > 700 ? 10 : 5);
|
|
} else {
|
|
m_powerXAxis->setTickCount(width > 1200 ? 10 : 5);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::calcSpectrumChartTickCount(QValueAxis *axis, int width)
|
|
{
|
|
if (axis) {
|
|
axis->setTickCount(width > 700 ? 10 : 5);
|
|
}
|
|
}
|
|
|
|
void RadioAstronomyGUI::resizeEvent(QResizeEvent* size)
|
|
{
|
|
int width = size->size().width();
|
|
calcPowerChartTickCount(width);
|
|
calcSpectrumChartTickCount(m_fftXAxis, width);
|
|
calcSpectrumChartTickCount(m_fftDopplerAxis, width);
|
|
calcSpectrumChartTickCount(m_calXAxis, width);
|
|
ChannelGUI::resizeEvent(size);
|
|
}
|
|
|
|
void RadioAstronomyGUI::makeUIConnections()
|
|
{
|
|
QObject::connect(ui->deltaFrequency, &ValueDialZ::changed, this, &RadioAstronomyGUI::on_deltaFrequency_changed);
|
|
QObject::connect(ui->sampleRate, &ValueDialZ::changed, this, &RadioAstronomyGUI::on_sampleRate_changed);
|
|
QObject::connect(ui->rfBW, &ValueDialZ::changed, this, &RadioAstronomyGUI::on_rfBW_changed);
|
|
QObject::connect(ui->integration, &ValueDialZ::changed, this, &RadioAstronomyGUI::on_integration_changed);
|
|
QObject::connect(ui->fftSize, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RadioAstronomyGUI::on_fftSize_currentIndexChanged);
|
|
QObject::connect(ui->fftWindow, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RadioAstronomyGUI::on_fftWindow_currentIndexChanged);
|
|
QObject::connect(ui->filterFreqs, &QLineEdit::editingFinished, this, &RadioAstronomyGUI::on_filterFreqs_editingFinished);
|
|
QObject::connect(ui->starTracker, &QComboBox::currentTextChanged, this, &RadioAstronomyGUI::on_starTracker_currentTextChanged);
|
|
QObject::connect(ui->rotator, &QComboBox::currentTextChanged, this, &RadioAstronomyGUI::on_rotator_currentTextChanged);
|
|
QObject::connect(ui->showSensors, &QToolButton::clicked, this, &RadioAstronomyGUI::on_showSensors_clicked);
|
|
QObject::connect(ui->tempRXSelect, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RadioAstronomyGUI::on_tempRXSelect_currentIndexChanged);
|
|
QObject::connect(ui->tempRX, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_tempRX_valueChanged);
|
|
QObject::connect(ui->tempCMB, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_tempCMB_valueChanged);
|
|
QObject::connect(ui->tempGal, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_tempGal_valueChanged);
|
|
QObject::connect(ui->tempSP, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_tempSP_valueChanged);
|
|
QObject::connect(ui->tempAtm, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_tempAtm_valueChanged);
|
|
QObject::connect(ui->tempAir, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_tempAir_valueChanged);
|
|
QObject::connect(ui->zenithOpacity, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_zenithOpacity_valueChanged);
|
|
QObject::connect(ui->elevation, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_elevation_valueChanged);
|
|
QObject::connect(ui->tempAtmLink, &ButtonSwitch::toggled, this, &RadioAstronomyGUI::on_tempAtmLink_toggled);
|
|
QObject::connect(ui->tempAirLink, &ButtonSwitch::toggled, this, &RadioAstronomyGUI::on_tempAirLink_toggled);
|
|
QObject::connect(ui->tempGalLink, &ButtonSwitch::toggled, this, &RadioAstronomyGUI::on_tempGalLink_toggled);
|
|
QObject::connect(ui->elevationLink, &ButtonSwitch::toggled, this, &RadioAstronomyGUI::on_elevationLink_toggled);
|
|
QObject::connect(ui->gainVariation, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_gainVariation_valueChanged);
|
|
QObject::connect(ui->omegaAUnits, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RadioAstronomyGUI::on_omegaAUnits_currentIndexChanged);
|
|
QObject::connect(ui->sourceType, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RadioAstronomyGUI::on_sourceType_currentIndexChanged);
|
|
QObject::connect(ui->omegaS, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_omegaS_valueChanged);
|
|
QObject::connect(ui->omegaSUnits, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RadioAstronomyGUI::on_omegaSUnits_currentIndexChanged);
|
|
QObject::connect(ui->spectrumChartSelect, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RadioAstronomyGUI::on_spectrumChartSelect_currentIndexChanged);
|
|
QObject::connect(ui->showCalSettings, &QToolButton::clicked, this, &RadioAstronomyGUI::on_showCalSettings_clicked);
|
|
QObject::connect(ui->startCalHot, &QToolButton::clicked, this, &RadioAstronomyGUI::on_startCalHot_clicked);
|
|
QObject::connect(ui->startCalCold, &QToolButton::clicked, this, &RadioAstronomyGUI::on_startCalCold_clicked);
|
|
QObject::connect(ui->recalibrate, &ButtonSwitch::toggled, this, &RadioAstronomyGUI::on_recalibrate_toggled);
|
|
QObject::connect(ui->spectrumShowLegend, &ButtonSwitch::toggled, this, &RadioAstronomyGUI::on_spectrumShowLegend_toggled);
|
|
QObject::connect(ui->spectrumShowRefLine, &ButtonSwitch::toggled, this, &RadioAstronomyGUI::on_spectrumShowRefLine_toggled);
|
|
QObject::connect(ui->spectrumTemp, &ButtonSwitch::toggled, this, &RadioAstronomyGUI::on_spectrumTemp_toggled);
|
|
QObject::connect(ui->spectrumMarker, &ButtonSwitch::toggled, this, &RadioAstronomyGUI::on_spectrumMarker_toggled);
|
|
QObject::connect(ui->spectrumPeak, &ButtonSwitch::toggled, this, &RadioAstronomyGUI::on_spectrumPeak_toggled);
|
|
QObject::connect(ui->spectrumReverseXAxis, &ButtonSwitch::toggled, this, &RadioAstronomyGUI::on_spectrumReverseXAxis_toggled);
|
|
QObject::connect(ui->savePowerData, &QToolButton::clicked, this, &RadioAstronomyGUI::on_savePowerData_clicked);
|
|
QObject::connect(ui->savePowerChartImage, &QToolButton::clicked, this, &RadioAstronomyGUI::on_savePowerChartImage_clicked);
|
|
QObject::connect(ui->saveSpectrumData, &QToolButton::clicked, this, &RadioAstronomyGUI::on_saveSpectrumData_clicked);
|
|
QObject::connect(ui->loadSpectrumData, &QToolButton::clicked, this, &RadioAstronomyGUI::on_loadSpectrumData_clicked);
|
|
QObject::connect(ui->saveSpectrumChartImage, &QToolButton::clicked, this, &RadioAstronomyGUI::on_saveSpectrumChartImage_clicked);
|
|
QObject::connect(ui->saveSpectrumChartImages, &QToolButton::clicked, this, &RadioAstronomyGUI::on_saveSpectrumChartImages_clicked);
|
|
QObject::connect(ui->clearData, &QToolButton::clicked, this, &RadioAstronomyGUI::on_clearData_clicked);
|
|
QObject::connect(ui->clearCal, &QToolButton::clicked, this, &RadioAstronomyGUI::on_clearCal_clicked);
|
|
QObject::connect(ui->spectrumAutoscale, &QToolButton::toggled, this, &RadioAstronomyGUI::on_spectrumAutoscale_toggled);
|
|
QObject::connect(ui->spectrumAutoscaleX, &QToolButton::clicked, this, &RadioAstronomyGUI::on_spectrumAutoscaleX_clicked);
|
|
QObject::connect(ui->spectrumAutoscaleY, &QToolButton::clicked, this, &RadioAstronomyGUI::on_spectrumAutoscaleY_clicked);
|
|
QObject::connect(ui->spectrumReference, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_spectrumReference_valueChanged);
|
|
QObject::connect(ui->spectrumRange, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_spectrumRange_valueChanged);
|
|
QObject::connect(ui->spectrumCenterFreq, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_spectrumCenterFreq_valueChanged);
|
|
QObject::connect(ui->spectrumSpan, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_spectrumSpan_valueChanged);
|
|
QObject::connect(ui->spectrumYUnits, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RadioAstronomyGUI::on_spectrumYUnits_currentIndexChanged);
|
|
QObject::connect(ui->spectrumBaseline, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RadioAstronomyGUI::on_spectrumBaseline_currentIndexChanged);
|
|
QObject::connect(ui->spectrumIndex, &QSlider::valueChanged, this, &RadioAstronomyGUI::on_spectrumIndex_valueChanged);
|
|
QObject::connect(ui->spectrumLine, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RadioAstronomyGUI::on_spectrumLine_currentIndexChanged);
|
|
QObject::connect(ui->spectrumLineFrequency, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_spectrumLineFrequency_valueChanged);
|
|
QObject::connect(ui->refFrame, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RadioAstronomyGUI::on_refFrame_currentIndexChanged);
|
|
QObject::connect(ui->sunDistanceToGC, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_sunDistanceToGC_valueChanged);
|
|
QObject::connect(ui->sunOrbitalVelocity, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_sunOrbitalVelocity_valueChanged);
|
|
QObject::connect(ui->spectrumGaussianFreq, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_spectrumGaussianFreq_valueChanged);
|
|
QObject::connect(ui->spectrumGaussianAmp, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_spectrumGaussianAmp_valueChanged);
|
|
QObject::connect(ui->spectrumGaussianFloor, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_spectrumGaussianFloor_valueChanged);
|
|
QObject::connect(ui->spectrumGaussianFWHM, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_spectrumGaussianFWHM_valueChanged);
|
|
QObject::connect(ui->spectrumGaussianTurb, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_spectrumGaussianTurb_valueChanged);
|
|
QObject::connect(ui->spectrumTemperature, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_spectrumTemperature_valueChanged);
|
|
QObject::connect(ui->spectrumShowLAB, &ButtonSwitch::toggled, this, &RadioAstronomyGUI::on_spectrumShowLAB_toggled);
|
|
QObject::connect(ui->spectrumShowDistance, &ButtonSwitch::toggled, this, &RadioAstronomyGUI::on_spectrumShowDistance_toggled);
|
|
QObject::connect(ui->tCalHotSelect, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RadioAstronomyGUI::on_tCalHotSelect_currentIndexChanged);
|
|
QObject::connect(ui->tCalHot, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_tCalHot_valueChanged);
|
|
QObject::connect(ui->tCalColdSelect, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RadioAstronomyGUI::on_tCalColdSelect_currentIndexChanged);
|
|
QObject::connect(ui->tCalCold, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_tCalCold_valueChanged);
|
|
QObject::connect(ui->powerChartSelect, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RadioAstronomyGUI::on_powerChartSelect_currentIndexChanged);
|
|
QObject::connect(ui->powerYUnits, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RadioAstronomyGUI::on_powerYUnits_currentIndexChanged);
|
|
QObject::connect(ui->powerShowMarker, &ButtonSwitch::toggled, this, &RadioAstronomyGUI::on_powerShowMarker_toggled);
|
|
QObject::connect(ui->powerShowAirTemp, &ButtonSwitch::toggled, this, &RadioAstronomyGUI::on_powerShowAirTemp_toggled);
|
|
QObject::connect(ui->powerShowPeak, &ButtonSwitch::toggled, this, &RadioAstronomyGUI::on_powerShowPeak_toggled);
|
|
QObject::connect(ui->powerShowAvg, &ButtonSwitch::toggled, this, &RadioAstronomyGUI::on_powerShowAvg_toggled);
|
|
QObject::connect(ui->powerShowLegend, &ButtonSwitch::toggled, this, &RadioAstronomyGUI::on_powerShowLegend_toggled);
|
|
QObject::connect(ui->powerAutoscale, &QToolButton::toggled, this, &RadioAstronomyGUI::on_powerAutoscale_toggled);
|
|
QObject::connect(ui->powerAutoscaleY, &QToolButton::clicked, this, &RadioAstronomyGUI::on_powerAutoscaleY_clicked);
|
|
QObject::connect(ui->powerAutoscaleX, &QToolButton::clicked, this, &RadioAstronomyGUI::on_powerAutoscaleX_clicked);
|
|
QObject::connect(ui->powerReference, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_powerReference_valueChanged);
|
|
QObject::connect(ui->powerRange, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_powerRange_valueChanged);
|
|
QObject::connect(ui->powerStartTime, &WrappingDateTimeEdit::dateTimeChanged, this, &RadioAstronomyGUI::on_powerStartTime_dateTimeChanged);
|
|
QObject::connect(ui->powerEndTime, &WrappingDateTimeEdit::dateTimeChanged, this, &RadioAstronomyGUI::on_powerEndTime_dateTimeChanged);
|
|
QObject::connect(ui->powerShowGaussian, &ButtonSwitch::clicked, this, &RadioAstronomyGUI::on_powerShowGaussian_clicked);
|
|
QObject::connect(ui->powerGaussianCenter, &WrappingDateTimeEdit::dateTimeChanged, this, &RadioAstronomyGUI::on_powerGaussianCenter_dateTimeChanged);
|
|
QObject::connect(ui->powerGaussianAmp, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_powerGaussianAmp_valueChanged);
|
|
QObject::connect(ui->powerGaussianFloor, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_powerGaussianFloor_valueChanged);
|
|
QObject::connect(ui->powerGaussianFWHM, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_powerGaussianFWHM_valueChanged);
|
|
QObject::connect(ui->powerGaussianHPBW, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_powerGaussianHPBW_valueChanged);
|
|
QObject::connect(ui->runMode, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RadioAstronomyGUI::on_runMode_currentIndexChanged);
|
|
QObject::connect(ui->sweepType, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RadioAstronomyGUI::on_sweepType_currentIndexChanged);
|
|
QObject::connect(ui->startStop, &ButtonSwitch::clicked, this, &RadioAstronomyGUI::on_startStop_clicked);
|
|
QObject::connect(ui->sweepStartAtTime, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RadioAstronomyGUI::on_sweepStartAtTime_currentIndexChanged);
|
|
QObject::connect(ui->sweepStartDateTime, &QDateTimeEdit::dateTimeChanged, this, &RadioAstronomyGUI::on_sweepStartDateTime_dateTimeChanged);
|
|
QObject::connect(ui->powerColourAutoscale, &QToolButton::toggled, this, &RadioAstronomyGUI::on_powerColourAutoscale_toggled);
|
|
QObject::connect(ui->powerColourScaleMin, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_powerColourScaleMin_valueChanged);
|
|
QObject::connect(ui->powerColourScaleMax, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &RadioAstronomyGUI::on_powerColourScaleMax_valueChanged);
|
|
QObject::connect(ui->powerColourPalette, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RadioAstronomyGUI::on_powerColourPalette_currentIndexChanged);
|
|
QObject::connect(ui->powerTable, &QTableWidget::cellDoubleClicked, this, &RadioAstronomyGUI::on_powerTable_cellDoubleClicked);
|
|
}
|
|
|
|
void RadioAstronomyGUI::updateAbsoluteCenterFrequency()
|
|
{
|
|
setStatusFrequency(m_deviceCenterFrequency + m_settings.m_inputFrequencyOffset);
|
|
}
|