Kstars

optimalsubexposurecalculator.cpp
1/*
2 SPDX-FileCopyrightText: 2023 Joseph McGee <joseph.mcgee@sbcglobal.net>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include <QLoggingCategory>
8#include <cmath>
9#include "optimalsubexposurecalculator.h"
10#include "imagingcameradata.h"
11#include "calculatedgainsubexposuretime.h"
12#include "cameraexposureenvelope.h"
13#include "optimalexposuredetail.h"
14#include <ekos_capture_debug.h>
15
16namespace OptimalExposure
17{
18
19/*
20 * For UI presentation provide two calculation methods.
21 * 1) A method for the overall calculation for
22 * exposure times over the range of gain read-noise pairs.
23 * This method would be called when changes are made to:
24 * Camera selection, Focal Ratio, Sky Quality, Filter Compensation, or Noise Tolerance
25 * This method will return an obect (CameraExposureEnvelope) containing (for ui presentation)
26 * lightPollutionElectronBaseRate, lightPollutionForOpticFocalRatio,
27 * a vector of gain sub-exposure pairs, and int values the max and min exposure time.
28 * This method is intented to be used to present the graphical display of exposures for the camera,
29 * based on a specifc SQM. The SQM can be adjusted in the UI, and an this method will be used to refresh ui.
30 *
31 * 2) A method for a calculation of an exposure time (OptimalExposureDetail) at a specific gain.
32 * This method is intented to be used to present a specific exposure calculation, and
33 * resulting shot and stack noise information. This method will interpolate for a
34 * read-noise value (between camera gain read-noise pairs), and will be called when
35 * a change is made to selected Gain on the UI.
36 * This method will return an object that conains:
37 * Precise Exposure Time in (interpolated), Shot pollution electrons, Exposure shot noise, Exposure total noise
38 * a Vector for stacks of 1 to n hours, which includes Exposure Count, Stack Total Time, Stack total noise
39 *
40 */
41
42/*
43 *
44 * More work is needed for the calculation of effect of a Filter on exposure noise and time.
45 * Original effort (in Java) used an estimage of the spectrum bandwidth passed by a filter. For
46 * example: on broadband filters for a one shot color camera:
47 * Optolong l-Pro apprears to pass about 165 nanometers.
48 * Optolong l-Enhance apprears to pass only about 33 nanometers.
49 *
50 * In this code the filter compensation has is applied as a reducer of the calulation of the
51 * light pollution rate for the optic. For example, the filter compensation to the light pollution
52 * would be 165 / 300 for an l-Pro filter.
53 *
54 * But this filter compensatoin approach is imprecise & and does not reflect reality. It might
55 * be better to analyze a filter for its ability to block the distinct emmission lines found
56 * in light pollution:
57 *
58 * Hg 435.8, 546.1, 577, 578.1
59 * Na 598, 589.6, 615.4, 616.1
60 *
61 * And finally, the use of 300 nanometers (the bandwidth of the visible light spectrum) as the
62 * basis for filter compensation may not be accurate. An unfiltered camera sensor may be able
63 * to "see" a spectrum that is much more broad. Is an unfiltered sensor collecting more noise
64 * from beyond the range of the visible spectrum? But other discussions on web forums regarding
65 * Dr. Glovers calculations appear to use 300 as the basis for filter compensation.
66 *
67 */
68
69OptimalSubExposureCalculator::OptimalSubExposureCalculator(
70 double aNoiseTolerance, double aSkyQuality, double aFocalRatio, double aFilterCompensation,
71 ImagingCameraData &anImagingCameraData) :
72 aNoiseTolerance(aNoiseTolerance),
73 aSkyQuality(aSkyQuality),
74 aFocalRatio(aFocalRatio),
75 aFilterCompensation(aFilterCompensation),
76 anImagingCameraData(anImagingCameraData) {}
77
78
79
80double OptimalSubExposureCalculator::getASkyQuality()
81{
82 return aSkyQuality;
83}
84
85void OptimalSubExposureCalculator::setASkyQuality(double newASkyQuality)
86{
87 aSkyQuality = newASkyQuality;
88}
89
90double OptimalSubExposureCalculator::getANoiseTolerance()
91{
92 return aNoiseTolerance;
93}
94
95void OptimalSubExposureCalculator::setANoiseTolerance(double newANoiseTolerance)
96{
97 aNoiseTolerance = newANoiseTolerance;
98}
99
100double OptimalSubExposureCalculator::getAFocalRatio()
101{
102 return aFocalRatio;
103}
104
105void OptimalSubExposureCalculator::setAFocalRatio(double newAFocalRatio)
106{
107 aFocalRatio = newAFocalRatio;
108}
109
110double OptimalSubExposureCalculator::getAFilterCompensation()
111{
112 return aFilterCompensation;
113}
114
115void OptimalSubExposureCalculator::setAFilterCompensation(double newAFilterCompensation)
116{
117 aFilterCompensation = newAFilterCompensation;
118}
119
120ImagingCameraData &OptimalSubExposureCalculator::getImagingCameraData()
121{
122 return anImagingCameraData;
123}
124
125void OptimalSubExposureCalculator::setImagingCameraData(ImagingCameraData &newanImagingCameraData)
126{
127 anImagingCameraData = newanImagingCameraData;
128}
129
130int OptimalSubExposureCalculator::getASelectedGain()
131{
132 return aSelectedGain;
133}
134
135void OptimalSubExposureCalculator::setASelectedGain(int newASelectedGain)
136{
137 aSelectedGain = newASelectedGain;
138}
139
140int OptimalSubExposureCalculator::getASelectedCameraReadMode() const
141{
142 return aSelectedCameraReadMode;
143}
144
145void OptimalSubExposureCalculator::setASelectedCameraReadMode(int newASelectedCameraReadMode)
146{
147 aSelectedCameraReadMode = newASelectedCameraReadMode;
148}
149
150
151
152QVector<CalculatedGainSubExposureTime> OptimalSubExposureCalculator::calculateGainSubExposureVector(double cFactor,
153 double lightPollutionForOpticFocalRatio)
154{
155 // qCInfo(KSTARS_EKOS_CAPTURE) << "Calculating gain sub-exposure vector: ";
156 QVector<CalculatedGainSubExposureTime> aCalculatedGainSubExposureTimeVector;
157
158 OptimalExposure::CameraGainReadMode aSelectedReadMode =
159 anImagingCameraData.getCameraGainReadModeVector()[aSelectedCameraReadMode];
160 // qCInfo(KSTARS_EKOS_CAPTURE) << "\t with camera read mode: "
161 // << aSelectedReadMode.getCameraGainReadModeName();
162
163 QVector<OptimalExposure::CameraGainReadNoise> aCameraGainReadNoiseVector
164 = aSelectedReadMode.getCameraGainReadNoiseVector();
165
166 for(QVector<OptimalExposure::CameraGainReadNoise>::iterator rn = aCameraGainReadNoiseVector.begin();
167 rn != aCameraGainReadNoiseVector.end(); ++rn)
168 {
169 int aGain = rn->getGain();
170 // qCInfo(KSTARS_EKOS_CAPTURE) << "At a gain of: " << aGain;
171 double aReadNoise = rn->getReadNoise();
172 // qCInfo(KSTARS_EKOS_CAPTURE) << "\t camera read-noise is: " << aReadNoise;
173
174 // initial sub exposure.
175 double anOptimalSubExposure = 0.0;
176
177
178 switch(anImagingCameraData.getSensorType())
179 {
180 case SENSORTYPE_MONOCHROME:
181 anOptimalSubExposure = cFactor * pow(aReadNoise, 2) / lightPollutionForOpticFocalRatio;
182 break;
183
184 case SENSORTYPE_COLOR:
185 anOptimalSubExposure = (double)3.0 * cFactor * pow(aReadNoise, 2) / lightPollutionForOpticFocalRatio;
186 break;
187 }
188
189 // qCInfo(KSTARS_EKOS_CAPTURE) << "anOptimalSubExposure is: " << anOptimalSubExposure;
190
191 CalculatedGainSubExposureTime *aSubExposureTime = new CalculatedGainSubExposureTime(aGain, anOptimalSubExposure);
192
193 aCalculatedGainSubExposureTimeVector.append(*aSubExposureTime);
194
195 }
196
197 return aCalculatedGainSubExposureTimeVector;
198}
199/*
200 double OptimalSubExposureCalculator::calculateLightPolutionForOpticFocalRatio(double lightPollutionElectronBaseRate, double aFocalRatio) {
201 // double lightPollutionRateForOptic = lightPollutionElectronBaseRate * java.lang.Math.pow(anOptic.getFocalRatio(),-2);
202 return((double) (lightPollutionElectronBaseRate * pow(aFocalRatio,-2)));
203 }
204*/
205
206double OptimalSubExposureCalculator::calculateLightPolutionForOpticFocalRatio(double lightPollutionElectronBaseRate,
207 double aFocalRatio, double aFilterCompensation)
208{
209 // double lightPollutionRateForOptic =
210 // lightPollutionElectronBaseRate
211 // * java.lang.Math.pow(anOptic.getFocalRatio(),-2);
212 return(((double) (lightPollutionElectronBaseRate * pow(aFocalRatio, -2))) * aFilterCompensation);
213}
214
215double OptimalSubExposureCalculator::calculateCFactor(double aNoiseTolerance)
216{
217 // cFactor = 1/( (((100+allowableNoiseIncreasePercent)/100)^2) -1)
218
219 return((double) (1 / ( pow((((double)100 + (double) aNoiseTolerance) / (double)100), (double)2) - 1)));
220
221}
222
223double OptimalSubExposureCalculator::calculateLightPollutionElectronBaseRate(double skyQuality)
224{
225 // Conversion curve fitted from Dr Glover data
226 double base = std::stod("1.25286030612621E+27");
227 double power = (double) -19.3234809465887;
228
229 // New version of Dr Glover's function calculates the Electron rate at thet pixel level
230 // and requires pixel size in microns and QE of sensor.
231 // double baseV2 = 1009110388.7838
232 // Calculation of an initial electron rate will
233 // use a new fitted curve based upon a 1.0 micon pixel and 100% QE
234 // curve appears to be f(sqm) = 1009110388.7838 exp (-0.921471189594521 * sqm)
235 //
236
237
238 return(base * pow(skyQuality, power));
239}
240
241OptimalExposure::CameraExposureEnvelope OptimalSubExposureCalculator::calculateCameraExposureEnvelope()
242{
243 /*
244 This method calculates the exposures for each gain setting found in the camera gain readnoise table.
245 It is used to refresh the ui presentation, in prparation for a calculation of sub-exposure data with a
246 specific (probably interpolated) read-noise value.
247 */
248
249 double lightPollutionElectronBaseRate = OptimalSubExposureCalculator::calculateLightPollutionElectronBaseRate(aSkyQuality);
250 /*
251 qCInfo(KSTARS_EKOS_CAPTURE) << "Calculating CameraExposureEnvelope..."
252 << "Using Sky Quality: " << aSkyQuality
253 << "\nConverted to lightPollutionElectronBaseRate: " << lightPollutionElectronBaseRate
254 << "Using an Optical Focal Ratio: " << aFocalRatio;
255 */
256 double lightPollutionForOpticFocalRatio = calculateLightPolutionForOpticFocalRatio(lightPollutionElectronBaseRate,
257 aFocalRatio, aFilterCompensation);
258 /*
259 qCInfo(KSTARS_EKOS_CAPTURE)
260 << "\tResulting in an Light Pollution Rate for the Optic of: "
261 << lightPollutionForOpticFocalRatio;
262 */
263 // qCDebug(KSTARS_EKOS_CAPTURE) << "Using a camera Id: " << anImagingCameraData.getCameraId();
264 OptimalExposure::CameraGainReadMode aSelectedReadMode =
265 anImagingCameraData.getCameraGainReadModeVector()[aSelectedCameraReadMode];
266 /*
267 qCInfo(KSTARS_EKOS_CAPTURE) << "\t with camera read mode: "
268 << aSelectedReadMode.getCameraGainReadModeName()
269 << "\tWith a read-noise table of : "
270 << aSelectedReadMode.getCameraGainReadNoiseVector().size() << " values";
271 */
272 // qCInfo(KSTARS_EKOS_CAPTURE) << "Using a Noise Tolerance of: " << aNoiseTolerance;
273
274 double cFactor = calculateCFactor(aNoiseTolerance);
275 // qCInfo(KSTARS_EKOS_CAPTURE) << "Calculated CFactor is: " << cFactor;
276
277 QVector<CalculatedGainSubExposureTime> aSubExposureTimeVector = calculateGainSubExposureVector(cFactor,
278 lightPollutionForOpticFocalRatio);
279
280 double exposureTimeMin = 99999999999.9;
281 double exposureTimeMax = -1.0;
282 // Iterate the times to capture and store the min and max exposure times.
283 // (But the QCustomPlot can rescale, so this may be unnecessary
285 oe != aSubExposureTimeVector.end(); ++oe)
286 {
287 if(exposureTimeMin > oe->getSubExposureTime()) exposureTimeMin = oe->getSubExposureTime();
288 if(exposureTimeMax < oe->getSubExposureTime()) exposureTimeMax = oe->getSubExposureTime();
289 }
290
291 CameraExposureEnvelope aCameraExposureEnvelope = CameraExposureEnvelope(
292 lightPollutionElectronBaseRate,
293 lightPollutionForOpticFocalRatio,
294 aSubExposureTimeVector,
295 exposureTimeMin,
296 exposureTimeMax);
297
298 return(aCameraExposureEnvelope);
299}
300
301// OptimalExposure::OptimalExposureDetail OptimalSubExposureCalculator::calculateSubExposureDetail(int aSelectedGainValue)
302OptimalExposure::OptimalExposureDetail OptimalSubExposureCalculator::calculateSubExposureDetail()
303{
304 /*
305 This method calculates some details for a sub-exposure. It will interpolate between
306 points in the camera read-noise table, to find a reasonable appoximation of the read-noise
307 for a non-listed gain value.
308 */
309
310 // qCDebug(KSTARS_EKOS_CAPTURE) << "aSelectedGainValue: " << aSelectedGainValue;
311
312 int aSelectedGain = getASelectedGain();
313
314 // Look for a matching gain from the camera gain read-noise table, or identify a bracket for interpolation.
315 // Interpolation may result in slight errors when the read-noise data is curve. (there's probably a better way to code this)
316 double aReadNoise = 100.0; // defaulted high just to make an error in the interpolation obvious
317 bool matched = false;
318 int lowerReadNoiseIndex = 0;
319
320 OptimalExposure::CameraGainReadMode aSelectedReadMode =
321 anImagingCameraData.getCameraGainReadModeVector()[aSelectedCameraReadMode];
322
323 QVector<OptimalExposure::CameraGainReadNoise> aCameraGainReadNoiseVector =
324 aSelectedReadMode.getCameraGainReadNoiseVector();
325
326
327
328 for(int readNoiseIndex = 0; readNoiseIndex < aSelectedReadMode.getCameraGainReadNoiseVector().size(); readNoiseIndex++)
329 {
330 if(!matched)
331 {
332 CameraGainReadNoise aCameraGainReadNoise = aSelectedReadMode.getCameraGainReadNoiseVector()[readNoiseIndex];
333 if(aCameraGainReadNoise.getGain() == aSelectedGain)
334 {
335 matched = true;
336 // qCInfo(KSTARS_EKOS_CAPTURE) << " matched a camera gain ";
337 aReadNoise = aCameraGainReadNoise.getReadNoise();
338 break;
339 }
340 if(aCameraGainReadNoise.getGain() < aSelectedGain)
341 {
342 lowerReadNoiseIndex = readNoiseIndex;
343 }
344 }
345 }
346
347 if(!matched)
348 {
349 int upperReadNoiseIndex = lowerReadNoiseIndex + 1;
350 // qCInfo(KSTARS_EKOS_CAPTURE)
351 // << "Interpolating read noise gain bracket: " << lowerReadNoiseIndex
352 // << " and " << upperReadNoiseIndex;
353
354 if (lowerReadNoiseIndex < aSelectedReadMode.getCameraGainReadNoiseVector().size() - 1)
355 {
356
357 CameraGainReadNoise aLowerIndexCameraReadNoise = aSelectedReadMode.getCameraGainReadNoiseVector()[lowerReadNoiseIndex];
358 CameraGainReadNoise anUpperIndexCameraReadNoise = aSelectedReadMode.getCameraGainReadNoiseVector()[upperReadNoiseIndex];
359
360 // interpolate a read-noise value
361 double m = (anUpperIndexCameraReadNoise.getReadNoise() - aLowerIndexCameraReadNoise.getReadNoise()) /
362 (anUpperIndexCameraReadNoise.getGain() - aLowerIndexCameraReadNoise.getGain());
363 aReadNoise = aLowerIndexCameraReadNoise.getReadNoise() + (m * (aSelectedGain - aLowerIndexCameraReadNoise.getGain()));
364 // qCInfo(KSTARS_EKOS_CAPTURE)
365 // << "The camera read-noise for the selected gain value is interpolated to: " << aReadNoise;
366 }
367 else
368 {
369 // qCInfo(KSTARS_EKOS_CAPTURE) << " using max camera gain ";
370 int maxIndex = aSelectedReadMode.getCameraGainReadNoiseVector().size() - 1;
371 CameraGainReadNoise aMaxIndexCameraReadNoise = aSelectedReadMode.getCameraGainReadNoiseVector()[maxIndex];
372 aReadNoise = aMaxIndexCameraReadNoise.getReadNoise();
373 }
374 }
375 else
376 {
377 // qCInfo(KSTARS_EKOS_CAPTURE)
378 // << "The camera read-noise for the selected gain value was matched: " << aReadNoise;
379 }
380
381 double anOptimalSubExposure = 0.0;
382
383 double lightPollutionElectronBaseRate = OptimalSubExposureCalculator::calculateLightPollutionElectronBaseRate(aSkyQuality);
384 double lightPollutionForOpticFocalRatio = calculateLightPolutionForOpticFocalRatio(lightPollutionElectronBaseRate,
385 aFocalRatio, aFilterCompensation);
386 double cFactor = calculateCFactor(aNoiseTolerance);
387
388 switch(anImagingCameraData.getSensorType())
389 {
390 case SENSORTYPE_MONOCHROME:
391 anOptimalSubExposure = cFactor * pow(aReadNoise, 2) / lightPollutionForOpticFocalRatio;
392 break;
393
394 case SENSORTYPE_COLOR:
395 anOptimalSubExposure = (double)3.0 * cFactor * pow(aReadNoise, 2) / lightPollutionForOpticFocalRatio;
396 break;
397 }
398
399 // qCInfo(KSTARS_EKOS_CAPTURE) << "an Optimal Sub Exposure at gain: "
400 // << aSelectedGainValue << " is " << anOptimalSubExposure << " seconds";
401
402 // Add the single exposure noise calcs to the response
403 double anExposurePollutionElectrons = lightPollutionForOpticFocalRatio * anOptimalSubExposure;
404 // qCInfo(KSTARS_EKOS_CAPTURE) << "Exposure Pollution Electrons: " << anExposurePollutionElectrons;
405
406 double anExposureShotNoise = sqrt(lightPollutionForOpticFocalRatio * anOptimalSubExposure);
407 // qCInfo(KSTARS_EKOS_CAPTURE) << "Exposure Shot Noise: " << anExposureShotNoise;
408
409 double anExposureTotalNoise = sqrt( pow(aReadNoise, 2)
410 + lightPollutionForOpticFocalRatio * anOptimalSubExposure);
411 // qCInfo(KSTARS_EKOS_CAPTURE) << "Exposure Total Noise: " << anExposureTotalNoise;
412
413 // Need to build and populate the stack estimations
414 QVector<OptimalExposureStack> aStackSummary;
415
416 // Loop through planned sessions for hours of stacking
417 for(int sessionHours = 1; sessionHours < 41; sessionHours++)
418 {
419 // rounding up to ensure that exposure count is at least "sessionHours" of data.
420 int anExposureCount = (int) ceil( (double)(sessionHours * 3600) /
421 anOptimalSubExposure );
422
423 int aStackTime = anExposureCount * anOptimalSubExposure;
424 double aStackTotalNoise = sqrt( (double)anExposureCount * pow(aReadNoise, 2)
425 + lightPollutionForOpticFocalRatio * (anExposureCount * anOptimalSubExposure));
426
427 OptimalExposureStack *anOptimalExposureStack
428 = new OptimalExposureStack(sessionHours, anExposureCount, aStackTime, aStackTotalNoise);
429
430 /*
431 qCInfo(KSTARS_EKOS_CAPTURE) << sessionHours << "\t" << anExposureCount
432 << "\t" << aStackTime << "\t" << aStackTotalNoise
433 << "\t" << aStackTime / aStackTotalNoise;
434
435 // << aSelectedGainValue << " is " << anOptimalSubExposure << " seconds";
436 */
437 aStackSummary.push_back(*anOptimalExposureStack);
438
439
440 }
441
442 OptimalExposureDetail anOptimalExposureDetail = OptimalExposureDetail(getASelectedGain(), anOptimalSubExposure,
443 anExposurePollutionElectrons, anExposureShotNoise, anExposureTotalNoise, aStackSummary);
444
445 return(anOptimalExposureDetail);
446}
447
448
449}
450
void append(QList< T > &&value)
iterator begin()
iterator end()
void push_back(parameter_type value)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 4 2024 16:38:42 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.