1 /*
2     SPDX-FileCopyrightText: 2016 Jasem Mutlaq <mutlaqja@ikarustech.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "darkprocessor.h"
8 #include "darklibrary.h"
9 
10 #include <array>
11 
12 #include "ekos_debug.h"
13 
14 namespace Ekos
15 {
16 
DarkProcessor(QObject * parent)17 DarkProcessor::DarkProcessor(QObject *parent) : QObject(parent)
18 {
19     connect(&m_Watcher, &QFutureWatcher<bool>::finished, this, [this]()
20     {
21         emit darkFrameCompleted(m_Watcher.result());
22     });
23 
24 }
25 
26 ///////////////////////////////////////////////////////////////////////////////////////
27 ///
28 ///////////////////////////////////////////////////////////////////////////////////////
normalizeDefects(const QSharedPointer<DefectMap> & defectMap,const QSharedPointer<FITSData> & lightData,uint16_t offsetX,uint16_t offsetY)29 void DarkProcessor::normalizeDefects(const QSharedPointer<DefectMap> &defectMap, const QSharedPointer<FITSData> &lightData,
30                                      uint16_t offsetX, uint16_t offsetY)
31 {
32     switch (lightData->dataType())
33     {
34         case TBYTE:
35             normalizeDefectsInternal<uint8_t>(defectMap, lightData, offsetX, offsetY);
36             break;
37 
38         case TSHORT:
39             normalizeDefectsInternal<int16_t>(defectMap, lightData, offsetX, offsetY);
40             break;
41 
42         case TUSHORT:
43             normalizeDefectsInternal<uint16_t>(defectMap, lightData, offsetX, offsetY);
44             break;
45 
46         case TLONG:
47             normalizeDefectsInternal<int32_t>(defectMap, lightData, offsetX, offsetY);
48             break;
49 
50         case TULONG:
51             normalizeDefectsInternal<uint32_t>(defectMap, lightData, offsetX, offsetY);
52             break;
53 
54         case TFLOAT:
55             normalizeDefectsInternal<float>(defectMap, lightData, offsetX, offsetY);
56             break;
57 
58         case TLONGLONG:
59             normalizeDefectsInternal<int64_t>(defectMap, lightData, offsetX, offsetY);
60             break;
61 
62         case TDOUBLE:
63             normalizeDefectsInternal<double>(defectMap, lightData, offsetX, offsetY);
64             break;
65 
66         default:
67             break;
68     }
69 }
70 
71 ///////////////////////////////////////////////////////////////////////////////////////
72 ///
73 ///////////////////////////////////////////////////////////////////////////////////////
74 template <typename T>
normalizeDefectsInternal(const QSharedPointer<DefectMap> & defectMap,const QSharedPointer<FITSData> & lightData,uint16_t offsetX,uint16_t offsetY)75 void DarkProcessor::normalizeDefectsInternal(const QSharedPointer<DefectMap> &defectMap,
76         const QSharedPointer<FITSData> &lightData, uint16_t offsetX, uint16_t offsetY)
77 {
78 
79     T *lightBuffer = reinterpret_cast<T *>(lightData->getWritableImageBuffer());
80     const uint32_t width = lightData->width();
81 
82     // Account for offset X and Y
83     // e.g. if we send a subframed light frame 100x100 pixels wide
84     // but the source defect map covers 1000x1000 pixels array, then we need to only compensate
85     // for the 100x100 region.
86     for (BadPixelSet::const_iterator onePixel = defectMap->hotThreshold();
87             onePixel != defectMap->hotPixels().cend(); ++onePixel)
88     {
89         const uint16_t x = (*onePixel).x;
90         const uint16_t y = (*onePixel).y;
91 
92         if (x <= offsetX || y <= offsetY)
93             continue;
94 
95         uint32_t offset = (x - offsetX) + (y - offsetY) * width;
96 
97         lightBuffer[offset] = median3x3Filter(x - offsetX, y - offsetY, width, lightBuffer);
98     }
99 
100     for (BadPixelSet::const_iterator onePixel = defectMap->coldPixels().cbegin();
101             onePixel != defectMap->coldThreshold(); ++onePixel)
102     {
103         const uint16_t x = (*onePixel).x;
104         const uint16_t y = (*onePixel).y;
105 
106         if (x <= offsetX || y <= offsetY)
107             continue;
108 
109         uint32_t offset = (x - offsetX) + (y - offsetY) * width;
110 
111         lightBuffer[offset] = median3x3Filter(x - offsetX, y - offsetY, width, lightBuffer);
112     }
113 
114     lightData->calculateStats(true);
115 
116 }
117 
118 ///////////////////////////////////////////////////////////////////////////////////////
119 ///
120 ///////////////////////////////////////////////////////////////////////////////////////
121 template <typename T>
median3x3Filter(uint16_t x,uint16_t y,uint32_t width,T * buffer)122 T DarkProcessor::median3x3Filter(uint16_t x, uint16_t y, uint32_t width, T *buffer)
123 {
124     T *top = buffer + (y - 1) * width + (x - 1);
125     T *mid = buffer + (y - 0) * width + (x - 1);
126     T *bot = buffer + (y + 1) * width + (x - 1);
127 
128     std::array<T, 8> elements;
129 
130     // Top
131     elements[0] = *(top + 0);
132     elements[1] = *(top + 1);
133     elements[2] = *(top + 2);
134     // Mid
135     elements[3] = *(mid + 0);
136     // Mid+1 is the defective value, so we skip and go for + 2
137     elements[4] = *(mid + 2);
138     // Bottom
139     elements[5] = *(bot + 0);
140     elements[6] = *(bot + 1);
141     elements[7] = *(bot + 2);
142 
143     std::sort(elements.begin(), elements.end());
144     auto median = (elements[3] + elements[4]) / 2;
145     return median;
146 }
147 
148 ///////////////////////////////////////////////////////////////////////////////////////
149 ///
150 ///////////////////////////////////////////////////////////////////////////////////////
subtractDarkData(const QSharedPointer<FITSData> & darkData,const QSharedPointer<FITSData> & lightData,uint16_t offsetX,uint16_t offsetY)151 void DarkProcessor::subtractDarkData(const QSharedPointer<FITSData> &darkData, const QSharedPointer<FITSData> &lightData,
152                                      uint16_t offsetX, uint16_t offsetY)
153 {
154     switch (darkData->dataType())
155     {
156         case TBYTE:
157             subtractInternal<uint8_t>(darkData, lightData, offsetX, offsetY);
158             break;
159 
160         case TSHORT:
161             subtractInternal<int16_t>(darkData, lightData, offsetX, offsetY);
162             break;
163 
164         case TUSHORT:
165             subtractInternal<uint16_t>(darkData, lightData, offsetX, offsetY);
166             break;
167 
168         case TLONG:
169             subtractInternal<int32_t>(darkData, lightData, offsetX, offsetY);
170             break;
171 
172         case TULONG:
173             subtractInternal<uint32_t>(darkData, lightData, offsetX, offsetY);
174             break;
175 
176         case TFLOAT:
177             subtractInternal<float>(darkData, lightData, offsetX, offsetY);
178             break;
179 
180         case TLONGLONG:
181             subtractInternal<int64_t>(darkData, lightData, offsetX, offsetY);
182             break;
183 
184         case TDOUBLE:
185             subtractInternal<double>(darkData, lightData, offsetX, offsetY);
186             break;
187 
188         default:
189             break;
190     }
191 }
192 
193 ///////////////////////////////////////////////////////////////////////////////////////
194 ///
195 ///////////////////////////////////////////////////////////////////////////////////////
196 template <typename T>
subtractInternal(const QSharedPointer<FITSData> & darkData,const QSharedPointer<FITSData> & lightData,uint16_t offsetX,uint16_t offsetY)197 void DarkProcessor::subtractInternal(const QSharedPointer<FITSData> &darkData, const QSharedPointer<FITSData> &lightData,
198                                      uint16_t offsetX, uint16_t offsetY)
199 {
200     const uint32_t width = lightData->width();
201     const uint32_t height = lightData->height();
202     T *lightBuffer = reinterpret_cast<T *>(lightData->getWritableImageBuffer());
203 
204     const uint32_t darkStride = darkData->width();
205     const uint32_t darkoffset = offsetX + offsetY * darkStride;
206     T const *darkBuffer  = reinterpret_cast<T const*>(darkData->getImageBuffer()) + darkoffset;
207 
208     for (uint32_t y = 0; y < height; y++)
209     {
210         for (uint32_t x = 0; x < width; x++)
211             lightBuffer[x] = (lightBuffer[x] > darkBuffer[x]) ? (lightBuffer[x] - darkBuffer[x]) : 0;
212 
213         lightBuffer += width;
214         darkBuffer += darkStride;
215     }
216 
217     lightData->calculateStats(true);
218 }
219 
220 ///////////////////////////////////////////////////////////////////////////////////////
221 ///
222 ///////////////////////////////////////////////////////////////////////////////////////
denoise(ISD::CCDChip * m_TargetChip,const QSharedPointer<FITSData> & targetData,double duration,uint16_t offsetX,uint16_t offsetY)223 void DarkProcessor::denoise(ISD::CCDChip *m_TargetChip, const QSharedPointer<FITSData> &targetData,
224                             double duration, uint16_t offsetX, uint16_t offsetY)
225 {
226     info = {m_TargetChip, targetData, duration, offsetX, offsetY};
227     QFuture<bool> result = QtConcurrent::run(this, &DarkProcessor::denoiseInternal);
228     m_Watcher.setFuture(result);
229 }
230 
231 ///////////////////////////////////////////////////////////////////////////////////////
232 ///
233 ///////////////////////////////////////////////////////////////////////////////////////
denoiseInternal()234 bool DarkProcessor::denoiseInternal()
235 {
236     const QString device = info.targetChip->getCCD()->getDeviceName();
237 
238     // Check if we have preference for defect map
239     // If yes, check if defect map exists
240     // If not, we check if we have regular dark frame as backup.
241     if (DarkLibrary::Instance()->cameraHasDefectMaps(device))
242     {
243         QSharedPointer<DefectMap> targetDefectMap;
244         if (DarkLibrary::Instance()->findDefectMap(info.targetChip, info.duration, targetDefectMap))
245         {
246             normalizeDefects(targetDefectMap, info.targetData, info.offsetX, info.offsetY);
247             qCDebug(KSTARS_EKOS) << "Defect map denoising applied";
248             return true;
249         }
250     }
251 
252     // Check if we have valid dark data and then use it.
253     QSharedPointer<FITSData> darkData;
254     if (DarkLibrary::Instance()->findDarkFrame(info.targetChip, info.duration, darkData))
255     {
256         // Make sure it's the same dimension
257         if (info.targetData->width() != darkData->width() || info.targetData->height() != darkData->height())
258         {
259             darkData.clear();
260             emit newLog(i18n("No suitable dark frames or defect maps found. Please run the Dark Library wizard in Capture module."));
261             return false;
262         }
263 
264         subtractDarkData(darkData, info.targetData, info.offsetX, info.offsetY);
265         qCDebug(KSTARS_EKOS) << "Dark frame subtraction applied";
266         return true;
267     }
268 
269     emit newLog(i18n("No suitable dark frames or defect maps found. Please run the Dark Library wizard in Capture module."));
270     return false;
271 }
272 
273 }
274