File indexing completed on 2024-04-28 03:43:06
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 }