APT Demod updates.

Add projection of image on to 3D map.
Add support for temperature map.
Add support for colour palettes for image enhancements.
Fix IR channel names.
pull/1127/head
Jon Beniston 2022-02-04 16:36:02 +00:00
rodzic 26b8619bb1
commit 7b6708a256
19 zmienionych plików z 1974 dodań i 155 usunięć

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 961 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 944 KiB

Wyświetl plik

@ -23,6 +23,7 @@ set(demodapt_HEADERS
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
${APT_INCLUDE_DIR}
${SGP4_INCLUDE_DIR}
)
if(NOT SERVER_MODE)
@ -32,12 +33,15 @@ if(NOT SERVER_MODE)
aptdemodgui.ui
aptdemodsettingsdialog.cpp
aptdemodsettingsdialog.ui
aptdemodselectdialog.cpp
aptdemodselectdialog.ui
icons.qrc
)
set(demodapt_HEADERS
${demodapt_HEADERS}
aptdemodgui.h
aptdemodsettingsdialog.h
aptdemodselectdialog.h
)
set(TARGET_NAME demodapt)
@ -59,12 +63,22 @@ if(APT_EXTERNAL)
add_dependencies(${TARGET_NAME} apt)
endif()
if(SGP4_EXTERNAL)
add_dependencies(${TARGET_NAME} sgp4)
endif()
target_link_libraries(${TARGET_NAME}
Qt5::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
${APT_LIBRARIES}
${SGP4_LIBRARIES}
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
# Install debug symbols
if (WIN32)
install(FILES $<TARGET_PDB_FILE:${TARGET_NAME}> CONFIGURATIONS Debug RelWithDebInfo DESTINATION ${INSTALL_FOLDER} )
endif()

Wyświetl plik

@ -47,6 +47,7 @@ MESSAGE_CLASS_DEFINITION(APTDemod::MsgConfigureAPTDemod, Message)
MESSAGE_CLASS_DEFINITION(APTDemod::MsgPixels, Message)
MESSAGE_CLASS_DEFINITION(APTDemod::MsgImage, Message)
MESSAGE_CLASS_DEFINITION(APTDemod::MsgLine, Message)
MESSAGE_CLASS_DEFINITION(APTDemod::MsgMapImageName, Message)
MESSAGE_CLASS_DEFINITION(APTDemod::MsgResetDecoder, Message)
const char * const APTDemod::m_channelIdURI = "sdrangel.channel.aptdemod";
@ -62,7 +63,7 @@ APTDemod::APTDemod(DeviceAPI *deviceAPI) :
m_basebandSink = new APTDemodBaseband(this);
m_basebandSink->moveToThread(&m_thread);
m_imageWorker = new APTDemodImageWorker();
m_imageWorker = new APTDemodImageWorker(this);
m_basebandSink->setImagWorkerMessageQueue(m_imageWorker->getInputMessageQueue());
m_imageWorker->moveToThread(&m_imageThread);
@ -286,6 +287,42 @@ void APTDemod::applySettings(const APTDemodSettings& settings, bool force)
if ((settings.m_autoSaveMinScanLines != m_settings.m_autoSaveMinScanLines) || force) {
reverseAPIKeys.append("autoSaveMinScanLines");
}
if ((settings.m_saveCombined != m_settings.m_saveCombined) || force) {
reverseAPIKeys.append("saveCombined");
}
if ((settings.m_saveSeparate != m_settings.m_saveSeparate) || force) {
reverseAPIKeys.append("saveSeparate");
}
if ((settings.m_saveProjection != m_settings.m_saveProjection) || force) {
reverseAPIKeys.append("saveProjection");
}
if ((settings.m_scanlinesPerImageUpdate != m_settings.m_scanlinesPerImageUpdate) || force) {
reverseAPIKeys.append("scanlinesPerImageUpdate");
}
if ((settings.m_transparencyThreshold != m_settings.m_transparencyThreshold) || force) {
reverseAPIKeys.append("transparencyThreshold");
}
if ((settings.m_opacityThreshold != m_settings.m_opacityThreshold) || force) {
reverseAPIKeys.append("opacityThreshold");
}
if ((settings.m_palettes != m_settings.m_palettes) || force) {
reverseAPIKeys.append("palettes");
}
if ((settings.m_palette != m_settings.m_palette) || force) {
reverseAPIKeys.append("palette");
}
if ((settings.m_horizontalPixelsPerDegree != m_settings.m_horizontalPixelsPerDegree) || force) {
reverseAPIKeys.append("horizontalPixelsPerDegree");
}
if ((settings.m_verticalPixelsPerDegree != m_settings.m_verticalPixelsPerDegree) || force) {
reverseAPIKeys.append("verticalPixelsPerDegree");
}
if ((settings.m_satTimeOffset != m_settings.m_satTimeOffset) || force) {
reverseAPIKeys.append("satTimeOffset");
}
if ((settings.m_satYaw != m_settings.m_satYaw) || force) {
reverseAPIKeys.append("satYaw");
}
if (m_settings.m_streamIndex != settings.m_streamIndex)
{
@ -426,6 +463,42 @@ void APTDemod::webapiUpdateChannelSettings(
if (channelSettingsKeys.contains("autoSaveMinScanLines")) {
settings.m_autoSaveMinScanLines = response.getAptDemodSettings()->getAutoSaveMinScanLines();
}
if (channelSettingsKeys.contains("saveCombined")) {
settings.m_saveCombined = response.getAptDemodSettings()->getSaveCombined();
}
if (channelSettingsKeys.contains("saveSeparate")) {
settings.m_saveSeparate = response.getAptDemodSettings()->getSaveSeparate();
}
if (channelSettingsKeys.contains("saveProjection")) {
settings.m_saveProjection = response.getAptDemodSettings()->getSaveProjection();
}
if (channelSettingsKeys.contains("scanlinesPerImageUpdate")) {
settings.m_scanlinesPerImageUpdate = response.getAptDemodSettings()->getScanlinesPerImageUpdate();
}
if (channelSettingsKeys.contains("transparencyThreshold")) {
settings.m_transparencyThreshold = response.getAptDemodSettings()->getTransparencyThreshold();
}
if (channelSettingsKeys.contains("m_opacityThreshold")) {
settings.m_opacityThreshold = response.getAptDemodSettings()->getOpacityThreshold();
}
if (channelSettingsKeys.contains("palettes")) {
settings.m_palettes = (*response.getAptDemodSettings()->getPalettes()).split(";");
}
if (channelSettingsKeys.contains("palette")) {
settings.m_palette = response.getAptDemodSettings()->getPalette();
}
if (channelSettingsKeys.contains("horizontalPixelsPerDegree")) {
settings.m_horizontalPixelsPerDegree = response.getAptDemodSettings()->getHorizontalPixelsPerDegree();
}
if (channelSettingsKeys.contains("verticalPixelsPerDegree")) {
settings.m_verticalPixelsPerDegree = response.getAptDemodSettings()->getVerticalPixelsPerDegree();
}
if (channelSettingsKeys.contains("satTimeOffset")) {
settings.m_satTimeOffset = response.getAptDemodSettings()->getSatTimeOffset();
}
if (channelSettingsKeys.contains("satYaw")) {
settings.m_satYaw = response.getAptDemodSettings()->getSatYaw();
}
if (channelSettingsKeys.contains("rgbColor")) {
settings.m_rgbColor = response.getAptDemodSettings()->getRgbColor();
}
@ -474,6 +547,18 @@ void APTDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& resp
response.getAptDemodSettings()->setAutoSave(settings.m_autoSave);
response.getAptDemodSettings()->setAutoSavePath(new QString(settings.m_autoSavePath));
response.getAptDemodSettings()->setAutoSaveMinScanLines(settings.m_autoSaveMinScanLines);
response.getAptDemodSettings()->setSaveCombined(settings.m_saveCombined);
response.getAptDemodSettings()->setSaveSeparate(settings.m_saveSeparate);
response.getAptDemodSettings()->setSaveProjection(settings.m_saveProjection);
response.getAptDemodSettings()->setScanlinesPerImageUpdate(settings.m_scanlinesPerImageUpdate);
response.getAptDemodSettings()->setTransparencyThreshold(settings.m_transparencyThreshold);
response.getAptDemodSettings()->setOpacityThreshold(settings.m_opacityThreshold);
response.getAptDemodSettings()->setPalettes(new QString(settings.m_palettes.join(";")));
response.getAptDemodSettings()->setPalette(settings.m_palette);
response.getAptDemodSettings()->setHorizontalPixelsPerDegree(settings.m_horizontalPixelsPerDegree);
response.getAptDemodSettings()->setVerticalPixelsPerDegree(settings.m_verticalPixelsPerDegree);
response.getAptDemodSettings()->setSatTimeOffset(settings.m_satTimeOffset);
response.getAptDemodSettings()->setSatYaw(settings.m_satYaw);
response.getAptDemodSettings()->setRgbColor(settings.m_rgbColor);
@ -599,15 +684,51 @@ void APTDemod::webapiFormatChannelSettings(
if (channelSettingsKeys.contains("decodeEnabled") || force) {
swgAPTDemodSettings->setDecodeEnabled(settings.m_decodeEnabled);
}
if (channelSettingsKeys.contains("m_autoSave") || force) {
if (channelSettingsKeys.contains("autoSave") || force) {
swgAPTDemodSettings->setAutoSave(settings.m_autoSave);
}
if (channelSettingsKeys.contains("m_autoSavePath") || force) {
if (channelSettingsKeys.contains("autoSavePath") || force) {
swgAPTDemodSettings->setAutoSavePath(new QString(settings.m_autoSavePath));
}
if (channelSettingsKeys.contains("m_autoSaveMinScanLines") || force) {
if (channelSettingsKeys.contains("autoSaveMinScanLines") || force) {
swgAPTDemodSettings->setAutoSaveMinScanLines(settings.m_autoSaveMinScanLines);
}
if (channelSettingsKeys.contains("saveCombined") || force) {
swgAPTDemodSettings->setSaveCombined(settings.m_saveCombined);
}
if (channelSettingsKeys.contains("saveSeparate") || force) {
swgAPTDemodSettings->setSaveSeparate(settings.m_saveSeparate);
}
if (channelSettingsKeys.contains("saveProjection") || force) {
swgAPTDemodSettings->setSaveProjection(settings.m_saveProjection);
}
if (channelSettingsKeys.contains("scanlinesPerImageUpdate") || force) {
swgAPTDemodSettings->setScanlinesPerImageUpdate(settings.m_scanlinesPerImageUpdate);
}
if (channelSettingsKeys.contains("transparencyThreshold") || force) {
swgAPTDemodSettings->setTransparencyThreshold(settings.m_transparencyThreshold);
}
if (channelSettingsKeys.contains("opacityThreshold") || force) {
swgAPTDemodSettings->setOpacityThreshold(settings.m_opacityThreshold);
}
if (channelSettingsKeys.contains("palettes") || force) {
swgAPTDemodSettings->setPalettes(new QString(settings.m_palettes.join(";")));
}
if (channelSettingsKeys.contains("palette") || force) {
swgAPTDemodSettings->setPalette(settings.m_palette);
}
if (channelSettingsKeys.contains("horizontalPixelsPerDegree") || force) {
swgAPTDemodSettings->setHorizontalPixelsPerDegree(settings.m_horizontalPixelsPerDegree);
}
if (channelSettingsKeys.contains("verticalPixelsPerDegree") || force) {
swgAPTDemodSettings->setVerticalPixelsPerDegree(settings.m_verticalPixelsPerDegree);
}
if (channelSettingsKeys.contains("satTimeOffset") || force) {
swgAPTDemodSettings->setSatTimeOffset(settings.m_satTimeOffset);
}
if (channelSettingsKeys.contains("satYaw") || force) {
swgAPTDemodSettings->setSatYaw(settings.m_satYaw);
}
if (channelSettingsKeys.contains("rgbColor") || force) {
swgAPTDemodSettings->setRgbColor(settings.m_rgbColor);
}
@ -654,6 +775,9 @@ int APTDemod::webapiActionsPost(
// Reset for new pass
m_imageWorker->getInputMessageQueue()->push(APTDemod::MsgResetDecoder::create());
m_basebandSink->getInputMessageQueue()->push(APTDemod::MsgResetDecoder::create());
if (m_guiMessageQueue) {
m_guiMessageQueue->push(APTDemod::MsgResetDecoder::create());
}
// Save satellite name
m_imageWorker->getInputMessageQueue()->push(APTDemodImageWorker::MsgSetSatelliteName::create(*satelliteName));
@ -662,10 +786,14 @@ int APTDemod::webapiActionsPost(
APTDemodSettings settings = m_settings;
settings.m_decodeEnabled = true;
settings.m_flip = !aos->getNorthToSouthPass();
settings.m_tle = *aos->getTle();
settings.m_aosDateTime = QDateTime::fromString(*aos->getDateTime(), Qt::ISODateWithMs);
settings.m_northToSouth = aos->getNorthToSouthPass();
m_inputMessageQueue.push(MsgConfigureAPTDemod::create(settings, false));
if (m_guiMessageQueue)
if (m_guiMessageQueue) {
m_guiMessageQueue->push(MsgConfigureAPTDemod::create(settings, false));
}
}
return 202;

Wyświetl plik

@ -146,6 +146,28 @@ public:
{}
};
// Sent from worker to GUI to indicate name of image on Map
class MsgMapImageName : public Message {
MESSAGE_CLASS_DECLARATION
public:
QString getName() const { return m_name; }
static MsgMapImageName* create(const QString &name)
{
return new MsgMapImageName(name);
}
private:
QString m_name;
MsgMapImageName(const QString &name) :
Message(),
m_name(name)
{
}
};
// Sent from GUI to reset decoder
class MsgResetDecoder : public Message {
MESSAGE_CLASS_DECLARATION

Wyświetl plik

@ -25,8 +25,10 @@
#include <QAction>
#include <QRegExp>
#include <QFileDialog>
#include <QFileInfo>
#include <QGraphicsScene>
#include <QGraphicsPixmapItem>
#include <QGraphicsSceneMouseEvent>
#include "aptdemodgui.h"
@ -50,6 +52,45 @@
#include "aptdemod.h"
#include "aptdemodsink.h"
#include "aptdemodsettingsdialog.h"
#include "aptdemodselectdialog.h"
#include "SWGMapItem.h"
TempScale::TempScale(QGraphicsItem *parent) :
QGraphicsRectItem(parent)
{
// Temp scale appears to be -100 to +60C
// We just draw -100 to +50C, so it's nicely divides up according to the palette
setRect(30, 30, 25, 240);
m_gradient.setCoordinateMode(QGradient::ObjectBoundingMode);
m_gradient.setStart(0.0, 0.0);
m_gradient.setFinalStop(0.0, 1.0);
for (int i = 0; i < 240; i++)
{
int idx = (240 - i) * 3;
QColor color((unsigned char)apt_TempPalette[idx], (unsigned char)apt_TempPalette[idx+1], (unsigned char)apt_TempPalette[idx+2]);
m_gradient.setColorAt(i/240.0, color);
}
}
void TempScale::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(option);
Q_UNUSED(widget);
int left = rect().left() + rect().width() + 10;
painter->setPen(QPen(Qt::black));
painter->setBrush(m_gradient);
painter->drawRect(rect());
painter->drawText(left, rect().top(), "50C");
painter->drawText(left, rect().top() + rect().height() * 1 / 6, "25C");
painter->drawText(left, rect().top() + rect().height() * 2 / 6, "0C");
painter->drawText(left, rect().top() + rect().height() * 3 / 6, "-25C");
painter->drawText(left, rect().top() + rect().height() * 4 / 6, "-50C");
painter->drawText(left, rect().top() + rect().height() * 5 / 6, "-75C");
painter->drawText(left, rect().top() + rect().height(), "-100C");
}
APTDemodGUI* APTDemodGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel)
{
@ -76,7 +117,8 @@ QByteArray APTDemodGUI::serialize() const
bool APTDemodGUI::deserialize(const QByteArray& data)
{
if(m_settings.deserialize(data)) {
if(m_settings.deserialize(data))
{
displaySettings();
applySettings(true);
return true;
@ -103,19 +145,29 @@ bool APTDemodGUI::handleMessage(const Message& message)
{
const APTDemod::MsgImage& imageMsg = (APTDemod::MsgImage&) message;
m_image = imageMsg.getImage();
m_pixmap.convertFromImage(m_image);
if (m_pixmapItem != nullptr)
// Display can be corrupted if we try to drawn an image with 0 height
if (m_image.height() > 0)
{
m_pixmapItem->setPixmap(m_pixmap);
if (ui->zoomAll->isChecked()) {
m_pixmap.convertFromImage(m_image);
if (m_pixmapItem != nullptr)
{
m_pixmapItem->setPixmap(m_pixmap);
if (ui->zoomAll->isChecked()) {
ui->image->fitInView(m_pixmapItem, Qt::KeepAspectRatio);
}
}
else
{
m_pixmapItem = m_scene->addPixmap(m_pixmap);
m_pixmapItem->setPos(0, 0);
ui->image->fitInView(m_pixmapItem, Qt::KeepAspectRatio);
}
}
else
{
m_pixmapItem = m_scene->addPixmap(m_pixmap);
m_pixmapItem->setPos(0, 0);
ui->image->fitInView(m_pixmapItem, Qt::KeepAspectRatio);
bool temp = m_settings.m_channels == APTDemodSettings::TEMPERATURE;
m_tempScale->setVisible(temp);
m_tempScaleBG->setVisible(temp);
if (!temp) {
m_tempText->setVisible(false);
}
}
QStringList imageTypes = imageMsg.getImageTypes();
@ -126,20 +178,24 @@ bool APTDemodGUI::handleMessage(const Message& message)
}
else
{
if (imageTypes[0].isEmpty())
if (imageTypes[0].isEmpty()) {
ui->channelALabel->setText("Channel A");
else
} else {
ui->channelALabel->setText(imageTypes[0]);
if (imageTypes[1].isEmpty())
}
if (imageTypes[1].isEmpty()) {
ui->channelBLabel->setText("Channel B");
else
} else {
ui->channelBLabel->setText(imageTypes[1]);
}
}
QString satelliteName = imageMsg.getSatelliteName();
if (!satelliteName.isEmpty())
if (!satelliteName.isEmpty()) {
ui->imageContainer->setWindowTitle("Received image from " + satelliteName);
else
} else {
ui->imageContainer->setWindowTitle("Received image");
}
return true;
}
else if (APTDemod::MsgLine::match(message))
@ -203,6 +259,18 @@ bool APTDemodGUI::handleMessage(const Message& message)
return true;
}
else if (APTDemod::MsgMapImageName::match(message))
{
const APTDemod::MsgMapImageName& mapNameMsg = (APTDemod::MsgMapImageName&) message;
QString name = mapNameMsg.getName();
if (!m_mapImages.contains(name)) {
m_mapImages.append(name);
}
}
else if (APTDemod::MsgResetDecoder::match(message))
{
resetDecoder();
}
else if (DSPSignalNotification::match(message))
{
DSPSignalNotification& notif = (DSPSignalNotification&) message;
@ -261,24 +329,88 @@ void APTDemodGUI::on_fmDev_valueChanged(int value)
applySettings();
}
void APTDemodGUI::on_channels_currentIndexChanged(int index)
void APTDemodGUI::displayLabels()
{
m_settings.m_channels = (APTDemodSettings::ChannelSelection)index;
if (m_settings.m_channels == APTDemodSettings::BOTH_CHANNELS)
{
ui->channelALabel->setVisible(true);
ui->channelBLabel->setVisible(true);
ui->precipitation->setVisible(true);
}
else if (m_settings.m_channels == APTDemodSettings::CHANNEL_A)
{
ui->channelALabel->setVisible(true);
ui->channelBLabel->setVisible(false);
ui->precipitation->setVisible(true);
}
else if (m_settings.m_channels == APTDemodSettings::CHANNEL_B)
{
ui->channelALabel->setVisible(false);
ui->channelBLabel->setVisible(true);
ui->precipitation->setVisible(true);
}
else if (m_settings.m_channels == APTDemodSettings::TEMPERATURE)
{
ui->channelALabel->setVisible(false);
ui->channelBLabel->setVisible(false);
ui->precipitation->setVisible(false);
}
else
{
ui->channelALabel->setVisible(false);
ui->channelBLabel->setVisible(true);
ui->channelBLabel->setVisible(false);
ui->precipitation->setVisible(false);
}
}
void APTDemodGUI::on_channels_currentIndexChanged(int index)
{
if (index <= (int)APTDemodSettings::CHANNEL_B)
{
m_settings.m_channels = (APTDemodSettings::ChannelSelection)index;
}
else if (index == (int)APTDemodSettings::TEMPERATURE)
{
m_settings.m_channels = APTDemodSettings::TEMPERATURE;
m_settings.m_precipitationOverlay = false;
}
else
{
m_settings.m_channels = APTDemodSettings::PALETTE;
m_settings.m_palette = index - (int)APTDemodSettings::PALETTE;
m_settings.m_precipitationOverlay = false;
}
displayLabels();
applySettings();
}
void APTDemodGUI::on_transparencyThreshold_valueChanged(int value)
{
m_settings.m_transparencyThreshold = value;
ui->transparencyThresholdText->setText(QString::number(m_settings.m_transparencyThreshold));
// Don't applySettings while tracking, as processing an image takes a long time
if (!ui->transparencyThreshold->isSliderDown()) {
applySettings();
}
}
void APTDemodGUI::on_transparencyThreshold_sliderReleased()
{
applySettings();
}
void APTDemodGUI::on_opacityThreshold_valueChanged(int value)
{
m_settings.m_opacityThreshold = value;
ui->opacityThresholdText->setText(QString::number(m_settings.m_opacityThreshold));
// Don't applySettings while tracking, as processing an image takes a long time
if (!ui->opacityThreshold->isSliderDown()) {
applySettings();
}
}
void APTDemodGUI::on_opacityThreshold_sliderReleased()
{
applySettings();
}
@ -315,10 +447,11 @@ void APTDemodGUI::on_precipitation_clicked(bool checked)
void APTDemodGUI::on_flip_clicked(bool checked)
{
m_settings.m_flip = checked;
if (m_settings.m_flip)
if (m_settings.m_flip) {
ui->image->setAlignment(Qt::AlignBottom | Qt::AlignHCenter);
else
} else {
ui->image->setAlignment(Qt::AlignTop | Qt::AlignHCenter);
}
applySettings();
}
@ -328,13 +461,22 @@ void APTDemodGUI::on_startStop_clicked(bool checked)
applySettings();
}
void APTDemodGUI::on_resetDecoder_clicked()
void APTDemodGUI::resetDecoder()
{
if (m_pixmapItem != nullptr) {
if (m_pixmapItem != nullptr)
{
m_image = QImage();
m_pixmapItem->setPixmap(QPixmap());
}
ui->imageContainer->setWindowTitle("Received image");
// Send message to reset decoder
ui->channelALabel->setText("Channel A");
ui->channelBLabel->setText("Channel B");
}
void APTDemodGUI::on_resetDecoder_clicked()
{
resetDecoder();
// Send message to reset decoder to other parts of demod
m_aptDemod->getInputMessageQueue()->push(APTDemod::MsgResetDecoder::create());
}
@ -342,7 +484,10 @@ void APTDemodGUI::on_showSettings_clicked()
{
APTDemodSettingsDialog dialog(&m_settings);
if (dialog.exec() == QDialog::Accepted)
{
displayPalettes();
applySettings();
}
}
// Save image to disk
@ -356,8 +501,9 @@ void APTDemodGUI::on_saveImage_clicked()
if (fileNames.size() > 0)
{
qDebug() << "APT: Saving image to " << fileNames;
if (!m_image.save(fileNames[0]))
if (!m_image.save(fileNames[0])) {
QMessageBox::critical(this, "APT Demodulator", QString("Failed to save image to %1").arg(fileNames[0]));
}
}
}
}
@ -489,10 +635,28 @@ APTDemodGUI::APTDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban
m_zoom = new GraphicsViewZoom(ui->image); // Deleted automatically when view is deleted
connect(m_zoom, SIGNAL(zoomed()), this, SLOT(on_image_zoomed()));
// Create slightly transparent white background so labels can be seen
m_tempScale = new TempScale();
m_tempScale->setZValue(2.0);
m_tempScale->setVisible(false);
QRectF rect = m_tempScale->rect();
m_tempScaleBG = new QGraphicsRectItem(rect.left()-10, rect.top()-15, rect.width()+60, rect.height()+45);
m_tempScaleBG->setPen(QColor(200, 200, 200, 200));
m_tempScaleBG->setBrush(QColor(200, 200, 200, 200));
m_tempScaleBG->setZValue(1.0);
m_tempScaleBG->setVisible(false);
m_tempText = new QGraphicsSimpleTextItem("");
m_tempText->setZValue(3.0);
m_tempText->setVisible(false);
m_scene = new QGraphicsScene(ui->image);
m_scene->addItem(m_tempScale);
m_scene->addItem(m_tempScaleBG);
m_scene->addItem(m_tempText);
ui->image->setScene(m_scene);
ui->image->show();
m_scene->installEventFilter(this);
displaySettings();
applySettings(true);
}
@ -502,6 +666,55 @@ APTDemodGUI::~APTDemodGUI()
delete ui;
}
bool APTDemodGUI::eventFilter(QObject *obj, QEvent *event)
{
if ((obj == m_scene) && (m_settings.m_channels == APTDemodSettings::TEMPERATURE))
{
if (event->type() == QEvent::GraphicsSceneMouseMove)
{
QGraphicsSceneMouseEvent *mouseEvent = static_cast<QGraphicsSceneMouseEvent *>(event);
// Find temperature under cursor
int x = round(mouseEvent->scenePos().x());
int y = round(mouseEvent->scenePos().y());
if ((x >= 0) && (y >= 0) && (x < m_image.width()) && (y < m_image.height()))
{
// Map from colored temperature pixel back to greyscale level
// This is perhaps a bit slow - might be better to give GUI access to greyscale image as well
QRgb p = m_image.pixel(x, y);
int r = qRed(p);
int g = qGreen(p);
int b = qBlue(p);
int i;
for (i = 0; i < 256; i++)
{
if ( (r == (unsigned char)apt_TempPalette[i*3])
&& (g == (unsigned char)apt_TempPalette[i*3+1])
&& (b == (unsigned char)apt_TempPalette[i*3+2]))
{
// Map from palette index to degrees C
int temp = (i / 255.0) * 160.0 - 100.0;
m_tempText->setText(QString("%1C").arg(temp));
int width = m_tempText->boundingRect().width();
int height = m_tempText->boundingRect().height();
QRectF rect = m_tempScaleBG->rect();
m_tempText->setPos(rect.left()+rect.width()/2-width/2, rect.top()+rect.height()-height-5);
m_tempText->setVisible(true);
break;
}
}
if (i == 256) {
m_tempText->setVisible(false);
}
}
else
{
m_tempText->setVisible(false);
}
}
}
return ChannelGUI::eventFilter(obj, event);
}
void APTDemodGUI::blockApplySettings(bool block)
{
m_doApplySettings = !block;
@ -538,6 +751,11 @@ void APTDemodGUI::displaySettings()
ui->fmDevText->setText(QString("%1k").arg(m_settings.m_fmDeviation / 1000.0, 0, 'f', 1));
ui->fmDev->setValue(m_settings.m_fmDeviation / 100.0);
ui->transparencyThreshold->setValue(m_settings.m_transparencyThreshold);
ui->transparencyThresholdText->setText(QString::number(m_settings.m_transparencyThreshold));
ui->opacityThreshold->setValue(m_settings.m_opacityThreshold);
ui->opacityThresholdText->setText(QString::number(m_settings.m_opacityThreshold));
ui->startStop->setChecked(m_settings.m_decodeEnabled);
ui->cropNoise->setChecked(m_settings.m_cropNoise);
ui->denoise->setChecked(m_settings.m_denoise);
@ -552,14 +770,38 @@ void APTDemodGUI::displaySettings()
ui->image->setAlignment(Qt::AlignTop | Qt::AlignHCenter);
}
ui->channels->setCurrentIndex((int)m_settings.m_channels);
displayPalettes();
displayLabels();
displayStreamIndex();
restoreState(m_rollupState);
blockApplySettings(false);
}
void APTDemodGUI::displayPalettes()
{
ui->channels->blockSignals(true);
ui->channels->clear();
ui->channels->addItem("Both");
ui->channels->addItem("A");
ui->channels->addItem("B");
ui->channels->addItem("Temperature");
for (auto palette : m_settings.m_palettes)
{
QFileInfo fi(palette);
ui->channels->addItem(fi.baseName());
}
if (m_settings.m_channels == APTDemodSettings::PALETTE)
{
ui->channels->setCurrentIndex(((int)m_settings.m_channels) + m_settings.m_palette);
}
else
{
ui->channels->setCurrentIndex((int)m_settings.m_channels);
}
ui->channels->blockSignals(false);
}
void APTDemodGUI::displayStreamIndex()
{
if (m_deviceUISet->m_deviceMIMOEngine) {
@ -598,3 +840,47 @@ void APTDemodGUI::tick()
m_tickCount++;
}
void APTDemodGUI::on_deleteImageFromMap_clicked()
{
// If more than one image, pop up a dialog to select which to delete
if (m_mapImages.size() > 1)
{
APTDemodSelectDialog dialog(m_mapImages, this);
if (dialog.exec() == QDialog::Accepted)
{
for (auto name : dialog.getSelected())
{
deleteImageFromMap(name);
m_mapImages.removeAll(name);
}
}
}
else
{
for (auto name : m_mapImages) {
deleteImageFromMap(name);
}
m_mapImages.clear();
}
}
void APTDemodGUI::deleteImageFromMap(const QString &name)
{
MessagePipes& messagePipes = MainCore::instance()->getMessagePipes();
QList<MessageQueue*> *mapMessageQueues = messagePipes.getMessageQueues(m_aptDemod, "mapitems");
if (mapMessageQueues)
{
QList<MessageQueue*>::iterator it = mapMessageQueues->begin();
for (; it != mapMessageQueues->end(); ++it)
{
SWGSDRangel::SWGMapItem *swgMapItem = new SWGSDRangel::SWGMapItem();
swgMapItem->setName(new QString(name));
swgMapItem->setImage(new QString()); // Set image to "" to delete it
swgMapItem->setType(1);
MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_aptDemod, swgMapItem);
(*it)->push(msg);
}
}
}

Wyświetl plik

@ -30,6 +30,7 @@
#include <QMenu>
#include <QImage>
#include <QPixmap>
#include <QGraphicsRectItem>
#include "channel/channelgui.h"
#include "dsp/channelmarker.h"
@ -52,6 +53,16 @@ namespace Ui {
}
class APTDemodGUI;
// Temperature scale
class TempScale : public QObject, public QGraphicsRectItem {
Q_OBJECT
public:
TempScale(QGraphicsItem *parent = nullptr);
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
private:
QLinearGradient m_gradient;
};
class APTDemodGUI : public ChannelGUI {
Q_OBJECT
@ -63,6 +74,7 @@ public:
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
virtual bool eventFilter(QObject *watched, QEvent *event) override;
public slots:
void channelMarkerChangedByCursor();
@ -87,6 +99,11 @@ private:
QGraphicsScene* m_scene;
QGraphicsPixmapItem* m_pixmapItem;
GraphicsViewZoom* m_zoom;
TempScale *m_tempScale;
QGraphicsRectItem *m_tempScaleBG;
QGraphicsSimpleTextItem *m_tempText;
QList<QString> m_mapImages;
explicit APTDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0);
virtual ~APTDemodGUI();
@ -94,8 +111,12 @@ private:
void blockApplySettings(bool block);
void applySettings(bool force = false);
void displaySettings();
void displayPalettes();
void displayLabels();
void displayStreamIndex();
bool handleMessage(const Message& message);
void deleteImageFromMap(const QString &name);
void resetDecoder();
void leaveEvent(QEvent*);
void enterEvent(QEvent*);
@ -105,6 +126,11 @@ private slots:
void on_rfBW_valueChanged(int index);
void on_fmDev_valueChanged(int value);
void on_channels_currentIndexChanged(int index);
void on_transparencyThreshold_valueChanged(int value);
void on_transparencyThreshold_sliderReleased();
void on_opacityThreshold_valueChanged(int value);
void on_opacityThreshold_sliderReleased();
void on_deleteImageFromMap_clicked();
void on_cropNoise_clicked(bool checked=false);
void on_denoise_clicked(bool checked=false);
void on_linear_clicked(bool checked=false);

Wyświetl plik

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>451</width>
<width>440</width>
<height>569</height>
</rect>
</property>
@ -18,13 +18,12 @@
</property>
<property name="minimumSize">
<size>
<width>352</width>
<width>440</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
@ -105,7 +104,6 @@
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>12</pointsize>
</font>
</property>
@ -198,7 +196,6 @@
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>8</pointsize>
</font>
</property>
@ -275,26 +272,6 @@
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="fmDevLabel">
<property name="text">
@ -352,6 +329,132 @@
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="Line" name="line_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="transparencyThresholdLabel">
<property name="text">
<string>T&lt;sub&gt;TH&lt;/sub&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="transparencyThreshold">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Transparency threshold for image on map</string>
</property>
<property name="maximum">
<number>255</number>
</property>
<property name="pageStep">
<number>10</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="transparencyThresholdText">
<property name="minimumSize">
<size>
<width>22</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>255</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="opacityThresholdLabel">
<property name="text">
<string>O&lt;sub&gt;TH&lt;/sub&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="opacityThreshold">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Opacity threshold for image on map</string>
</property>
<property name="maximum">
<number>255</number>
</property>
<property name="pageStep">
<number>10</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="opacityThresholdText">
<property name="minimumSize">
<size>
<width>22</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>255</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="deleteImageFromMap">
<property name="toolTip">
<string>Delete images from map</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/bin.png</normaloff>:/bin.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item>
@ -729,7 +832,6 @@
<tabstops>
<tabstop>deltaFrequency</tabstop>
<tabstop>rfBW</tabstop>
<tabstop>fmDev</tabstop>
<tabstop>startStop</tabstop>
<tabstop>showSettings</tabstop>
<tabstop>resetDecoder</tabstop>

Wyświetl plik

@ -19,17 +19,25 @@
#include <algorithm>
#include <QTime>
#include <QBuffer>
#include <QDebug>
#include "maincore.h"
#include "util/units.h"
#include "aptdemod.h"
#include "aptdemodimageworker.h"
#include "SWGMapItem.h"
MESSAGE_CLASS_DEFINITION(APTDemodImageWorker::MsgConfigureAPTDemodImageWorker, Message)
MESSAGE_CLASS_DEFINITION(APTDemodImageWorker::MsgSaveImageToDisk, Message)
MESSAGE_CLASS_DEFINITION(APTDemodImageWorker::MsgSetSatelliteName, Message)
APTDemodImageWorker::APTDemodImageWorker() :
APTDemodImageWorker::APTDemodImageWorker(APTDemod *aptDemod) :
m_messageQueueToGUI(nullptr),
m_aptDemod(aptDemod),
m_sgp4(nullptr),
m_running(false),
m_mutex(QMutex::Recursive)
{
@ -51,6 +59,8 @@ APTDemodImageWorker::~APTDemodImageWorker()
delete[] m_image.prow[y];
delete[] m_tempImage.prow[y];
}
delete m_sgp4;
}
void APTDemodImageWorker::reset()
@ -129,6 +139,8 @@ bool APTDemodImageWorker::handleMessage(const Message& cmd)
void APTDemodImageWorker::applySettings(const APTDemodSettings& settings, bool force)
{
(void) force;
bool callRecalcCoords = false;
bool callProcessImage = false;
if ((settings.m_cropNoise != m_settings.m_cropNoise) ||
@ -137,14 +149,52 @@ void APTDemodImageWorker::applySettings(const APTDemodSettings& settings, bool f
(settings.m_histogramEqualise != m_settings.m_histogramEqualise) ||
(settings.m_precipitationOverlay != m_settings.m_precipitationOverlay) ||
(settings.m_flip != m_settings.m_flip) ||
(settings.m_channels != m_settings.m_channels))
(settings.m_channels != m_settings.m_channels) ||
(settings.m_transparencyThreshold != m_settings.m_transparencyThreshold) ||
(settings.m_opacityThreshold != m_settings.m_opacityThreshold) ||
(settings.m_palettes != m_settings.m_palettes) ||
(settings.m_palette != m_settings.m_palette) ||
(settings.m_horizontalPixelsPerDegree != m_settings.m_horizontalPixelsPerDegree) ||
(settings.m_verticalPixelsPerDegree != m_settings.m_verticalPixelsPerDegree))
{
// Call after settings have been applied
callProcessImage = true;
}
if ((settings.m_satTimeOffset != m_settings.m_satTimeOffset) ||
(settings.m_satYaw != m_settings.m_satYaw))
{
callRecalcCoords = true;
callProcessImage = true;
}
if (!settings.m_decodeEnabled && m_settings.m_decodeEnabled)
{
// Decode complete - make sure we do a full image update
// so we aren't left we unprocessed lines
callProcessImage = true;
}
if (settings.m_palettes != m_settings.m_palettes)
{
// Load colour palettes
m_palettes.clear();
for (auto palette : settings.m_palettes)
{
QImage img;
img.load(palette);
if ((img.width() != 256) || (img.height() != 256)) {
qWarning() << "APT colour palette " << palette << " is not 256x256 pixels - " << img.width() << "x" << img.height();
}
m_palettes.append(img);
}
}
m_settings = settings;
if (callRecalcCoords) {
recalcCoords();
}
if (callProcessImage) {
sendImageToGUI();
}
@ -153,25 +203,234 @@ void APTDemodImageWorker::applySettings(const APTDemodSettings& settings, bool f
void APTDemodImageWorker::resetDecoder()
{
m_image.nrow = 0;
m_image.zenith = 0;
m_tempImage.nrow = 0;
m_tempImage.zenith = 0;
m_greyImage = QImage(APT_IMG_WIDTH, APT_MAX_HEIGHT, QImage::Format_Grayscale8);
m_greyImage.fill(0);
m_colourImage = QImage(APT_IMG_WIDTH, APT_MAX_HEIGHT, QImage::Format_RGB888);
m_colourImage.fill(0);
m_satelliteName = "";
m_satCoords.clear();
m_pixelCoords.clear();
delete m_sgp4;
m_sgp4 = nullptr;
}
// Convert Qt QDataTime to QGP4 DateTime
static DateTime qDateTimeToDateTime(QDateTime qdt)
{
QDateTime utc = qdt.toUTC();
QDate date = utc.date();
QTime time = utc.time();
DateTime dt;
dt.Initialise(date.year(), date.month(), date.day(), time.hour(), time.minute(), time.second(), time.msec() * 1000);
return dt;
}
// Get heading in range [0,360)
static double normaliseHeading(double heading)
{
return fmod(heading + 360.0, 360.0);
}
// Get longitude in range -180,180
static double normaliseLongitude(double lon)
{
return fmod(lon + 540.0, 360.0) - 180.0;
}
// Calculate heading (azimuth) in degrees
double APTDemodImageWorker::calcHeading(CoordGeodetic from, CoordGeodetic to) const
{
// From https://en.wikipedia.org/wiki/Azimuth Section In Geodesy
double flattening = 1.0 / 298.257223563; // For WGS84 ellipsoid
double eSq = flattening * (2.0 - flattening);
double oneMinusESq = (1.0 - flattening) * (1.0 - flattening);
double tl1 = tan(from.latitude);
double tl2 = tan(to.latitude);
double n1 = 1.0 + oneMinusESq * tl2 * tl2;
double d1 = 1.0 + oneMinusESq * tl1 * tl1;
double l = to.longitude - from.longitude;
double alpha;
if (from.latitude == 0.0)
{
alpha = atan2(sin(l), (oneMinusESq * tan(to.latitude)));
}
else
{
double lambda = oneMinusESq * tan(to.latitude) / tan(from.latitude) + eSq * sqrt(n1/d1);
alpha = atan2(sin(l), ((lambda - cos(l)) * sin(from.latitude)));
}
double deg = Units::radiansToDegrees(alpha);
if (!m_settings.m_northToSouth) {
deg += 180.0;
}
deg = normaliseHeading(deg);
return deg;
}
// CoordGeodetic are in radians. Distance in metres. Bearing in radians.
// https://www.movable-type.co.uk/scripts/latlong.html
// This approximates Earth as spherical. If we need more accurate algorithm, see:
// https://www.movable-type.co.uk/scripts/latlong-vincenty.html
static void calcRadialEndPoint(CoordGeodetic start, double distance, double bearing, CoordGeodetic &end)
{
double earthRadius = 6378137.0; // At equator
double delta = distance/earthRadius;
end.latitude = std::asin(sin(start.latitude)*cos(delta) + cos(start.latitude)*sin(delta)*cos(bearing));
end.longitude = start.longitude + std::atan2(sin(bearing)*sin(delta)*cos(start.latitude), cos(delta) - sin(start.latitude)*sin(end.latitude));
end.longitude = normaliseLongitude(end.longitude);
}
void APTDemodImageWorker::calcPixelCoords(CoordGeodetic centreCoord, double heading)
{
// Calculate coordinates of each pixel in a row (swath)
// Assume satellite is at centre pixel, and project +-90 degrees from satellite heading
// https://www.ncei.noaa.gov/pub/data/satellite/publications/podguides/N-15%20thru%20N-19/pdf/APPENDIX%20J%20Instrument%20Scan%20Properties.pdf
// Swath for AVHRR/3 of 2926.6km at 833km altitude over spherical Earth
// Some docs say resolution is 4.0km, but it varies as per fig 4.2.3-1 in:
// https://www.ncei.noaa.gov/pub/data/satellite/publications/podguides/N-15%20thru%20N-19/pdf/2.1%20Section%204.0%20Real%20Time%20Data%20Systems%20for%20Local%20Users%20.pdf
// TODO: Could try to adjust for altitude
QVector<CoordGeodetic> pixelCoords(APT_CH_WIDTH);
pixelCoords[APT_CH_WIDTH/2] = centreCoord;
double heading1 = Units::degreesToRadians(heading + m_settings.m_satYaw + 90.0);
double heading2 = Units::degreesToRadians(heading + m_settings.m_satYaw - 90.0);
for (int i = 1; i <= APT_CH_WIDTH/2; i++)
{
double distance = i * 2926600.0/APT_CH_WIDTH;
calcRadialEndPoint(centreCoord, distance, heading1, pixelCoords[APT_CH_WIDTH/2-i]);
calcRadialEndPoint(centreCoord, distance, heading2, pixelCoords[APT_CH_WIDTH/2+i]);
}
if (m_settings.m_northToSouth) {
m_pixelCoords.append(pixelCoords);
} else {
m_pixelCoords.prepend(pixelCoords);
}
}
// Recalculate all pixel coordiantes as satTimeOffset or satYaw has changed
void APTDemodImageWorker::recalcCoords()
{
if (m_sgp4)
{
m_satCoords.clear();
m_pixelCoords.clear();
for (int row = 0; row < m_image.nrow; row++)
{
QDateTime qdt = m_settings.m_aosDateTime.addMSecs(m_settings.m_satTimeOffset * 1000.0f + row * 500);
calcCoords(qdt, row);
}
}
}
// Calculate pixel coordinates for a single row at the given date and time
void APTDemodImageWorker::calcCoords(QDateTime qdt, int row)
{
try
{
DateTime dt = qDateTimeToDateTime(qdt);
// Calculate satellite position
Eci eci = m_sgp4->FindPosition(dt);
// Convert satellite position to geodetic coordinates (lat and long)
CoordGeodetic geo = eci.ToGeodetic();
m_satCoords.append(geo);
// Calculate satellite heading (Could convert eci.Velocity() instead)
double heading;
if (m_satCoords.size() == 2)
{
heading = calcHeading(m_satCoords[0], m_satCoords[1]);
calcPixelCoords(m_satCoords[0], heading);
calcPixelCoords(m_satCoords[1], heading);
}
else if (m_satCoords.size() > 2)
{
heading = calcHeading(m_satCoords[row-1], m_satCoords[row]);
calcPixelCoords(geo, heading);
}
}
catch (SatelliteException& se)
{
qDebug() << "APTDemodImageWorker::calcCoord: " << se.what();
}
catch (DecayedException& de)
{
qDebug() << "APTDemodImageWorker::calcCoord: " << de.what();
}
catch (TleException& tlee)
{
qDebug() << "APTDemodImageWorker::calcCoord: " << tlee.what();
}
}
// Calculate satellite's geodetic coordinates and heading
void APTDemodImageWorker::calcCoord(int row)
{
if (row == 0)
{
QStringList tle = m_settings.m_tle.trimmed().split("\n");
if (tle.size() == 3)
{
// Initalise SGP4
Tle tle(tle[0].toStdString(), tle[1].toStdString(), tle[2].toStdString());
m_sgp4 = new SGP4(tle);
// Output time so we can check time offset from when AOS is signalled
qDebug() << "APTDemod: Processing row 0 at " << QDateTime::currentDateTime();
calcCoords(m_settings.m_aosDateTime, row);
}
else
{
qDebug() << "APTDemodImageWorker::calcCoord: No TLE for satellite. Is Satellite Tracker running?";
return;
}
}
else if (m_sgp4 == nullptr)
{
return;
}
else
{
// Calculate time at which
// Don't try to use QDateTime::currentDateTime() as processing & scheduling delays mean
// it's not constant and can sometimes even be 0
// Lines should be transmitted at 2 per second, so just use number of rows since AOS
// We add a user-defined delay to account for delays in transferring SDR data and demodulation
QDateTime qdt = m_settings.m_aosDateTime.addMSecs(m_settings.m_satTimeOffset * 1000.0f + row * 500);
calcCoords(qdt, row);
}
}
void APTDemodImageWorker::processPixels(const float *pixels)
{
std::copy(pixels, pixels + APT_PROW_WIDTH, m_image.prow[m_image.nrow]);
if (m_image.nrow < APT_MAX_HEIGHT)
{
// Calculate lat and lon of centre of row
calcCoord(m_image.nrow);
if (m_image.nrow % 20 == 0) { // send full image only every 20 lines
sendImageToGUI();
} else { // else send unprocessed line just to show stg is moving
sendLineToGUI();
std::copy(pixels, pixels + APT_PROW_WIDTH, m_image.prow[m_image.nrow]);
m_image.nrow++;
if (m_image.nrow % m_settings.m_scanlinesPerImageUpdate == 0) { // send full image only every N lines
sendImageToGUI();
} else { // else send unprocessed line just to show stg is moving
sendLineToGUI();
}
}
m_image.nrow++;
}
void APTDemodImageWorker::sendImageToGUI()
@ -180,8 +439,278 @@ void APTDemodImageWorker::sendImageToGUI()
if (m_messageQueueToGUI)
{
QStringList imageTypes;
QImage image = processImage(imageTypes);
QImage image = processImage(imageTypes, m_settings.m_channels);
m_messageQueueToGUI->push(APTDemod::MsgImage::create(image, imageTypes, m_satelliteName));
if (m_sgp4) {
sendImageToMap(image, imageTypes);
}
}
}
// Find the value of the pixel closest to the given coordinates
// If we have previously found a pixel, we constrain the search to be nearby, in order to speed up the search
QRgb APTDemodImageWorker::findNearest(const QImage &image, double latitude, double longitude, int xPrevious, int yPrevious, int &xNearest, int &yNearest) const
{
double dmin = 360.0 * 360.0 + 90.0 * 90.0;
xNearest = -1;
yNearest = -1;
QRgb p = qRgba(0, 0, 0, 0); // Transparent
int xMin, xMax;
int yMin, yMax;
int yStartPostCrop;
int yEndPostCrop;
if (m_settings.m_northToSouth)
{
yStartPostCrop = abs(m_tempImage.zenith);
yEndPostCrop = yStartPostCrop + image.height();
}
else
{
yStartPostCrop = m_image.nrow - m_tempImage.nrow - abs(m_tempImage.zenith);
yEndPostCrop = yStartPostCrop + image.height();
}
if (xPrevious == -1)
{
yMin = yStartPostCrop;
yMax = yEndPostCrop;
xMin = 0;
xMax = m_pixelCoords[0].size();
}
else
{
int searchRadius = 4;
yMin = yPrevious - searchRadius;
yMax = yPrevious + searchRadius + 1;
xMin = xPrevious - searchRadius;
xMax = xPrevious + searchRadius + 1;
yMin = std::max(yMin, yStartPostCrop);
yMax = std::min(yMax, yEndPostCrop);
xMin = std::max(xMin, 0);
xMax = std::min(xMax, m_pixelCoords[0].size());
}
const int ySize = yEndPostCrop-1;
const int xSize = m_pixelCoords[0].size()-1;
for (int y = yMin; y < yMax; y++)
{
for (int x = xMin; x < xMax; x++)
{
CoordGeodetic coord = m_pixelCoords[y][x];
double dlat = coord.latitude - latitude;
double dlon = coord.longitude - longitude;
double d = dlat * dlat + dlon * dlon;
if (d < dmin)
{
dmin = d;
xNearest = x;
yNearest = y;
// Only use color of pixel if we're inside the source image
if ( ((y != yStartPostCrop) || ((y == yStartPostCrop) && (latitude <= coord.latitude)))
&& ((y != ySize) || ((y == ySize) && (latitude >= coord.latitude)))
&& ((x != 0) || ((x == 0) && (longitude >= coord.longitude)))
&& ((x != xSize) || ((x == xSize) && (longitude <= coord.longitude)))
)
{
p = image.pixel(x, y - yStartPostCrop);
}
else
{
p = qRgba(0, 0, 0, 0); // Transparent
}
}
}
}
return p;
}
// Calculate bounding box for projected image in terms of latitude and longitude
// TODO: Handle crossing of anti-meridian
void APTDemodImageWorker::calcBoundingBox(double &east, double &south, double &west, double &north, const QImage &image)
{
int start;
if (m_settings.m_northToSouth) {
start = abs(m_tempImage.zenith);
} else {
start = m_image.nrow - m_tempImage.nrow - abs(m_tempImage.zenith);
}
int stop = start + image.height();
east = -M_PI;
west = M_PI;
north = -M_PI/2.0;
south = M_PI/2.0;
//FILE *f = fopen("coords.txt", "w");
for (int y = start; y < stop; y++)
{
for (int x = 0; x < m_pixelCoords[y].size(); x++)
{
double latitude = m_pixelCoords[y][x].latitude;
double longitude = m_pixelCoords[y][x].longitude;
//fprintf(f, "%f,%f ", Units::radiansToDegrees(m_pixelCoords[y][x].latitude), Units::radiansToDegrees(m_pixelCoords[y][x].longitude));
south = std::min(latitude, south);
north = std::max(latitude, north);
east = std::max(longitude, east);
west = std::min(longitude, west);
}
//fprintf(f, "\n");
}
//fclose(f);
}
// Project satellite image to equidistant cyclindrical projection (Plate Carree) for use on 3D Map
// We've previously computed lat and lon for each pixel in satellite image
// so we just work through coords in projected image, trying to find closest pixel in satellite image
// FIXME: How do we handle sat going over the poles?
QImage APTDemodImageWorker::projectImage(const QImage &image)
{
double east, south, west, north;
// Calculate bounding box for image tile
calcBoundingBox(east, south, west, north, image);
m_tileEast = ceil(Units::radiansToDegrees(east));
m_tileWest = floor(Units::radiansToDegrees(west));
m_tileNorth = ceil(Units::radiansToDegrees(north));
m_tileSouth = floor(Units::radiansToDegrees(south));
double widthDeg = m_tileEast - m_tileWest;
double heightDeg = m_tileNorth - m_tileSouth;
int width = widthDeg * m_settings.m_horizontalPixelsPerDegree;
int height = heightDeg * m_settings.m_verticalPixelsPerDegree;
//image.save("source.png");
//FILE *f = fopen("mapping.txt", "w");
QImage projection(width, height, QImage::Format_ARGB32);
int xNearest, yNearest, xPrevious, yPrevious;
xPrevious = -1;
yPrevious = -1;
for (int y = 0; y < height; y++)
{
// Calculate geodetic coords of pixel in projected image
double lat = m_tileNorth - (y / (double)m_settings.m_verticalPixelsPerDegree);
// Reverse search direction in alternate rows, so we are always seaching
// close to previously found pixel
if ((y & 1) == 0)
{
for (int x = 0; x < width; x++)
{
double lon = m_tileWest + (x / (double)m_settings.m_horizontalPixelsPerDegree);
// Find closest pixel in source image
QRgb pixel = findNearest(image, Units::degreesToRadians(lat), Units::degreesToRadians(lon), xPrevious, yPrevious, xNearest, yNearest);
xPrevious = xNearest;
yPrevious = yNearest;
projection.setPixel(x, y, pixel);
//fprintf(f, "%f,%f,%d,%d,%d ", lat, lon, xNearest, yNearest, pixel==0);
}
}
else
{
for (int x = width - 1; x >= 0; x--)
{
double lon = m_tileWest + (x / (double)m_settings.m_horizontalPixelsPerDegree);
// Find closest pixel in source image
QRgb pixel = findNearest(image, Units::degreesToRadians(lat), Units::degreesToRadians(lon), xPrevious, yPrevious, xNearest, yNearest);
xPrevious = xNearest;
yPrevious = yNearest;
projection.setPixel(x, y, pixel);
//fprintf(f, "%f,%f,%d,%d,%d ", lat, lon, xNearest, yNearest, pixel==0);
}
}
//fprintf(f, "\n");
}
//fclose(f);
return projection;
}
// Make an image transparent, so when overlaid on 3D map, we can see the underlying terrain
// Image is full transparent below m_transparencyThreshold and fully opaque above m_opacityThreshold
void APTDemodImageWorker::makeTransparent(QImage &image)
{
for (int y = 0; y < image.height(); y++)
{
for (int x = 0; x < image.width(); x++)
{
QRgb pixel = image.pixel(x, y);
int grey = qGray(pixel);
if (grey < m_settings.m_transparencyThreshold)
{
// Make fully transparent
pixel = qRgba(qRed(pixel), qGreen(pixel), qBlue(pixel), 0);
image.setPixel(x, y, pixel);
}
else if (grey < m_settings.m_opacityThreshold)
{
// Make slightly transparent
float opacity = 1.0f - ((m_settings.m_opacityThreshold - grey) / (float)(m_settings.m_opacityThreshold - m_settings.m_transparencyThreshold));
opacity = opacity * 255.0f;
opacity = std::min(255.0f, opacity);
opacity = std::max(0.0f, opacity);
pixel = qRgba(qRed(pixel), qGreen(pixel), qBlue(pixel), (int)std::round(opacity));
image.setPixel(x, y, pixel);
}
}
}
}
void APTDemodImageWorker::sendImageToMap(QImage image, QStringList imageTypes)
{
// Send to Map feature
MessagePipes& messagePipes = MainCore::instance()->getMessagePipes();
QList<MessageQueue*> *mapMessageQueues = messagePipes.getMessageQueues(m_aptDemod, "mapitems");
if (mapMessageQueues)
{
// Only display one channel on map
QImage selectedChannel;
if (m_settings.m_channels == APTDemodSettings::BOTH_CHANNELS) {
selectedChannel = extractImage(image, APTDemodSettings::CHANNEL_B);
} else {
selectedChannel = image;
}
// Project image to geodetic coords (lat & lon)
selectedChannel = projectImage(selectedChannel);
//selectedChannel.save("projected.png");
// Use alpha channel to remove land & sea
makeTransparent(selectedChannel);
// Encode image as base64 PNG
QByteArray ba;
QBuffer buffer(&ba);
buffer.open(QIODevice::WriteOnly);
selectedChannel.save(&buffer, "PNG");
QByteArray data = ba.toBase64();
// Create name for the image
QString satName = m_satelliteName;
satName.replace(" ", "_");
QString name = QString("apt_%1_%2").arg(satName).arg(m_settings.m_aosDateTime.toString("yyyyMMdd_hhmmss"));
// Send name to GUI
m_messageQueueToGUI->push(APTDemod::MsgMapImageName::create(name));
QList<MessageQueue*>::iterator it = mapMessageQueues->begin();
for (; it != mapMessageQueues->end(); ++it)
{
SWGSDRangel::SWGMapItem *swgMapItem = new SWGSDRangel::SWGMapItem();
swgMapItem->setName(new QString(name));
swgMapItem->setImage(new QString(data));
swgMapItem->setAltitude(3000.0); // Typical cloud height - So it appears above objects on the ground
swgMapItem->setType(1);
swgMapItem->setImageTileEast(m_tileEast);
swgMapItem->setImageTileWest(m_tileWest);
swgMapItem->setImageTileNorth(m_tileNorth);
swgMapItem->setImageTileSouth(m_tileSouth);
MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_aptDemod, swgMapItem);
(*it)->push(msg);
}
}
}
@ -189,7 +718,7 @@ void APTDemodImageWorker::sendLineToGUI()
{
if (m_messageQueueToGUI)
{
float *pixels = m_image.prow[m_image.nrow];
float *pixels = m_image.prow[m_image.nrow-1];
uchar *line;
APTDemod::MsgLine *msg = APTDemod::MsgLine::create(&line);
@ -219,12 +748,12 @@ void APTDemodImageWorker::sendLineToGUI()
}
}
QImage APTDemodImageWorker::processImage(QStringList& imageTypes)
QImage APTDemodImageWorker::processImage(QStringList& imageTypes, APTDemodSettings::ChannelSelection channels)
{
copyImage(&m_tempImage, &m_image);
// Calibrate channels according to wavelength
if (m_tempImage.nrow >= APT_CALIBRATION_ROWS)
// Calibrate channels according to wavelength (1.7x to stop flickering)
if (m_tempImage.nrow >= 1.7 * APT_CALIBRATION_ROWS)
{
m_tempImage.chA = apt_calibrate(m_tempImage.prow, m_tempImage.nrow, APT_CHA_OFFSET, APT_CH_WIDTH);
m_tempImage.chB = apt_calibrate(m_tempImage.prow, m_tempImage.nrow, APT_CHB_OFFSET, APT_CH_WIDTH);
@ -233,9 +762,9 @@ QImage APTDemodImageWorker::processImage(QStringList& imageTypes)
"Visible (0.58-0.68 um)",
"Near-IR (0.725-1.0 um)",
"Near-IR (1.58-1.64 um)",
"Mid-infrared (3.55-3.93 um)",
"Thermal-infrared (10.3-11.3 um)",
"Thermal-infrared (11.5-12.5 um)"
"Thermal-infrared (11.5-12.5 um)",
"Mid-infrared (3.55-3.93 um)"
});
imageTypes.append(channelTypes[m_tempImage.chA]);
@ -243,8 +772,9 @@ QImage APTDemodImageWorker::processImage(QStringList& imageTypes)
}
// Crop noise due to low elevation at top and bottom of image
if (m_settings.m_cropNoise)
if (m_settings.m_cropNoise) {
m_tempImage.zenith -= apt_cropNoise(&m_tempImage);
}
// Denoise filter
if (m_settings.m_denoise)
@ -305,10 +835,65 @@ QImage APTDemodImageWorker::processImage(QStringList& imageTypes)
}
}
}
return extractImage(m_colourImage);
return extractImage(m_colourImage, channels);
}
else if (channels == APTDemodSettings::TEMPERATURE)
{
// Temperature calibration
int satnum = 15;
if (m_satelliteName == "NOAA 18") {
satnum = 18;
} else if (m_satelliteName == "NOAA 19") {
satnum = 19;
}
apt_temperature(satnum, &m_tempImage, APT_CHB_OFFSET, APT_CH_WIDTH);
// Apply colour palette
for (int r = 0; r < m_tempImage.nrow; r++)
{
uchar *l = m_colourImage.scanLine(r);
for (int i = 0; i < APT_CH_WIDTH; i++)
{
float p = m_tempImage.prow[r][i+APT_CHB_OFFSET];
uchar q = roundAndClip(p);
l[i*3] = apt_TempPalette[q*3];
l[i*3+1] = apt_TempPalette[q*3+1];
l[i*3+2] = apt_TempPalette[q*3+2];
}
}
return m_colourImage.copy(0, 0, APT_CH_WIDTH, m_tempImage.nrow);
}
else if (channels == APTDemodSettings::PALETTE)
{
if ((m_settings.m_palette >= 0) && (m_settings.m_palette < m_palettes.size()))
{
// Apply colour palette
for (int r = 0; r < m_tempImage.nrow; r++)
{
uchar *l = m_colourImage.scanLine(r);
for (int i = 0; i < APT_CH_WIDTH; i++)
{
float pA = m_tempImage.prow[r][i+APT_CHA_OFFSET];
float pB = m_tempImage.prow[r][i+APT_CHB_OFFSET];
uchar qA = roundAndClip(pA);
uchar qB = roundAndClip(pB);
QRgb rgb = m_palettes[m_settings.m_palette].pixel(qA, qB);
l[i*3] = qRed(rgb);
l[i*3+1] = qGreen(rgb);
l[i*3+2] = qBlue(rgb);
}
}
return m_colourImage.copy(0, 0, APT_CH_WIDTH, m_tempImage.nrow);
}
else
{
qDebug() << "APTDemodImageWorker::processImage - Illegal palette number: " << m_settings.m_palette;
return QImage();
}
}
else
{
// Extract grey-scale image
for (int r = 0; r < m_tempImage.nrow; r++)
{
uchar *l = m_greyImage.scanLine(r);
@ -319,43 +904,92 @@ QImage APTDemodImageWorker::processImage(QStringList& imageTypes)
l[i] = roundAndClip(p);
}
}
return extractImage(m_greyImage);
return extractImage(m_greyImage, channels);
}
}
QImage APTDemodImageWorker::extractImage(QImage image)
QImage APTDemodImageWorker::extractImage(QImage image, APTDemodSettings::ChannelSelection channels)
{
if (m_settings.m_channels == APTDemodSettings::BOTH_CHANNELS) {
if (channels == APTDemodSettings::BOTH_CHANNELS) {
return image.copy(0, 0, APT_IMG_WIDTH, m_tempImage.nrow);
} else if (m_settings.m_channels == APTDemodSettings::CHANNEL_A) {
} else if (channels == APTDemodSettings::CHANNEL_A) {
return image.copy(APT_CHA_OFFSET, 0, APT_CH_WIDTH, m_tempImage.nrow);
} else {
return image.copy(APT_CHB_OFFSET, 0, APT_CH_WIDTH, m_tempImage.nrow);
}
}
void APTDemodImageWorker::prependPath(QString &filename)
{
if (!m_settings.m_autoSavePath.isEmpty())
{
if (m_settings.m_autoSavePath.endsWith('/')) {
filename = m_settings.m_autoSavePath + filename;
} else {
filename = m_settings.m_autoSavePath + '/' + filename;
}
}
}
void APTDemodImageWorker::saveImageToDisk()
{
QStringList imageTypes;
QImage image = processImage(imageTypes);
QImage image = processImage(imageTypes, APTDemodSettings::BOTH_CHANNELS);
if (image.height() >= m_settings.m_autoSaveMinScanLines)
{
QString filename;
QDateTime datetime = QDateTime::currentDateTime();
filename = QString("apt_%1_%2.png").arg(m_satelliteName.replace(" ", "_")).arg(datetime.toString("yyyyMMdd_hhmm"));
QDateTime dateTime;
QString dt;
if (m_settings.m_aosDateTime.isValid()) {
dateTime = m_settings.m_aosDateTime;
} else {
dateTime = QDateTime::currentDateTime();
}
dt = dateTime.toString("yyyyMMdd_hhmm");
QString sat = m_satelliteName;
sat.replace(" ", "_");
if (!m_settings.m_autoSavePath.isEmpty())
if (m_settings.m_saveCombined)
{
if (m_settings.m_autoSavePath.endsWith('/')) {
filename = m_settings.m_autoSavePath + filename;
} else {
filename = m_settings.m_autoSavePath + '/' + filename;
filename = QString("apt_%1_%2.png").arg(sat).arg(dt);
prependPath(filename);
if (!image.save(filename)) {
qCritical() << "Failed to save APT image to: " << filename;
}
}
if (!image.save(filename)) {
qCritical() << "Failed to save APT image to: " << filename;
QImage chA = extractImage(image, APTDemodSettings::CHANNEL_A);
QImage chB = extractImage(image, APTDemodSettings::CHANNEL_B);
if (m_settings.m_saveSeparate)
{
filename = QString("apt_%1_%2_cha.png").arg(sat).arg(dt);
prependPath(filename);
if (!chA.save(filename)) {
qCritical() << "Failed to save APT image to: " << filename;
}
filename = QString("apt_%1_%2_chb.png").arg(sat).arg(dt);
prependPath(filename);
if (!chB.save(filename)) {
qCritical() << "Failed to save APT image to: " << filename;
}
}
if (m_settings.m_saveProjection)
{
filename = QString("apt_%1_%2_cha_eqi_cylindrical.png").arg(sat).arg(dt);
prependPath(filename);
QImage chAProj = projectImage(chA);
if (!chAProj.save(filename)) {
qCritical() << "Failed to save APT image to: " << filename;
}
filename = QString("apt_%1_%2_chb_eqi_cylindrical.png").arg(sat).arg(dt);
prependPath(filename);
QImage chBProj = projectImage(chB);
if (!chBProj.save(filename)) {
qCritical() << "Failed to save APT image to: " << filename;
}
}
}
}

Wyświetl plik

@ -25,14 +25,22 @@
#include <apt.h>
#include <CoordTopocentric.h>
#include <CoordGeodetic.h>
#include <Observer.h>
#include <SGP4.h>
#include "util/messagequeue.h"
#include "util/message.h"
#include "aptdemodsettings.h"
class APTDemod;
class APTDemodImageWorker : public QObject
{
Q_OBJECT
public:
class MsgConfigureAPTDemodImageWorker : public Message {
MESSAGE_CLASS_DECLARATION
@ -95,7 +103,7 @@ public:
}
};
APTDemodImageWorker();
APTDemodImageWorker(APTDemod *aptDemod);
~APTDemodImageWorker();
void reset();
void startWork();
@ -109,6 +117,7 @@ private:
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
MessageQueue *m_messageQueueToGUI;
APTDemodSettings m_settings;
APTDemod *m_aptDemod;
// Image buffers
apt_image_t m_image; // Received image
@ -117,18 +126,39 @@ private:
QImage m_colourImage;
QString m_satelliteName;
QList<CoordGeodetic> m_satCoords; // Lat,lon for satellite for each image row - in received order for both pass directions
QVector<QVector<CoordGeodetic>> m_pixelCoords; // Coordinates for each pixel - reversed y order for south to north passes, so always highest lat first
SGP4 *m_sgp4; // For calculating satellite position
double m_tileEast; // Bounding box for projected image, in degrees
double m_tileWest;
double m_tileNorth;
double m_tileSouth;
QList<QImage> m_palettes;
bool m_running;
QMutex m_mutex;
bool handleMessage(const Message& cmd);
void applySettings(const APTDemodSettings& settings, bool force = false);
void resetDecoder();
double calcHeading(CoordGeodetic from, CoordGeodetic to) const;
void calcPixelCoords(CoordGeodetic centreCoord, double heading);
void recalcCoords();
void calcCoords(QDateTime qdt, int row);
void calcCoord(int row);
void processPixels(const float *pixels);
void sendImageToGUI();
QRgb findNearest(const QImage &image, double latitude, double longitude, int xPrevious, int yPrevious, int &xNearest, int &yNearest) const;
void calcBoundingBox(double &east, double &south, double &west, double &north, const QImage &image);
QImage projectImage(const QImage &image);
void makeTransparent(QImage &image);
void sendImageToMap(QImage image, QStringList imageTypes);
void sendLineToGUI();
void prependPath(QString &filename);
void saveImageToDisk();
QImage processImage(QStringList& imageTypes);
QImage extractImage(QImage image);
QImage processImage(QStringList& imageTypes, APTDemodSettings::ChannelSelection channels);
QImage extractImage(QImage image, APTDemodSettings::ChannelSelection channels);
static void copyImage(apt_image_t *dst, apt_image_t *src);
static uchar roundAndClip(float p);

Wyświetl plik

@ -0,0 +1,47 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <QDebug>
#include <QFileDialog>
#include "aptdemodselectdialog.h"
APTDemodSelectDialog::APTDemodSelectDialog(const QStringList &list, QWidget* parent) :
QDialog(parent),
ui(new Ui::APTDemodSelectDialog)
{
ui->setupUi(this);
for (auto item : list) {
ui->list->addItem(item);
}
}
APTDemodSelectDialog::~APTDemodSelectDialog()
{
delete ui;
}
void APTDemodSelectDialog::accept()
{
QList<QListWidgetItem *> items = ui->list->selectedItems();
m_selected.clear();
for (auto item : items)
{
m_selected.append(item->text());
}
QDialog::accept();
}

Wyświetl plik

@ -0,0 +1,40 @@
///////////////////////////////////////////////////////////////////////////////////
// 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/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_APTDEMODSELECTDIALOG_H
#define INCLUDE_APTDEMODSELECTDIALOG_H
#include "ui_aptdemodselectdialog.h"
#include "aptdemodsettings.h"
class APTDemodSelectDialog : public QDialog {
Q_OBJECT
public:
explicit APTDemodSelectDialog(const QStringList &list, QWidget* parent = 0);
~APTDemodSelectDialog();
QStringList getSelected() const { return m_selected; }
private slots:
void accept();
private:
QStringList m_selected;
Ui::APTDemodSelectDialog* ui;
};
#endif // INCLUDE_APTDEMODSELECTDIALOG_H

Wyświetl plik

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>APTDemodSelectDialog</class>
<widget class="QDialog" name="APTDemodSelectDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>304</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>APT Demodulator Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="listLabel">
<property name="text">
<string>Select images to delete from the map</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="list">
<property name="selectionMode">
<enum>QAbstractItemView::MultiSelection</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
<include location="icons.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>APTDemodSelectDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>APTDemodSelectDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

Wyświetl plik

@ -48,6 +48,18 @@ void APTDemodSettings::resetToDefaults()
m_autoSave = false;
m_autoSavePath = "";
m_autoSaveMinScanLines = 200;
m_saveCombined = true;
m_saveSeparate = false;
m_saveProjection = false;
m_scanlinesPerImageUpdate = 20;
m_transparencyThreshold = 100;
m_opacityThreshold = 200;
m_palettes.clear();
m_palette = 0;
m_horizontalPixelsPerDegree = 10;
m_verticalPixelsPerDegree = 20;
m_satTimeOffset = 0.0f;
m_satYaw = 0.0f;
m_rgbColor = QColor(216, 112, 169).rgb();
m_title = "APT Demodulator";
@ -79,6 +91,8 @@ QByteArray APTDemodSettings::serialize() const
s.writeBool(15, m_autoSave);
s.writeString(16, m_autoSavePath);
s.writeS32(17, m_autoSaveMinScanLines);
s.writeBool(18, m_saveProjection);
s.writeS32(19, m_scanlinesPerImageUpdate);
if (m_channelMarker) {
s.writeBlob(20, m_channelMarker->serialize());
@ -96,6 +110,17 @@ QByteArray APTDemodSettings::serialize() const
s.writeBlob(28, m_rollupState->serialize());
}
s.writeBool(29, m_saveCombined);
s.writeBool(30, m_saveSeparate);
s.writeS32(31, m_transparencyThreshold);
s.writeS32(32, m_opacityThreshold);
s.writeString(33, m_palettes.join(";"));
s.writeS32(34, m_palette);
s.writeS32(35, m_horizontalPixelsPerDegree);
s.writeS32(36, m_verticalPixelsPerDegree);
s.writeFloat(37, m_satTimeOffset);
s.writeFloat(38, m_satYaw);
return s.final();
}
@ -132,6 +157,8 @@ bool APTDemodSettings::deserialize(const QByteArray& data)
d.readBool(15, &m_autoSave, false);
d.readString(16, &m_autoSavePath, "");
d.readS32(17, &m_autoSaveMinScanLines, 200);
d.readBool(18, &m_saveProjection, false);
d.readS32(19, &m_scanlinesPerImageUpdate, 20);
if (m_channelMarker)
{
@ -162,6 +189,19 @@ bool APTDemodSettings::deserialize(const QByteArray& data)
m_rollupState->deserialize(bytetmp);
}
d.readBool(29, &m_saveCombined, true);
d.readBool(30, &m_saveSeparate, false);
d.readS32(31, &m_transparencyThreshold, 100);
d.readS32(32, &m_opacityThreshold, 200);
d.readString(33, &strtmp);
m_palettes = strtmp.split(";");
m_palettes.removeAll("");
d.readS32(34, &m_palette, 0);
d.readS32(35, &m_horizontalPixelsPerDegree, 10);
d.readS32(36, &m_verticalPixelsPerDegree, 20);
d.readFloat(37, &m_satTimeOffset, 0.0f);
d.readFloat(38, &m_satYaw, 0.0f);
return true;
}
else

Wyświetl plik

@ -21,6 +21,7 @@
#include <QByteArray>
#include <QHash>
#include <QDateTime>
class Serializable;
@ -35,18 +36,29 @@ struct APTDemodSettings
bool m_histogramEqualise;
bool m_precipitationOverlay;
bool m_flip;
enum ChannelSelection {BOTH_CHANNELS, CHANNEL_A, CHANNEL_B} m_channels;
enum ChannelSelection {BOTH_CHANNELS, CHANNEL_A, CHANNEL_B, TEMPERATURE, PALETTE} m_channels;
bool m_decodeEnabled;
bool m_satelliteTrackerControl; //! Whether Sat Tracker can set direction of pass
QString m_satelliteName; //!< All, NOAA 15, NOAA 18 or NOAA 19
bool m_autoSave;
QString m_autoSavePath;
int m_autoSaveMinScanLines;
bool m_saveCombined;
bool m_saveSeparate;
bool m_saveProjection;
int m_scanlinesPerImageUpdate;
int m_transparencyThreshold;
int m_opacityThreshold;
QStringList m_palettes; // List of 256x256 images to use a colour palette
int m_palette; // Index in to m_palettes - only if m_channels==PALETTE
int m_horizontalPixelsPerDegree; // Resolution for projected image
int m_verticalPixelsPerDegree;
float m_satTimeOffset;
float m_satYaw;
quint32 m_rgbColor;
QString m_title;
Serializable *m_channelMarker;
QString m_audioDeviceName;
int m_streamIndex; //!< MIMO channel. Not relevant when connected to SI (single Rx).
bool m_useReverseAPI;
QString m_reverseAPIAddress;
@ -55,6 +67,11 @@ struct APTDemodSettings
uint16_t m_reverseAPIChannelIndex;
Serializable *m_rollupState;
// The following are really working state, rather than settings
QString m_tle; // Satelite two-line elements, from satellite tracker
QDateTime m_aosDateTime; // When decoder was started (may not be current time, if replaying old file)
bool m_northToSouth; // Separate from flip, in case user changes it
APTDemodSettings();
void resetToDefaults();
void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; }

