Kstars

darkprocessor.cpp
1 /*
2  SPDX-FileCopyrightText: 2016 Jasem Mutlaq <[email protected]>
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 
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 ///////////////////////////////////////////////////////////////////////////////////////
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>
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>
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 ///////////////////////////////////////////////////////////////////////////////////////
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>
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 ///////////////////////////////////////////////////////////////////////////////////////
223 void DarkProcessor::denoise(ISD::CameraChip *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 ///////////////////////////////////////////////////////////////////////////////////////
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 if there is no offset
257  if (info.offsetX == 0 && info.offsetY == 0 &&
258  (info.targetData->width() != darkData->width() || info.targetData->height() != darkData->height()))
259  {
260  darkData.clear();
261  emit newLog(i18n("No suitable dark frames or defect maps found. Please run the Dark Library wizard in Capture module."));
262  return false;
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 }
QFuture< T > run(Function function,...)
Ekos is an advanced Astrophotography tool for Linux. It is based on a modular extensible framework to...
Definition: align.cpp:70
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QString i18n(const char *text, const TYPE &arg...)
void setFuture(const QFuture< T > &future)
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Fri Aug 12 2022 04:00:53 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.