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

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 }