Kstars

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

KDE's Doxygen guidelines are available online.