Wyświetl plik

@ -25,12 +25,27 @@ APTDemodSettingsDialog::APTDemodSettingsDialog(APTDemodSettings *settings, QWidg
m_settings(settings),
ui(new Ui::APTDemodSettingsDialog)
{
int idx;
ui->setupUi(this);
ui->satelliteTrackerControl->setChecked(settings->m_satelliteTrackerControl);
ui->satellite->setCurrentText(settings->m_satelliteName);
ui->autoSave->setChecked(settings->m_autoSave);
ui->saveCombined->setChecked(settings->m_saveCombined);
ui->saveSeparate->setChecked(settings->m_saveSeparate);
ui->saveProjection->setChecked(settings->m_saveProjection);
ui->autoSavePath->setText(settings->m_autoSavePath);
ui->minScanlines->setValue(settings->m_autoSaveMinScanLines);
ui->scanlinesPerImageUpdate->setValue(settings->m_scanlinesPerImageUpdate);
idx = ui->horizontalPixelsPerDegree->findText(QString::number(settings->m_horizontalPixelsPerDegree));
ui->horizontalPixelsPerDegree->setCurrentIndex(idx);
idx = ui->verticalPixelsPerDegree->findText(QString::number(settings->m_verticalPixelsPerDegree));
ui->verticalPixelsPerDegree->setCurrentIndex(idx);
ui->satTimeOffset->setValue(settings->m_satTimeOffset);
ui->satYaw->setValue(settings->m_satYaw);
for (auto file : settings->m_palettes) {
ui->palettes->addItem(file);
}
on_autoSave_clicked(settings->m_autoSave);
}
APTDemodSettingsDialog::~APTDemodSettingsDialog()
@ -43,8 +58,20 @@ void APTDemodSettingsDialog::accept()
m_settings->m_satelliteTrackerControl = ui->satelliteTrackerControl->isChecked();
m_settings->m_satelliteName = ui->satellite->currentText();
m_settings->m_autoSave = ui->autoSave->isChecked();
m_settings->m_saveCombined = ui->saveCombined->isChecked();
m_settings->m_saveSeparate = ui->saveSeparate->isChecked();
m_settings->m_saveProjection = ui->saveProjection->isChecked();
m_settings->m_autoSavePath = ui->autoSavePath->text();
m_settings->m_autoSaveMinScanLines = ui->minScanlines->value();
m_settings->m_scanlinesPerImageUpdate = ui->scanlinesPerImageUpdate->value();
m_settings->m_palettes.clear();
m_settings->m_horizontalPixelsPerDegree = ui->horizontalPixelsPerDegree->currentText().toInt();
m_settings->m_verticalPixelsPerDegree = ui->verticalPixelsPerDegree->currentText().toInt();
m_settings->m_satTimeOffset = ui->satTimeOffset->value();
m_settings->m_satYaw = ui->satYaw->value();
for (int i = 0; i < ui->palettes->count(); i++) {
m_settings->m_palettes.append(ui->palettes->item(i)->text());
}
QDialog::accept();
}
@ -54,3 +81,41 @@ void APTDemodSettingsDialog::on_autoSavePathBrowse_clicked()
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
ui->autoSavePath->setText(dir);
}
void APTDemodSettingsDialog::on_autoSave_clicked(bool checked)
{
/* Commented out until theme greys out disabled widgets
ui->saveProjectionLabel->setEnabled(checked);
ui->saveCombined->setEnabled(checked);
ui->saveSeparate->setEnabled(checked);
ui->saveProjection->setEnabled(checked);
ui->autoSavePathLabel->setEnabled(checked);
ui->autoSavePath->setEnabled(checked);
ui->autoSavePathBrowse->setEnabled(checked);
ui->minScanlinesLabel->setEnabled(checked);
ui->minScanlines->setEnabled(checked);
*/
}
void APTDemodSettingsDialog::on_addPalette_clicked()
{
QFileDialog fileDialog(nullptr, "Select palette files", "", "*.png;*.bmp");
fileDialog.setFileMode(QFileDialog::ExistingFiles);
if (fileDialog.exec())
{
QStringList fileNames = fileDialog.selectedFiles();
for (auto fileName : fileNames) {
ui->palettes->addItem(fileName);
}
}
}
void APTDemodSettingsDialog::on_removePalette_clicked()
{
QList<QListWidgetItem *> items = ui->palettes->selectedItems();
for (auto item : items)
{
ui->palettes->removeItemWidget(item);
delete item;
}
}

