mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-11-03 07:21:14 -05:00
6b6a3f66c3
Pass satellite name to image worker thread. Don't stop image worker thread when device stopped, as this prevents the image processing controls in the GUI from working.
381 lines
12 KiB
C++
381 lines
12 KiB
C++
///////////////////////////////////////////////////////////////////////////////////
|
|
// 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 <QTime>
|
|
#include <QDebug>
|
|
|
|
#include "aptdemod.h"
|
|
#include "aptdemodimageworker.h"
|
|
|
|
MESSAGE_CLASS_DEFINITION(APTDemodImageWorker::MsgConfigureAPTDemodImageWorker, Message)
|
|
MESSAGE_CLASS_DEFINITION(APTDemodImageWorker::MsgSaveImageToDisk, Message)
|
|
MESSAGE_CLASS_DEFINITION(APTDemodImageWorker::MsgSetSatelliteName, 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 (MsgSaveImageToDisk::match(cmd))
|
|
{
|
|
saveImageToDisk();
|
|
return true;
|
|
}
|
|
else if (MsgSetSatelliteName::match(cmd))
|
|
{
|
|
MsgSetSatelliteName& msg = (MsgSetSatelliteName&) cmd;
|
|
m_satelliteName = msg.getSatelliteName();
|
|
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);
|
|
delete[] 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;
|
|
bool callProcessImage = false;
|
|
|
|
if ((settings.m_cropNoise != m_settings.m_cropNoise) ||
|
|
(settings.m_denoise != m_settings.m_denoise) ||
|
|
(settings.m_linearEqualise != m_settings.m_linearEqualise) ||
|
|
(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))
|
|
{
|
|
// Call after settings have been applied
|
|
callProcessImage = true;
|
|
}
|
|
|
|
m_settings = settings;
|
|
|
|
if (callProcessImage) {
|
|
sendImageToGUI();
|
|
}
|
|
}
|
|
|
|
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]);
|
|
|
|
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();
|
|
}
|
|
|
|
m_image.nrow++;
|
|
}
|
|
|
|
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));
|
|
}
|
|
}
|
|
|
|
void APTDemodImageWorker::sendLineToGUI()
|
|
{
|
|
if (m_messageQueueToGUI)
|
|
{
|
|
float *pixels = m_image.prow[m_image.nrow];
|
|
uchar *line;
|
|
APTDemod::MsgLine *msg = APTDemod::MsgLine::create(&line);
|
|
|
|
if (m_settings.m_channels == APTDemodSettings::BOTH_CHANNELS)
|
|
{
|
|
for (int i = 0; i < APT_IMG_WIDTH; i++) {
|
|
line[i] = roundAndClip(pixels[i]);
|
|
}
|
|
msg->setSize(APT_IMG_WIDTH);
|
|
}
|
|
else if (m_settings.m_channels == APTDemodSettings::CHANNEL_A)
|
|
{
|
|
for (int i = 0; i < APT_CH_WIDTH; i++) {
|
|
line[i] = roundAndClip(pixels[i + APT_CHA_OFFSET]);
|
|
}
|
|
msg->setSize(APT_CH_WIDTH);
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < APT_CH_WIDTH; i++) {
|
|
line[i] = roundAndClip(pixels[i + APT_CHB_OFFSET]);
|
|
}
|
|
msg->setSize(APT_CH_WIDTH);
|
|
}
|
|
|
|
m_messageQueueToGUI->push(msg);
|
|
}
|
|
}
|
|
|
|
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::saveImageToDisk()
|
|
{
|
|
QStringList imageTypes;
|
|
QImage image = processImage(imageTypes);
|
|
|
|
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"));
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (!image.save(filename)) {
|
|
qCritical() << "Failed to save APT image to: " << filename;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|