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