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 #include "ekos/auxiliary/opticaltrainsettings.h"
10 
11 #include <array>
12 
13 #include "ekos_debug.h"
14 
15 namespace Ekos
16 {
17 
18 DarkProcessor::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 ///////////////////////////////////////////////////////////////////////////////////////
30 void 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 ///////////////////////////////////////////////////////////////////////////////////////
75 template <typename T>
76 void 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 ///////////////////////////////////////////////////////////////////////////////////////
122 template <typename T>
123 T 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 ///////////////////////////////////////////////////////////////////////////////////////
152 void 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 ///////////////////////////////////////////////////////////////////////////////////////
197 template <typename T>
198 void 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 ///////////////////////////////////////////////////////////////////////////////////////
224 void 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 
236  QFuture<bool> result = QtConcurrent::run(this, &DarkProcessor::denoiseInternal, useDefect);
237  m_Watcher.setFuture(result);
238 }
239 
240 ///////////////////////////////////////////////////////////////////////////////////////
241 ///
242 ///////////////////////////////////////////////////////////////////////////////////////
243 bool DarkProcessor::denoiseInternal(bool useDefect)
244 {
245  // Check if we have preference for defect map
246  // If yes, check if defect map exists
247  // If not, we check if we have regular dark frame as backup.
248  if (useDefect)
249  {
250  QSharedPointer<DefectMap> targetDefectMap;
251  if (DarkLibrary::Instance()->findDefectMap(info.targetChip, info.duration, targetDefectMap))
252  {
253  normalizeDefects(targetDefectMap, info.targetData, info.offsetX, info.offsetY);
254  qCDebug(KSTARS_EKOS) << "Defect map denoising applied";
255  return true;
256  }
257  }
258 
259  // Check if we have valid dark data and then use it.
260  QSharedPointer<FITSData> darkData;
261  if (DarkLibrary::Instance()->findDarkFrame(info.targetChip, info.duration, darkData))
262  {
263  // Make sure it's the same dimension if there is no offset
264  if (info.offsetX == 0 && info.offsetY == 0 &&
265  (info.targetData->width() != darkData->width() || info.targetData->height() != darkData->height()))
266  {
267  darkData.clear();
268  emit newLog(i18n("No suitable dark frames or defect maps found. Please run the Dark Library wizard in Capture module."));
269  return false;
270  }
271  subtractDarkData(darkData, info.targetData, info.offsetX, info.offsetY);
272  qCDebug(KSTARS_EKOS) << "Dark frame subtraction applied";
273  return true;
274  }
275 
276  emit newLog(i18n("No suitable dark frames or defect maps found. Please run the Dark Library wizard in Capture module."));
277  return false;
278 }
279 
280 }
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:76
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-2023 The KDE developers.
Generated on Sun Dec 3 2023 04:06:15 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.