Wyświetl plik

@ -33,6 +33,9 @@ public:
private slots:
void accept();
void on_autoSavePathBrowse_clicked();
void on_autoSave_clicked(bool checked);
void on_addPalette_clicked();
void on_removePalette_clicked();
private:
Ui::APTDemodSettingsDialog* ui;

Wyświetl plik

@ -6,13 +6,12 @@
<rect>
<x>0</x>
<y>0</y>
<width>385</width>
<height>212</height>
<width>600</width>
<height>576</height>
</rect>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
@ -29,69 +28,31 @@
</sizepolicy>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="5" column="0">
<widget class="QLabel" name="autoSavePathLabel">
<item row="0" column="0">
<widget class="QLabel" name="satelliteTrackerControlLabel">
<property name="text">
<string>Path to save image</string>
<string>Enable Satellite Tracker control</string>
</property>
</widget>
</item>
<item row="5" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="autoSavePath">
<property name="toolTip">
<string>Path to save images to</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="autoSavePathBrowse">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/load.png</normaloff>:/load.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item row="6" column="0">
<widget class="QLabel" name="minScanlinesLabel">
<property name="text">
<string>Minimum scanlines</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QSpinBox" name="minScanlines">
<item row="0" column="1">
<widget class="QCheckBox" name="satelliteTrackerControl">
<property name="toolTip">
<string>Enter the minimum number of scanlines in an image (after cropping) for it to be automatically saved</string>
<string>Check to enable control by Satellite Tracker feature</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>30000</number>
</property>
<property name="singleStep">
<number>100</number>
</property>
<property name="value">
<number>200</number>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="2" column="0">
<item row="3" column="0">
<widget class="QLabel" name="satelliteLabel">
<property name="text">
<string>Satellite</string>
</property>
</widget>
</item>
<item row="2" column="1">
<item row="3" column="1">
<widget class="QComboBox" name="satellite">
<property name="toolTip">
<string>Select which satellite this channel will be used for</string>
@ -121,23 +82,316 @@
</item>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="satelliteTrackerControl">
<property name="toolTip">
<string>Check to enable control by Satellite Tracker feature</string>
</property>
<item row="4" column="0">
<widget class="QLabel" name="autoSaveLabel">
<property name="text">
<string>Enable Satellite Tracker control</string>
<string>Auto save images</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<item row="4" column="1">
<widget class="QCheckBox" name="autoSave">
<property name="toolTip">
<string>Check to automatically save images when acquisition is stopped or LOS</string>
</property>
<property name="text">
<string>Auto save image</string>
<string/>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="saveCombinedLabel">
<property name="text">
<string>Save combined image</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="saveCombined">
<property name="toolTip">
<string>Save a combined image of both channel A and B</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="saveSeparateLabel">
<property name="text">
<string>Save separate images</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QCheckBox" name="saveSeparate">
<property name="toolTip">
<string>Save images from channels A and B to separate files</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="saveProjectionLabel">
<property name="text">
<string>Save projected images</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QCheckBox" name="saveProjection">
<property name="toolTip">
<string>Saves the equidistant cylindrical projected image</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="autoSavePathLabel">
<property name="text">
<string>Path to save images</string>
</property>
</widget>
</item>
<item row="9" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="autoSavePath">
<property name="toolTip">
<string>Path to save images to</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="autoSavePathBrowse">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/load.png</normaloff>:/load.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item row="11" column="0">
<widget class="QLabel" name="minScanlinesLabel">
<property name="text">
<string>Minimum scanlines for auto save</string>
</property>
</widget>
</item>
<item row="11" column="1">
<widget class="QSpinBox" name="minScanlines">
<property name="toolTip">
<string>Enter the minimum number of scanlines in an image (after cropping) for it to be automatically saved</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>30000</number>
</property>
<property name="singleStep">
<number>100</number>
</property>
<property name="value">
<number>200</number>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="scanlinesPerImageUpdateLabel">
<property name="text">
<string>Scanlines per image update</string>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="QSpinBox" name="scanlinesPerImageUpdate">
<property name="toolTip">
<string>How often the image processing functions are applied to the image and how often it is sent to the map</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="17" column="0">
<widget class="QLabel" name="palettesLabel">
<property name="text">
<string>Colour palettes</string>
</property>
</widget>
</item>
<item row="17" column="1">
<widget class="QListWidget" name="palettes">
<property name="selectionMode">
<enum>QAbstractItemView::MultiSelection</enum>
</property>
</widget>
</item>
<item row="18" column="1">
<layout class="QHBoxLayout" name="buttonsHhorizontalLayout">
<item>
<widget class="QPushButton" name="addPalette">
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removePalette">
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item>
<spacer name="buttonsHorizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="13" column="1">
<widget class="QComboBox" name="horizontalPixelsPerDegree">
<property name="toolTip">
<string>Number of pixels per degree longitude in projected image</string>
</property>
<item>
<property name="text">
<string>10</string>
</property>
</item>
<item>
<property name="text">
<string>15</string>
</property>
</item>
<item>
<property name="text">
<string>20</string>
</property>
</item>
</widget>
</item>
<item row="14" column="1">
<widget class="QComboBox" name="verticalPixelsPerDegree">
<property name="toolTip">
<string>Number of pixels per degree latitude in projected image</string>
</property>
<item>
<property name="text">
<string>20</string>
</property>
</item>
<item>
<property name="text">
<string>30</string>
</property>
</item>
<item>
<property name="text">
<string>40</string>
</property>
</item>
<item>
<property name="text">
<string>45</string>
</property>
</item>
<item>
<property name="text">
<string>50</string>
</property>
</item>
<item>
<property name="text">
<string>55</string>
</property>
</item>
<item>
<property name="text">
<string>60</string>
</property>
</item>
</widget>
</item>
<item row="13" column="0">
<widget class="QLabel" name="horizontalPixelsPerDegreeLabel">
<property name="text">
<string>Horizontal pixels per degree</string>
</property>
</widget>
</item>
<item row="14" column="0">
<widget class="QLabel" name="verticalPixelsPerDegreeLabel">
<property name="text">
<string>Vertical pixels per degree</string>
</property>
</widget>
</item>
<item row="15" column="0">
<widget class="QLabel" name="satTimeOffsetLabel">
<property name="text">
<string>Satellite position time offset (s)</string>
</property>
</widget>
</item>
<item row="15" column="1">
<widget class="QDoubleSpinBox" name="satTimeOffset">
<property name="toolTip">
<string>Time offset in seconds to add when calculating satellites position.
This may be used to help align images on the map.</string>
</property>
<property name="decimals">
<number>1</number>
</property>
<property name="minimum">
<double>-100.000000000000000</double>
</property>
</widget>
</item>
<item row="16" column="0">
<widget class="QLabel" name="satYawLabel">
<property name="text">
<string>Satellite yaw correction (°)</string>
</property>
</widget>
</item>
<item row="16" column="1">
<widget class="QDoubleSpinBox" name="satYaw">
<property name="toolTip">
<string>Add yaw offset to help with aligning images on the map.</string>
</property>
<property name="decimals">
<number>2</number>
</property>
<property name="minimum">
<double>-10.000000000000000</double>
</property>
<property name="maximum">
<double>10.000000000000000</double>
</property>
<property name="singleStep">
<double>0.250000000000000</double>
</property>
</widget>
</item>
@ -160,9 +414,16 @@
<tabstop>satelliteTrackerControl</tabstop>
<tabstop>satellite</tabstop>
<tabstop>autoSave</tabstop>
<tabstop>saveCombined</tabstop>
<tabstop>saveSeparate</tabstop>
<tabstop>saveProjection</tabstop>
<tabstop>autoSavePath</tabstop>
<tabstop>autoSavePathBrowse</tabstop>
<tabstop>minScanlines</tabstop>
<tabstop>scanlinesPerImageUpdate</tabstop>
<tabstop>palettes</tabstop>
<tabstop>addPalette</tabstop>
<tabstop>removePalette</tabstop>
</tabstops>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>

Wyświetl plik

@ -81,6 +81,12 @@ APTDemodActions:
northToSouthPass:
description: "Satellite is passing from the North to the South (1) or South to North (0)"
type: integer
tle:
description: "Two line elements for satellite"
type: string
dateTime:
description: "Date and time of AOS (May differ from system clock when replaying old passes)"
type: string
los:
description: "Loss of signal"
type: object