APT demod: moved processPixels process to a separate thread

This commit is contained in:
f4exb 2021-04-22 22:10:04 +02:00
parent de23efe635
commit aac8f6fe2c
8 changed files with 456 additions and 13 deletions

View File

@ -5,6 +5,7 @@ set(demodapt_SOURCES
aptdemodsettings.cpp
aptdemodbaseband.cpp
aptdemodsink.cpp
aptdemodimageworker.cpp
aptdemodplugin.cpp
aptdemodwebapiadapter.cpp
)
@ -14,6 +15,7 @@ set(demodapt_HEADERS
aptdemodsettings.h
aptdemodbaseband.h
aptdemodsink.h
aptdemodimageworker.h
aptdemodplugin.h
aptdemodwebapiadapter.h
)

View File

@ -58,9 +58,12 @@ APTDemod::APTDemod(DeviceAPI *deviceAPI) :
setObjectName(m_channelId);
m_basebandSink = new APTDemodBaseband(this);
m_basebandSink->setMessageQueueToChannel(getInputMessageQueue());
m_basebandSink->moveToThread(&m_thread);
m_imageWorker = new APTDemodImageWorker();
m_basebandSink->setImagWorkerMessageQueue(m_imageWorker->getInputMessageQueue());
m_imageWorker->moveToThread(&m_imageThread);
applySettings(m_settings, true);
m_deviceAPI->addChannelSink(this);
@ -74,7 +77,8 @@ APTDemod::APTDemod(DeviceAPI *deviceAPI) :
m_image.prow[y] = new float[APT_PROW_WIDTH];
m_tempImage.prow[y] = new float[APT_PROW_WIDTH];
}
resetDecoder();
resetDecoder(); // FIXME: to be removed
}
APTDemod::~APTDemod()
@ -85,16 +89,22 @@ APTDemod::~APTDemod()
m_deviceAPI->removeChannelSinkAPI(this);
m_deviceAPI->removeChannelSink(this);
if (m_imageWorker->isRunning()) {
stopImageWorker();
}
delete m_imageWorker;
if (m_basebandSink->isRunning()) {
stop();
stopBasebandSink();
}
delete m_basebandSink;
for (int y = 0; y < APT_MAX_HEIGHT; y++)
{
delete m_image.prow[y];
delete m_tempImage.prow[y];
delete[] m_image.prow[y];
delete[] m_tempImage.prow[y];
}
}
@ -110,6 +120,12 @@ void APTDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto
}
void APTDemod::start()
{
startBasebandSink();
startImageWorker();
}
void APTDemod::startBasebandSink()
{
qDebug("APTDemod::start");
@ -124,7 +140,25 @@ void APTDemod::start()
m_basebandSink->getInputMessageQueue()->push(msg);
}
void APTDemod::startImageWorker()
{
qDebug("APTDemod::startImageWorker");
m_imageWorker->reset();
m_imageWorker->startWork();
m_imageThread.start();
APTDemodImageWorker::MsgConfigureAPTDemodImageWorker *msg = APTDemodImageWorker::MsgConfigureAPTDemodImageWorker::create(m_settings, true);
m_imageWorker->getInputMessageQueue()->push(msg);
}
void APTDemod::stop()
{
stopImageWorker();
stopBasebandSink();
}
void APTDemod::stopBasebandSink()
{
qDebug("APTDemod::stop");
m_basebandSink->stopWork();
@ -132,6 +166,14 @@ void APTDemod::stop()
m_thread.wait();
}
void APTDemod::stopImageWorker()
{
qDebug("APTDemod::stopImageWorker");
m_imageWorker->stopWork();
m_imageThread.quit();
m_imageThread.wait();
}
bool APTDemod::matchSatellite(const QString satelliteName)
{
return m_settings.m_satelliteTrackerControl
@ -176,7 +218,8 @@ bool APTDemod::handleMessage(const Message& cmd)
}
else if (APTDemod::MsgResetDecoder::match(cmd))
{
resetDecoder();
resetDecoder(); // FIXME: to be removed
m_imageWorker->getInputMessageQueue()->push(APTDemod::MsgResetDecoder::create());
// Forward to sink
m_basebandSink->getInputMessageQueue()->push(APTDemod::MsgResetDecoder::create());
return true;
@ -579,7 +622,8 @@ int APTDemod::webapiActionsPost(
if (matchSatellite(*satelliteName))
{
// Reset for new pass
resetDecoder();
resetDecoder(); // FIXME: to be removed
m_imageWorker->getInputMessageQueue()->push(APTDemod::MsgResetDecoder::create());
m_basebandSink->getInputMessageQueue()->push(APTDemod::MsgResetDecoder::create());
// Save satellite name

View File

@ -32,12 +32,14 @@
#include "util/message.h"
#include "aptdemodbaseband.h"
#include "aptdemodimageworker.h"
#include "aptdemodsettings.h"
class QNetworkAccessManager;
class QNetworkReply;
class QThread;
class DeviceAPI;
class APTDemodImageWorker;
class APTDemod : public BasebandSampleSink, public ChannelAPI {
Q_OBJECT
@ -144,8 +146,18 @@ public:
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po);
virtual void start();
virtual void stop();
virtual void startBasebandSink();
virtual void stopBasebandSink();
virtual void startImageWorker();
virtual void stopImageWorker();
virtual bool handleMessage(const Message& cmd);
void setMessageQueueToGUI(MessageQueue* queue) override
{
ChannelAPI::setMessageQueueToGUI(queue);
m_imageWorker->setMessageQueueToGUI(queue);
}
virtual void getIdentifier(QString& id) { id = objectName(); }
virtual const QString& getURI() const { return getName(); }
virtual void getTitle(QString& title) { title = m_settings.m_title; }
@ -202,7 +214,9 @@ public:
private:
DeviceAPI *m_deviceAPI;
QThread m_thread;
QThread m_imageThread;
APTDemodBaseband* m_basebandSink;
APTDemodImageWorker *m_imageWorker;
APTDemodSettings m_settings;
int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
qint64 m_centerFrequency;

View File

@ -68,7 +68,7 @@ public:
void getMagSqLevels(double& avg, double& peak, int& nbSamples) {
m_sink.getMagSqLevels(avg, peak, nbSamples);
}
void setMessageQueueToChannel(MessageQueue *messageQueue) { m_sink.setMessageQueueToChannel(messageQueue); }
void setImagWorkerMessageQueue(MessageQueue *messageQueue) { m_sink.setImageWorkerMessageQueue(messageQueue); }
void setBasebandSampleRate(int sampleRate);
double getMagSq() const { return m_sink.getMagSq(); }
bool isRunning() const { return m_running; }

View File

@ -0,0 +1,279 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015-2018 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 <algorithm>
#include "aptdemod.h"
#include "aptdemodimageworker.h"
MESSAGE_CLASS_DEFINITION(APTDemodImageWorker::MsgConfigureAPTDemodImageWorker, Message)
APTDemodImageWorker::APTDemodImageWorker() :
m_messageQueueToGUI(nullptr),
m_running(false),
m_mutex(QMutex::Recursive)
{
for (int y = 0; y < APT_MAX_HEIGHT; y++)
{
m_image.prow[y] = new float[APT_PROW_WIDTH];
m_tempImage.prow[y] = new float[APT_PROW_WIDTH];
}
resetDecoder();
}
APTDemodImageWorker::~APTDemodImageWorker()
{
m_inputMessageQueue.clear();
for (int y = 0; y < APT_MAX_HEIGHT; y++)
{
delete[] m_image.prow[y];
delete[] m_tempImage.prow[y];
}
}
void APTDemodImageWorker::reset()
{
QMutexLocker mutexLocker(&m_mutex);
m_inputMessageQueue.clear();
}
void APTDemodImageWorker::startWork()
{
QMutexLocker mutexLocker(&m_mutex);
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
m_running = true;
}
void APTDemodImageWorker::stopWork()
{
QMutexLocker mutexLocker(&m_mutex);
disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
m_running = false;
}
void APTDemodImageWorker::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
if (handleMessage(*message)) {
delete message;
}
}
}
bool APTDemodImageWorker::handleMessage(const Message& cmd)
{
if (MsgConfigureAPTDemodImageWorker::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureAPTDemodImageWorker& cfg = (MsgConfigureAPTDemodImageWorker&) cmd;
qDebug("APTDemodImageWorker::handleMessage: MsgConfigureAPTDemodImageWorker");
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (APTDemod::MsgPixels::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
const APTDemod::MsgPixels& pixelsMsg = (APTDemod::MsgPixels&) cmd;
const float *pixels = pixelsMsg.getPixels();
processPixels(pixels);
return true;
}
else if (APTDemod::MsgResetDecoder::match(cmd))
{
resetDecoder();
return true;
}
else
{
return false;
}
}
void APTDemodImageWorker::applySettings(const APTDemodSettings& settings, bool force)
{
(void) force;
m_settings = settings;
}
void APTDemodImageWorker::resetDecoder()
{
m_image.nrow = 0;
m_tempImage.nrow = 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 = "";
}
void APTDemodImageWorker::processPixels(const float *pixels)
{
std::copy(pixels, pixels + APT_PROW_WIDTH, m_image.prow[m_image.nrow]);
m_image.nrow++;
sendImageToGUI();
}
void APTDemodImageWorker::sendImageToGUI()
{
// Send image to GUI
if (m_messageQueueToGUI)
{
QStringList imageTypes;
QImage image = processImage(imageTypes);
m_messageQueueToGUI->push(APTDemod::MsgImage::create(image, imageTypes, m_satelliteName));
}
}
QImage APTDemodImageWorker::processImage(QStringList& imageTypes)
{
copyImage(&m_tempImage, &m_image);
// Calibrate channels according to wavelength
if (m_tempImage.nrow >= 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);
QStringList channelTypes({
"", // Unknown
"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)"
});
imageTypes.append(channelTypes[m_tempImage.chA]);
imageTypes.append(channelTypes[m_tempImage.chB]);
}
// Crop noise due to low elevation at top and bottom of image
if (m_settings.m_cropNoise)
m_tempImage.zenith -= apt_cropNoise(&m_tempImage);
// Denoise filter
if (m_settings.m_denoise)
{
apt_denoise(m_tempImage.prow, m_tempImage.nrow, APT_CHA_OFFSET, APT_CH_WIDTH);
apt_denoise(m_tempImage.prow, m_tempImage.nrow, APT_CHB_OFFSET, APT_CH_WIDTH);
}
// Flip image if satellite pass is North to South
if (m_settings.m_flip)
{
apt_flipImage(&m_tempImage, APT_CH_WIDTH, APT_CHA_OFFSET);
apt_flipImage(&m_tempImage, APT_CH_WIDTH, APT_CHB_OFFSET);
}
// Linear equalise to improve contrast
if (m_settings.m_linearEqualise)
{
apt_linearEnhance(m_tempImage.prow, m_tempImage.nrow, APT_CHA_OFFSET, APT_CH_WIDTH);
apt_linearEnhance(m_tempImage.prow, m_tempImage.nrow, APT_CHB_OFFSET, APT_CH_WIDTH);
}
// Histogram equalise to improve contrast
if (m_settings.m_histogramEqualise)
{
apt_histogramEqualise(m_tempImage.prow, m_tempImage.nrow, APT_CHA_OFFSET, APT_CH_WIDTH);
apt_histogramEqualise(m_tempImage.prow, m_tempImage.nrow, APT_CHB_OFFSET, APT_CH_WIDTH);
}
if (m_settings.m_precipitationOverlay)
{
// Overlay precipitation
for (int r = 0; r < m_tempImage.nrow; r++)
{
uchar *l = m_colourImage.scanLine(r);
for (int i = 0; i < APT_IMG_WIDTH; i++)
{
float p = m_tempImage.prow[r][i];
if ((i >= APT_CHB_OFFSET) && (i < APT_CHB_OFFSET + APT_CH_WIDTH) && (p >= 198))
{
apt_rgb_t rgb = apt_applyPalette(apt_PrecipPalette, p - 198);
// Negative float values get converted to positive uchars here
l[i*3] = (uchar)rgb.r;
l[i*3+1] = (uchar)rgb.g;
l[i*3+2] = (uchar)rgb.b;
int a = i - APT_CHB_OFFSET + APT_CHA_OFFSET;
l[a*3] = (uchar)rgb.r;
l[a*3+1] = (uchar)rgb.g;
l[a*3+2] = (uchar)rgb.b;
}
else
{
uchar q = roundAndClip(p);
l[i*3] = q;
l[i*3+1] = q;
l[i*3+2] = q;
}
}
}
return extractImage(m_colourImage);
}
else
{
for (int r = 0; r < m_tempImage.nrow; r++)
{
uchar *l = m_greyImage.scanLine(r);
for (int i = 0; i < APT_IMG_WIDTH; i++)
{
float p = m_tempImage.prow[r][i];
l[i] = roundAndClip(p);
}
}
return extractImage(m_greyImage);
}
}
QImage APTDemodImageWorker::extractImage(QImage image)
{
if (m_settings.m_channels == APTDemodSettings::BOTH_CHANNELS) {
return image.copy(0, 0, APT_IMG_WIDTH, m_tempImage.nrow);
} else if (m_settings.m_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::copyImage(apt_image_t *dst, apt_image_t *src)
{
dst->nrow = src->nrow;
dst->zenith = src->zenith;
dst->chA = src->chA;
dst->chB = src->chB;
for (int i = 0; i < src->nrow; i++) {
std::copy(src->prow[i], src->prow[i] + APT_PROW_WIDTH, dst->prow[i]);
}
}
uchar APTDemodImageWorker::roundAndClip(float p)
{
int q = (int) round(p);
q = q > 255 ? 255 : q < 0 ? 0 : q;
return q;
}

View File

@ -0,0 +1,100 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015-2018 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/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_APTDEMODIMAGEWORKER_H
#define INCLUDE_APTDEMODIMAGEWORKER_H
#include <QObject>
#include <QMutex>
#include <QImage>
#include <apt.h>
#include "util/messagequeue.h"
#include "util/message.h"
#include "aptdemodsettings.h"
class APTDemodImageWorker : public QObject
{
Q_OBJECT
public:
class MsgConfigureAPTDemodImageWorker : public Message {
MESSAGE_CLASS_DECLARATION
public:
const APTDemodSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureAPTDemodImageWorker* create(const APTDemodSettings& settings, bool force)
{
return new MsgConfigureAPTDemodImageWorker(settings, force);
}
private:
APTDemodSettings m_settings;
bool m_force;
MsgConfigureAPTDemodImageWorker(const APTDemodSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
APTDemodImageWorker();
~APTDemodImageWorker();
void reset();
void startWork();
void stopWork();
bool isRunning() const { return m_running; }
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
void setMessageQueueToGUI(MessageQueue *messageQueue) { m_messageQueueToGUI = messageQueue; }
private:
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
MessageQueue *m_messageQueueToGUI;
APTDemodSettings m_settings;
// Image buffers
apt_image_t m_image; // Received image
apt_image_t m_tempImage; // Processed image
QImage m_greyImage;
QImage m_colourImage;
QString m_satelliteName;
bool m_running;
QMutex m_mutex;
bool handleMessage(const Message& cmd);
void applySettings(const APTDemodSettings& settings, bool force = false);
void resetDecoder();
void processPixels(const float *pixels);
void sendImageToGUI();
QImage processImage(QStringList& imageTypes);
QImage extractImage(QImage image);
static void copyImage(apt_image_t *dst, apt_image_t *src);
static uchar roundAndClip(float p);
private slots:
void handleInputMessages();
};
#endif // INCLUDE_APTDEMODIMAGEWORKER_H

View File

@ -37,7 +37,7 @@ APTDemodSink::APTDemodSink(APTDemod *packetDemod) :
m_magsqSum(0.0f),
m_magsqPeak(0.0f),
m_magsqCount(0),
m_messageQueueToChannel(nullptr),
m_imageWorkerMessageQueue(nullptr),
m_samples(nullptr)
{
m_magsq = 0.0;
@ -124,7 +124,11 @@ void APTDemodSink::feed(const SampleVector::const_iterator& begin, const SampleV
{
float pixels[APT_PROW_WIDTH];
apt_getpixelrow(pixels, m_row, &m_zenith, m_row == 0, getsamples, this);
getMessageQueueToChannel()->push(APTDemod::MsgPixels::create(pixels, m_zenith));
if (getImageWorkerMessageQueue()) {
getImageWorkerMessageQueue()->push(APTDemod::MsgPixels::create(pixels, m_zenith));
}
m_row++;
}
}

View File

@ -51,7 +51,7 @@ public:
void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false);
void applySettings(const APTDemodSettings& settings, bool force = false);
void setMessageQueueToChannel(MessageQueue *messageQueue) { m_messageQueueToChannel = messageQueue; }
void setImageWorkerMessageQueue(MessageQueue *messageQueue) { m_imageWorkerMessageQueue = messageQueue; }
double getMagSq() const { return m_magsq; }
@ -103,7 +103,7 @@ private:
int m_magsqCount;
MagSqLevelsStore m_magSqLevelStore;
MessageQueue *m_messageQueueToChannel;
MessageQueue *m_imageWorkerMessageQueue;
MovingAverageUtil<Real, double, 16> m_movingAverage;
@ -120,7 +120,7 @@ private:
int m_zenith; // Row number of Zenith
void processOneSample(Complex &ci);
MessageQueue *getMessageQueueToChannel() { return m_messageQueueToChannel; }
MessageQueue *getImageWorkerMessageQueue() { return m_imageWorkerMessageQueue; }
};
#endif // INCLUDE_APTDEMODSINK_H