File indexing completed on 2024-04-28 03:43:07
0001 /* 0002 SPDX-FileCopyrightText: 2021 Jasem Mutlaq <mutlaqja@ikarustech.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "defectmap.h" 0008 #include <QJsonDocument> 0009 0010 ////////////////////////////////////////////////////////////////////////////// 0011 /// 0012 ////////////////////////////////////////////////////////////////////////////// 0013 QJsonObject BadPixel::json() const 0014 { 0015 QJsonObject object 0016 { 0017 {"x", x}, 0018 {"y", y}, 0019 {"value", value} 0020 }; 0021 return object; 0022 } 0023 0024 ////////////////////////////////////////////////////////////////////////////// 0025 /// 0026 ////////////////////////////////////////////////////////////////////////////// 0027 DefectMap::DefectMap() : QObject() 0028 { 0029 0030 } 0031 0032 ////////////////////////////////////////////////////////////////////////////// 0033 /// 0034 ////////////////////////////////////////////////////////////////////////////// 0035 bool DefectMap::load(const QString &filename) 0036 { 0037 QFile input(filename); 0038 if (!input.open(QIODevice::ReadOnly)) 0039 return false; 0040 0041 QJsonParseError parserError; 0042 auto doc = QJsonDocument::fromJson(input.readAll(), &parserError); 0043 if (parserError.error != QJsonParseError::NoError) 0044 return false; 0045 0046 m_Filename = filename; 0047 0048 auto root = doc.object(); 0049 0050 m_Camera = root["camera"].toString(); 0051 m_Median = root["median"].toDouble(); 0052 m_StandardDeviation = root["standardDeviation"].toDouble(); 0053 m_HotPixelsAggressiveness = root["hotAggressiveness"].toInt(); 0054 m_ColdPixelsAggressiveness = root["coldAggressiveness"].toInt(); 0055 0056 QJsonArray hot = root["hot"].toArray(); 0057 QJsonArray cold = root["cold"].toArray(); 0058 0059 m_HotPixels.clear(); 0060 m_ColdPixels.clear(); 0061 0062 for (const auto &onePixel : qAsConst(hot)) 0063 { 0064 QJsonObject oneObject = onePixel.toObject(); 0065 m_HotPixels.insert(BadPixel(oneObject["x"].toInt(), oneObject["y"].toInt(), oneObject["value"].toDouble())); 0066 } 0067 0068 m_HotPixelsCount = m_HotPixels.size(); 0069 0070 for (const auto &onePixel : qAsConst(cold)) 0071 { 0072 QJsonObject oneObject = onePixel.toObject(); 0073 m_ColdPixels.insert(BadPixel(oneObject["x"].toInt(), oneObject["y"].toInt(), oneObject["value"].toDouble())); 0074 } 0075 0076 m_ColdPixelsCount = m_ColdPixels.size(); 0077 return true; 0078 } 0079 0080 ////////////////////////////////////////////////////////////////////////////// 0081 /// 0082 ////////////////////////////////////////////////////////////////////////////// 0083 bool DefectMap::save(const QString &filename, const QString &camera) 0084 { 0085 QJsonObject root; 0086 0087 root.insert("camera", camera); 0088 root.insert("median", m_Median); 0089 root.insert("standardDeviation", m_StandardDeviation); 0090 root.insert("hotAggressiveness", m_HotPixelsAggressiveness); 0091 root.insert("coldAggressiveness", m_ColdPixelsAggressiveness); 0092 0093 QJsonArray hotArray, coldArray; 0094 if (m_HotEnabled) 0095 { 0096 for (const auto &onePixel : m_HotPixels) 0097 hotArray.append(onePixel.json()); 0098 } 0099 0100 if (m_ColdEnabled) 0101 { 0102 for (const auto &onePixel : m_ColdPixels) 0103 coldArray.append(onePixel.json()); 0104 } 0105 0106 root.insert("hot", hotArray); 0107 root.insert("cold", coldArray); 0108 0109 QFile output(filename); 0110 0111 if (!output.open(QIODevice::WriteOnly)) 0112 return false; 0113 0114 m_Filename = filename; 0115 output.write(QJsonDocument(root).toJson()); 0116 output.close(); 0117 0118 return true; 0119 } 0120 0121 ////////////////////////////////////////////////////////////////////////////// 0122 /// 0123 ////////////////////////////////////////////////////////////////////////////// 0124 double DefectMap::calculateSigma(uint8_t aggressiveness) 0125 { 0126 // Estimated power law fitting to compress values within 0.25 to 9.4 sigma range 0127 return 18.61742934980 * exp(-0.00052422221 * (aggressiveness - 9.89915467884) * (aggressiveness - 9.89915467884)); 0128 } 0129 0130 ////////////////////////////////////////////////////////////////////////////// 0131 /// 0132 ////////////////////////////////////////////////////////////////////////////// 0133 void DefectMap::setDarkData(const QSharedPointer<FITSData> &data) 0134 { 0135 m_DarkData = data; 0136 m_Median = data->getMedian(0); 0137 m_StandardDeviation = data->getStdDev(0); 0138 if (m_HotPixels.empty() && m_ColdPixels.empty()) 0139 initBadPixels(); 0140 else 0141 filterPixels(); 0142 } 0143 0144 ////////////////////////////////////////////////////////////////////////////// 0145 /// 0146 ////////////////////////////////////////////////////////////////////////////// 0147 double DefectMap::getHotThreshold(uint8_t aggressiveness) 0148 { 0149 return (m_Median + m_StandardDeviation * calculateSigma(aggressiveness)); 0150 } 0151 0152 ////////////////////////////////////////////////////////////////////////////// 0153 /// 0154 ////////////////////////////////////////////////////////////////////////////// 0155 double DefectMap::getColdThreshold(uint8_t aggressiveness) 0156 { 0157 return (m_Median - m_StandardDeviation * calculateSigma(aggressiveness)); 0158 } 0159 0160 ////////////////////////////////////////////////////////////////////////////// 0161 /// 0162 ////////////////////////////////////////////////////////////////////////////// 0163 void DefectMap::initBadPixels() 0164 { 0165 double hotPixelThreshold = getHotThreshold(100); 0166 double coldPixelThreshold = getColdThreshold(100); 0167 0168 m_ColdPixels.clear(); 0169 m_HotPixels.clear(); 0170 0171 switch (m_DarkData->dataType()) 0172 { 0173 case TBYTE: 0174 initBadPixelsInternal<uint8_t>(hotPixelThreshold, coldPixelThreshold); 0175 break; 0176 0177 case TSHORT: 0178 initBadPixelsInternal<int16_t>(hotPixelThreshold, coldPixelThreshold); 0179 break; 0180 0181 case TUSHORT: 0182 initBadPixelsInternal<uint16_t>(hotPixelThreshold, coldPixelThreshold); 0183 break; 0184 0185 case TLONG: 0186 initBadPixelsInternal<int32_t>(hotPixelThreshold, coldPixelThreshold); 0187 break; 0188 0189 case TULONG: 0190 initBadPixelsInternal<uint32_t>(hotPixelThreshold, coldPixelThreshold); 0191 break; 0192 0193 case TFLOAT: 0194 initBadPixelsInternal<float>(hotPixelThreshold, coldPixelThreshold); 0195 break; 0196 0197 case TLONGLONG: 0198 initBadPixelsInternal<int64_t>(hotPixelThreshold, coldPixelThreshold); 0199 break; 0200 0201 case TDOUBLE: 0202 initBadPixelsInternal<double>(hotPixelThreshold, coldPixelThreshold); 0203 break; 0204 0205 default: 0206 break; 0207 } 0208 } 0209 0210 ////////////////////////////////////////////////////////////////////////////// 0211 /// 0212 ////////////////////////////////////////////////////////////////////////////// 0213 template <typename T> 0214 void DefectMap::initBadPixelsInternal(double hotPixelThreshold, 0215 double coldPixelThreshold) 0216 { 0217 T const *buffer = reinterpret_cast<T const*>(m_DarkData->getImageBuffer()); 0218 const uint32_t width = m_DarkData->width(); 0219 const uint32_t height = m_DarkData->height(); 0220 0221 const uint32_t maxSize = 500000; 0222 uint32_t samples = m_DarkData->samplesPerChannel(); 0223 uint8_t downsample = 1; 0224 if (samples > maxSize) 0225 { 0226 downsample = (static_cast<double>(samples) / maxSize) + 0.999; 0227 samples /= downsample; 0228 } 0229 0230 // Need templated function 0231 for (uint32_t y = 4; y < height - 4; y += downsample) 0232 { 0233 for (uint32_t x = 4; x < width - 4; x += downsample) 0234 { 0235 uint32_t offset = x + y * width; 0236 if (buffer[offset] > hotPixelThreshold) 0237 m_HotPixels.insert(BadPixel(x, y, buffer[offset])); 0238 else if (buffer[offset] < coldPixelThreshold) 0239 m_ColdPixels.insert(BadPixel(x, y, buffer[offset])); 0240 } 0241 } 0242 0243 filterPixels(); 0244 } 0245 0246 ////////////////////////////////////////////////////////////////////////////// 0247 /// 0248 ////////////////////////////////////////////////////////////////////////////// 0249 void DefectMap::filterPixels() 0250 { 0251 double hotPixelThreshold = getHotThreshold(m_HotPixelsAggressiveness); 0252 double coldPixelThreshold = getColdThreshold(m_ColdPixelsAggressiveness); 0253 0254 m_HotPixelsThreshold = m_HotPixels.lower_bound(BadPixel(0, 0, hotPixelThreshold)); 0255 m_ColdPixelsThreshold = m_ColdPixels.lower_bound(BadPixel(0, 0, coldPixelThreshold)); 0256 0257 if (m_HotPixelsThreshold == m_HotPixels.cend()) 0258 m_HotPixelsCount = 0; 0259 else 0260 m_HotPixelsCount = std::distance(m_HotPixelsThreshold, m_HotPixels.cend()); 0261 0262 if (m_ColdPixelsThreshold == m_ColdPixels.cbegin()) 0263 m_ColdPixelsCount = 0; 0264 else 0265 m_ColdPixelsCount = std::distance(m_ColdPixels.cbegin(), m_ColdPixelsThreshold); 0266 0267 emit pixelsUpdated(m_HotPixelsCount, m_ColdPixelsCount); 0268 } 0269 0270 ////////////////////////////////////////////////////////////////////////////// 0271 /// 0272 ////////////////////////////////////////////////////////////////////////////// 0273 void DefectMap::setHotEnabled(bool enabled) 0274 { 0275 m_HotEnabled = enabled; 0276 emit pixelsUpdated(m_HotEnabled ? m_HotPixelsCount : 0, m_ColdPixelsCount); 0277 } 0278 0279 ////////////////////////////////////////////////////////////////////////////// 0280 /// 0281 ////////////////////////////////////////////////////////////////////////////// 0282 void DefectMap::setColdEnabled(bool enabled) 0283 { 0284 m_ColdEnabled = enabled; 0285 emit pixelsUpdated(m_HotPixelsCount, m_ColdEnabled ? m_ColdPixelsCount : 0); 0286 }