File indexing completed on 2024-04-28 15:09:09

0001 /*
0002     SPDX-FileCopyrightText: 2016 Jasem Mutlaq <mutlaqja@ikarustech.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "darkprocessor.h"
0008 #include "darklibrary.h"
0009 #include "ekos/auxiliary/opticaltrainsettings.h"
0010 
0011 #include <array>
0012 
0013 #include "ekos_debug.h"
0014 
0015 namespace Ekos
0016 {
0017 
0018 DarkProcessor::DarkProcessor(QObject *parent) : QObject(parent)
0019 {
0020     connect(&m_Watcher, &QFutureWatcher<bool>::finished, this, [this]()
0021     {
0022         emit darkFrameCompleted(m_Watcher.result());
0023     });
0024 
0025 }
0026 
0027 ///////////////////////////////////////////////////////////////////////////////////////
0028 ///
0029 ///////////////////////////////////////////////////////////////////////////////////////
0030 void DarkProcessor::normalizeDefects(const QSharedPointer<DefectMap> &defectMap, const QSharedPointer<FITSData> &lightData,
0031                                      uint16_t offsetX, uint16_t offsetY)
0032 {
0033     switch (lightData->dataType())
0034     {
0035         case TBYTE:
0036             normalizeDefectsInternal<uint8_t>(defectMap, lightData, offsetX, offsetY);
0037             break;
0038 
0039         case TSHORT:
0040             normalizeDefectsInternal<int16_t>(defectMap, lightData, offsetX, offsetY);
0041             break;
0042 
0043         case TUSHORT:
0044             normalizeDefectsInternal<uint16_t>(defectMap, lightData, offsetX, offsetY);
0045             break;
0046 
0047         case TLONG:
0048             normalizeDefectsInternal<int32_t>(defectMap, lightData, offsetX, offsetY);
0049             break;
0050 
0051         case TULONG:
0052             normalizeDefectsInternal<uint32_t>(defectMap, lightData, offsetX, offsetY);
0053             break;
0054 
0055         case TFLOAT:
0056             normalizeDefectsInternal<float>(defectMap, lightData, offsetX, offsetY);
0057             break;
0058 
0059         case TLONGLONG:
0060             normalizeDefectsInternal<int64_t>(defectMap, lightData, offsetX, offsetY);
0061             break;
0062 
0063         case TDOUBLE:
0064             normalizeDefectsInternal<double>(defectMap, lightData, offsetX, offsetY);
0065             break;
0066 
0067         default:
0068             break;
0069     }
0070 }
0071 
0072 ///////////////////////////////////////////////////////////////////////////////////////
0073 ///
0074 ///////////////////////////////////////////////////////////////////////////////////////
0075 template <typename T>
0076 void DarkProcessor::normalizeDefectsInternal(const QSharedPointer<DefectMap> &defectMap,
0077         const QSharedPointer<FITSData> &lightData, uint16_t offsetX, uint16_t offsetY)
0078 {
0079 
0080     T *lightBuffer = reinterpret_cast<T *>(lightData->getWritableImageBuffer());
0081     const uint32_t width = lightData->width();
0082 
0083     // Account for offset X and Y
0084     // e.g. if we send a subframed light frame 100x100 pixels wide
0085     // but the source defect map covers 1000x1000 pixels array, then we need to only compensate
0086     // for the 100x100 region.
0087     for (BadPixelSet::const_iterator onePixel = defectMap->hotThreshold();
0088             onePixel != defectMap->hotPixels().cend(); ++onePixel)
0089     {
0090         const uint16_t x = (*onePixel).x;
0091         const uint16_t y = (*onePixel).y;
0092 
0093         if (x <= offsetX || y <= offsetY)
0094             continue;
0095 
0096         uint32_t offset = (x - offsetX) + (y - offsetY) * width;
0097 
0098         lightBuffer[offset] = median3x3Filter(x - offsetX, y - offsetY, width, lightBuffer);
0099     }
0100 
0101     for (BadPixelSet::const_iterator onePixel = defectMap->coldPixels().cbegin();
0102             onePixel != defectMap->coldThreshold(); ++onePixel)
0103     {
0104         const uint16_t x = (*onePixel).x;
0105         const uint16_t y = (*onePixel).y;
0106 
0107         if (x <= offsetX || y <= offsetY)
0108             continue;
0109 
0110         uint32_t offset = (x - offsetX) + (y - offsetY) * width;
0111 
0112         lightBuffer[offset] = median3x3Filter(x - offsetX, y - offsetY, width, lightBuffer);
0113     }
0114 
0115     lightData->calculateStats(true);
0116 
0117 }
0118 
0119 ///////////////////////////////////////////////////////////////////////////////////////
0120 ///
0121 ///////////////////////////////////////////////////////////////////////////////////////
0122 template <typename T>
0123 T DarkProcessor::median3x3Filter(uint16_t x, uint16_t y, uint32_t width, T *buffer)
0124 {
0125     T *top = buffer + (y - 1) * width + (x - 1);
0126     T *mid = buffer + (y - 0) * width + (x - 1);
0127     T *bot = buffer + (y + 1) * width + (x - 1);
0128 
0129     std::array<T, 8> elements;
0130 
0131     // Top
0132     elements[0] = *(top + 0);
0133     elements[1] = *(top + 1);
0134     elements[2] = *(top + 2);
0135     // Mid
0136     elements[3] = *(mid + 0);
0137     // Mid+1 is the defective value, so we skip and go for + 2
0138     elements[4] = *(mid + 2);
0139     // Bottom
0140     elements[5] = *(bot + 0);
0141     elements[6] = *(bot + 1);
0142     elements[7] = *(bot + 2);
0143 
0144     std::sort(elements.begin(), elements.end());
0145     auto median = (elements[3] + elements[4]) / 2;
0146     return median;
0147 }
0148 
0149 ///////////////////////////////////////////////////////////////////////////////////////
0150 ///
0151 ///////////////////////////////////////////////////////////////////////////////////////
0152 void DarkProcessor::subtractDarkData(const QSharedPointer<FITSData> &darkData, const QSharedPointer<FITSData> &lightData,
0153                                      uint16_t offsetX, uint16_t offsetY)
0154 {
0155     switch (darkData->dataType())
0156     {
0157         case TBYTE:
0158             subtractInternal<uint8_t>(darkData, lightData, offsetX, offsetY);
0159             break;
0160 
0161         case TSHORT:
0162             subtractInternal<int16_t>(darkData, lightData, offsetX, offsetY);
0163             break;
0164 
0165         case TUSHORT:
0166             subtractInternal<uint16_t>(darkData, lightData, offsetX, offsetY);
0167             break;
0168 
0169         case TLONG:
0170             subtractInternal<int32_t>(darkData, lightData, offsetX, offsetY);
0171             break;
0172 
0173         case TULONG:
0174             subtractInternal<uint32_t>(darkData, lightData, offsetX, offsetY);
0175             break;
0176 
0177         case TFLOAT:
0178             subtractInternal<float>(darkData, lightData, offsetX, offsetY);
0179             break;
0180 
0181         case TLONGLONG:
0182             subtractInternal<int64_t>(darkData, lightData, offsetX, offsetY);
0183             break;
0184 
0185         case TDOUBLE:
0186             subtractInternal<double>(darkData, lightData, offsetX, offsetY);
0187             break;
0188 
0189         default:
0190             break;
0191     }
0192 }
0193 
0194 ///////////////////////////////////////////////////////////////////////////////////////
0195 ///
0196 ///////////////////////////////////////////////////////////////////////////////////////
0197 template <typename T>
0198 void DarkProcessor::subtractInternal(const QSharedPointer<FITSData> &darkData, const QSharedPointer<FITSData> &lightData,
0199                                      uint16_t offsetX, uint16_t offsetY)
0200 {
0201     const uint32_t width = lightData->width();
0202     const uint32_t height = lightData->height();
0203     T *lightBuffer = reinterpret_cast<T *>(lightData->getWritableImageBuffer());
0204 
0205     const uint32_t darkStride = darkData->width();
0206     const uint32_t darkoffset = offsetX + offsetY * darkStride;
0207     T const *darkBuffer  = reinterpret_cast<T const*>(darkData->getImageBuffer()) + darkoffset;
0208 
0209     for (uint32_t y = 0; y < height; y++)
0210     {
0211         for (uint32_t x = 0; x < width; x++)
0212             lightBuffer[x] = (lightBuffer[x] > darkBuffer[x]) ? (lightBuffer[x] - darkBuffer[x]) : 0;
0213 
0214         lightBuffer += width;
0215         darkBuffer += darkStride;
0216     }
0217 
0218     lightData->calculateStats(true);
0219 }
0220 
0221 ///////////////////////////////////////////////////////////////////////////////////////
0222 ///
0223 ///////////////////////////////////////////////////////////////////////////////////////
0224 void DarkProcessor::denoise(int trainID, ISD::CameraChip *m_TargetChip, const QSharedPointer<FITSData> &targetData,
0225                             double duration, uint16_t offsetX, uint16_t offsetY)
0226 {
0227     info = {trainID, m_TargetChip, targetData, duration, offsetX, offsetY};
0228 
0229     auto useDefect = false;
0230     // Get the train settings
0231     OpticalTrainSettings::Instance()->setOpticalTrainID(trainID);
0232     auto settings = OpticalTrainSettings::Instance()->getOneSetting(OpticalTrainSettings::DarkLibrary);
0233     if (settings.isValid())
0234         useDefect = settings.toMap().contains("preferDefectsRadio");
0235 
0236     QFuture<bool> result = QtConcurrent::run(this, &DarkProcessor::denoiseInternal, useDefect);
0237     m_Watcher.setFuture(result);
0238 }
0239 
0240 ///////////////////////////////////////////////////////////////////////////////////////
0241 ///
0242 ///////////////////////////////////////////////////////////////////////////////////////
0243 bool DarkProcessor::denoiseInternal(bool useDefect)
0244 {
0245     // Check if we have preference for defect map
0246     // If yes, check if defect map exists
0247     // If not, we check if we have regular dark frame as backup.
0248     if (useDefect)
0249     {
0250         QSharedPointer<DefectMap> targetDefectMap;
0251         if (DarkLibrary::Instance()->findDefectMap(info.targetChip, info.duration, targetDefectMap))
0252         {
0253             normalizeDefects(targetDefectMap, info.targetData, info.offsetX, info.offsetY);
0254             qCDebug(KSTARS_EKOS) << "Defect map denoising applied";
0255             return true;
0256         }
0257     }
0258 
0259     // Check if we have valid dark data and then use it.
0260     QSharedPointer<FITSData> darkData;
0261     if (DarkLibrary::Instance()->findDarkFrame(info.targetChip, info.duration, darkData))
0262     {
0263         // Make sure it's the same dimension if there is no offset
0264         if (info.offsetX == 0 && info.offsetY == 0 &&
0265                 (info.targetData->width() != darkData->width() || info.targetData->height() != darkData->height()))
0266         {
0267             darkData.clear();
0268             emit newLog(i18n("No suitable dark frames or defect maps found. Please run the Dark Library wizard in Capture module."));
0269             return false;
0270         }
0271         subtractDarkData(darkData, info.targetData, info.offsetX, info.offsetY);
0272         qCDebug(KSTARS_EKOS) << "Dark frame subtraction applied";
0273         return true;
0274     }
0275 
0276     emit newLog(i18n("No suitable dark frames or defect maps found. Please run the Dark Library wizard in Capture module."));
0277     return false;
0278 }
0279 
0280 }