Kstars

exposurecalculatordialog.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
8#include <QLoggingCategory>
9#include <QDir>
10#include <QVectorIterator>
11#include <QVariant>
12#include <QTableWidgetItem>
13#include "fileutilitycameradata.h"
14#include "optimalsubexposurecalculator.h"
15#include "optimalexposuredetail.h"
16#include <string>
17#include <iostream>
18#include <filesystem>
19#include "fileutilitycameradatadialog.h"
20#include "exposurecalculatordialog.h"
21#include "./ui_exposurecalculatordialog.h"
22#include <kspaths.h>
23#include <ekos_capture_debug.h>
24
25/*
26 *
27 *
28 * https://www.astrobin.com/forum/c/astrophotography/deep-sky/robin-glover-talk-questioning-length-of-single-exposure/
29 * http://astro.physics.uiowa.edu/~kaaret/2013f_29c137/Lab03_noise.html#:~:text=The%20read%20noise%20of%20the,removing%20hot%20and%20dead%20pixels
30 *
31 * Resource for DSLR read-noise:
32 * https://www.photonstophotos.net/Charts/RN_ADU.htm
33 *
34 */
35
36OptimalExposure::OptimalExposureDetail
37aSubExposureDetail; // Added during refactoring to simplify and better support the noise graph
38
39ExposureCalculatorDialog::ExposureCalculatorDialog(QWidget *parent,
40 double aPreferredSkyQualityValue,
41 double aPreferredFocalRatioValue,
42 const QString &aPreferredCameraId) :
43 QDialog(parent),
44 aPreferredSkyQualityValue(aPreferredSkyQualityValue),
45 aPreferredFocalRatioValue(aPreferredFocalRatioValue),
46 aPreferredCameraId(aPreferredCameraId),
47 ui(new Ui::ExposureCalculatorDialog)
48{
49 ui->setupUi(this);
50
51 ui->exposureDiffLabel->setText(QString("\u0394="));
52
53 QStringList availableCameraFiles = OptimalExposure::FileUtilityCameraData::getAvailableCameraFilesList();
54
55 if(availableCameraFiles.length() == 0)
56 {
57 // qCWarning(KSTARS_EKOS_CAPTURE) << "Exposure Calculator - No Camera data available, closing dialog";
58 // QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection);
59 qCWarning(KSTARS_EKOS_CAPTURE) << "Exposure Calculator - No Camera data available, opening camera data download dialog";
60 FileUtilityCameraDataDialog aCameraDownloadDialog(this, aPreferredCameraId);
61 aCameraDownloadDialog.setWindowModality(Qt::WindowModal);
63 // QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection);
64 // Look again for files...
65 availableCameraFiles = OptimalExposure::FileUtilityCameraData::getAvailableCameraFilesList();
66
67 }
68
69
70 if(availableCameraFiles.length() > 0)
71 {
72 ui->imagingCameraSelector->clear();
73 refreshCameraSelector(ui, availableCameraFiles, aPreferredCameraId);
74
75 ui->exposureCalculatorFrame->setEnabled(false);
76
77 hideGainSelectionWidgets();
78
79 ui->gainSelector->setMinimum(0);
80 ui->gainSelector->setMaximum(100);
81 ui->gainSelector->setValue(50);
82
83 ui->indiFocalRatio->setMinimum(1.0);
84 ui->indiFocalRatio->setMaximum(12.0);
85 ui->indiFocalRatio->setSingleStep(0.1);
86 // was coded to default as ui->indiFocalRatio->setValue(5.0);
87 ui->indiFocalRatio->setValue(aPreferredFocalRatioValue);
88
89 // Setup the "user" controls.
90 ui->userSkyQuality->setMinimum(16.00);
91 ui->userSkyQuality->setMaximum(22.00);
92 ui->userSkyQuality->setSingleStep(0.01);
93 // was coded ui->userSkyQuality->setValue(20.0);
94 ui->userSkyQuality->setValue(aPreferredSkyQualityValue);
95
96 ui->noiseTolerance->setMinimum(0.02);
97 ui->noiseTolerance->setMaximum(500.00);
98 ui->noiseTolerance->setSingleStep(0.25);
99 ui->noiseTolerance->setValue(5.0);
100
101 ui->filterBandwidth->setMinimum(2.8);
102 ui->filterBandwidth->setMaximum(300);
103 ui->filterBandwidth->setValue(300);
104 ui->filterBandwidth->setSingleStep(0.1);
105
106
107
108 /*
109 Experimental compensation for filters on light the pollution calculation are a bit tricky.
110 Part 1...
111
112 An unfiltered camera may include some IR and UV, and be able to read a bandwidth of say 360nm (at a reasonably high QE %).
113
114 But for simplicity, the filter compensation calculation will be based on the range for visible light, and use a nominal
115 bandwidth of 300, (roughly the bandwidth of a typical luminance filter).
116
117 The filter compensation factor that will be applied to light pollution will be the filter bandwidth (from the UI) / 300.
118 This means that a typical luminance filter would produce a "nuetral" compensation of 1.0 (300 / 300).
119
120 But the user interface will allow selection of wider bands for true "unfiltered" exposure calculations. Example: by selecting a
121 bandwith of 360, the light pollution compensation will 1.2, calculated as (360 / 300). This is to recognize that light pollution
122 may be entering the IR and UV range of an unfiltered camera sensor.
123
124 A Luminance filter may only pass 300nm, so the filter compensaton value would be 1.0 (300 / 300)
125 An RGB filter may only pass 100nm, so the filter compensaton value would be 0.3333 = (100 / 300)
126 An SHO filter may only pass 3nm, so the filter compensaton value would be 0.0100 = (3 / 300)
127
128 Filters will reduce bandwidth, but also slightly reduce tranmission within the range that they "pass".
129 The values stated are only for demonstration and testing purposes, further research is needed.
130
131 */
132
133 // Set up plot colors of Sub Exposure (night friendly)
134 ui->qCustomPlotSubExposure->setBackground(QBrush(Qt::black));
135 ui->qCustomPlotSubExposure->xAxis->setBasePen(QPen(Qt::white, 1));
136 ui->qCustomPlotSubExposure->yAxis->setBasePen(QPen(Qt::white, 1));
137 ui->qCustomPlotSubExposure->xAxis->setTickPen(QPen(Qt::white, 1));
138 ui->qCustomPlotSubExposure->yAxis->setTickPen(QPen(Qt::white, 1));
139 ui->qCustomPlotSubExposure->xAxis->setSubTickPen(QPen(Qt::white, 1));
140 ui->qCustomPlotSubExposure->yAxis->setSubTickPen(QPen(Qt::white, 1));
141 ui->qCustomPlotSubExposure->xAxis->setTickLabelColor(Qt::white);
142 ui->qCustomPlotSubExposure->yAxis->setTickLabelColor(Qt::white);
143 ui->qCustomPlotSubExposure->xAxis->setLabelColor(Qt::white);
144 ui->qCustomPlotSubExposure->yAxis->setLabelColor(Qt::white);
145
146 ui->qCustomPlotSubExposure->xAxis->grid()->setPen(QPen(Qt::darkGray));
147 ui->qCustomPlotSubExposure->yAxis->grid()->setPen(QPen(Qt::darkGray));
148
149 ui->qCustomPlotSubExposure->xAxis->setLabel("Gain");
150
151 ui->qCustomPlotSubExposure->yAxis->setLabel("Exposure Time");
152
153 ui->qCustomPlotSubExposure->addGraph();
154
155
156 // Set up plot colors of Integrated Image Noise (night friendly)
157 ui->qCustomPlotIntegrationNoise->setBackground(QBrush(Qt::black));
158 ui->qCustomPlotIntegrationNoise->xAxis->setBasePen(QPen(Qt::white, 1));
159 ui->qCustomPlotIntegrationNoise->yAxis->setBasePen(QPen(Qt::white, 1));
160 ui->qCustomPlotIntegrationNoise->xAxis->setTickPen(QPen(Qt::white, 1));
161 ui->qCustomPlotIntegrationNoise->yAxis->setTickPen(QPen(Qt::white, 1));
162 ui->qCustomPlotIntegrationNoise->xAxis->setSubTickPen(QPen(Qt::white, 1));
163 ui->qCustomPlotIntegrationNoise->yAxis->setSubTickPen(QPen(Qt::white, 1));
164 ui->qCustomPlotIntegrationNoise->xAxis->setTickLabelColor(Qt::white);
165 ui->qCustomPlotIntegrationNoise->yAxis->setTickLabelColor(Qt::white);
166 ui->qCustomPlotIntegrationNoise->xAxis->setLabelColor(Qt::white);
167 ui->qCustomPlotIntegrationNoise->yAxis->setLabelColor(Qt::white);
168
169 ui->qCustomPlotIntegrationNoise->xAxis->grid()->setPen(QPen(Qt::darkGray));
170 ui->qCustomPlotIntegrationNoise->yAxis->grid()->setPen(QPen(Qt::darkGray));
171
172 ui->qCustomPlotIntegrationNoise->addGraph(ui->qCustomPlotIntegrationNoise->xAxis, ui->qCustomPlotIntegrationNoise->yAxis);
173 ui->qCustomPlotIntegrationNoise->graph(0)->setPen(QPen(Qt::yellow));
174 ui->qCustomPlotIntegrationNoise->graph(0)->setName("Integration Time to Noise Ratio");
175 ui->qCustomPlotIntegrationNoise->xAxis->setLabel("Stacked Exposures");
176 ui->qCustomPlotIntegrationNoise->yAxis->setLabel("Noise Ratio");
177
178 // ui->qCustomPlotIntegrationNoise->addGraph(ui->qCustomPlotIntegrationNoise->xAxis, ui->qCustomPlotIntegrationNoise->yAxis);
179 // ui->qCustomPlotIntegrationNoise->graph(1)->setPen(QPen(Qt::green));
180 // ui->qCustomPlotIntegrationNoise->graph(1)->setName("Integration to Noise Ratio");
181 // ui->qCustomPlotIntegrationNoise->yAxis2->setLabel("Noise Ratio");
182
183
184
185
186 connect(ui->imagingCameraSelector, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
187 &ExposureCalculatorDialog::applyInitialInputs);
188
189 connect(ui->cameraReadModeSelector, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
190 &ExposureCalculatorDialog::handleUserAdjustment);
191
192 connect(ui->gainSelector, QOverload<int>::of(&QSpinBox::valueChanged), this,
193 &ExposureCalculatorDialog::handleUserAdjustment);
194
196 &ExposureCalculatorDialog::handleUserAdjustment);
197
199 &ExposureCalculatorDialog::handleUserAdjustment);
200
202 &ExposureCalculatorDialog::handleUserAdjustment);
203
205 &ExposureCalculatorDialog::handleUserAdjustment);
206
207 connect(ui->gainISODiscreteSelector, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
208 &ExposureCalculatorDialog::handleUserAdjustment);
209
210 connect(ui->targetNoiseRatio, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
211 &ExposureCalculatorDialog::handleStackCalculation);
212
213 // Hide the gain selector frames (until a camera is selected)
214 hideGainSelectionWidgets();
215
216 applyInitialInputs();
217
218 }
219 /*
220 else
221 {
222 // qCWarning(KSTARS_EKOS_CAPTURE) << "Exposure Calculator - No Camera data available, closing dialog";
223 // QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection);
224 qCWarning(KSTARS_EKOS_CAPTURE) << "Exposure Calculator - No Camera data available, opening camera data download dialog";
225 FileUtilityCameraDataDialog aCameraDownloadDialog(this, aPreferredCameraId);
226 aCameraDownloadDialog.setWindowModality(Qt::WindowModal);
227 aCameraDownloadDialog.exec();
228 QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection);
229 }
230 */
231}
232
233
234
235
236int ExposureCalculatorDialog::getGainSelection(OptimalExposure::GainSelectionType aGainSelectionType)
237{
238 int aSelectedGain = 0;
239 switch(aGainSelectionType)
240 {
241
242 case OptimalExposure::GAIN_SELECTION_TYPE_NORMAL:
243 // aSelectedGain = ui->gainSlider->value();
244 aSelectedGain = ui->gainSelector->value();
245 break;
246
247 case OptimalExposure::GAIN_SELECTION_TYPE_ISO_DISCRETE:
248 // qCInfo(KSTARS_EKOS_CAPTURE) << " iso selector text: " << ui->isoDiscreteSelector->currentText();
249 aSelectedGain = ui->gainISODiscreteSelector->currentText().toInt();
250 break;
251
252 case OptimalExposure::GAIN_SELECTION_TYPE_FIXED:
253 // qCInfo(KSTARS_EKOS_CAPTURE) << "Fixed read-noise camera gain set to 0";
254 aSelectedGain = 0; // Fixed noise cameras have read noise data at 0 gain.
255 break;
256
257 }
258
259 return(aSelectedGain);
260}
261
262
263QString skyQualityToBortleClassNumber(double anSQMValue)
264{
265
267 if(anSQMValue < 18.38)
268 {
269 aBortleClassNumber = "8 to 9";
270 }
271 else if(anSQMValue < 18.94)
272 {
273 aBortleClassNumber = "7";
274 }
275 else if(anSQMValue < 19.50)
276 {
277 aBortleClassNumber = "6";
278 }
279 else if(anSQMValue < 20.49)
280 {
281 aBortleClassNumber = "5";
282 }
283 else if(anSQMValue < 21.69)
284 {
285 aBortleClassNumber = "4";
286 }
287 else if(anSQMValue < 21.89)
288 {
289 aBortleClassNumber = "3";
290 }
291 else if(anSQMValue < 21.99)
292 {
293 aBortleClassNumber = "2";
294 }
295 else aBortleClassNumber = "1";
296
297 return(aBortleClassNumber);
298}
299
300// calculate a Bortle style color based on SQM
301QColor makeASkyQualityColor(double anSQMValue)
302{
305
306 if(anSQMValue < 18.32) // White Zone
307 {
308 aHueDegree = 40;
309 aSaturation = 0; // Saturation must move from
310 aValue = 254; // Value must move from 254 to 240
311 }
312 else if(anSQMValue < 18.44) // From White Zone at 18.32 transitioning to Red Zone at 18.44
313 {
314 aHueDegree = 0; // Hue is Red,
315 // Saturation must transition from 0 to 255 as SQM moves from 18.32 to 18.44
316 aSaturation = (int)(255 * ((anSQMValue - (double)18.32) / (18.44 - 18.32)));
317 aValue = 254;
318 }
319 else if(anSQMValue < 21.82 )
320 {
321 // In the color range transitions hue of Bortle can be approximated with a polynomial
322 aHueDegree = 17.351411032 * pow(anSQMValue, 4)
323 - 1384.0773705 * pow(anSQMValue, 3)
324 + 41383.66777 * pow(anSQMValue, 2)
325 - 549664.28976 * anSQMValue
326 + 2736244.0733;
327
328 if(aHueDegree < 0) aHueDegree = 0;
329
330 aSaturation = 255;
331 aValue = 240;
332
333 }
334 else if(anSQMValue < 21.92) // Transition from Blue to Dark Gray between 21.82 and 21.92
335 {
336 aHueDegree = 240;
337 // Saturation must transition from 255 to 0
338 aSaturation = (int) ((2550 * (21.92 - anSQMValue)));
339 // Value must transition from 240 to 100
340 aValue = (int)(100 + (1400 * (21.92 - anSQMValue)));
341
342 }
343 else if(anSQMValue < 21.99) // Dark gray zone
344 {
345 aHueDegree = 240;
346 aSaturation = 0;
347 aValue = 100;
348 }
349 else // Black zone should only be 21.99 and up
350 {
351 aHueDegree = 240;
352 aSaturation = 0;
353 aValue = 0;
354 }
355
356 // qCInfo(KSTARS_EKOS_CAPTURE) << "Sky Quality Color Hue: " << aHueDegree;
357 // qCInfo(KSTARS_EKOS_CAPTURE) << "Sky Quality Color Saturation: " << aSaturation;
358 // qCInfo(KSTARS_EKOS_CAPTURE) << "Sky Quality Color Value: " << aValue;
359
361
362 return(aSkyBrightnessColor);
363}
364
365
366void refreshSkyQualityPresentation(Ui::ExposureCalculatorDialog *ui, double aSkyQualityValue)
367{
368
369 // qCInfo(KSTARS_EKOS_CAPTURE) << "\ta selected Sky Quality: " << aSkyQualityValue;
370 QColor aSkyQualityColor = makeASkyQualityColor(aSkyQualityValue);
371
372 ui->bortleScaleValue->setText(skyQualityToBortleClassNumber(aSkyQualityValue));
373
374 // Update the skyQualityColor Widget
376
378 ui->skyQualityColor->setAutoFillBackground(true);
379 ui->skyQualityColor->setPalette(pal);
380 ui->skyQualityColor->show();
381
382}
383
384
385void ExposureCalculatorDialog::handleUserAdjustment()
386{
387
388 // This test for enabled was needed because dynamic changes to a
389 // combo box during initialization of the calculator were firing
390 // this method and prematurely triggering a calculation which was
391 // crashing because the initialization was incomplete.
392
393 if(ui->exposureCalculatorFrame->isEnabled())
394 {
395 // Recalculate and refresh the graph, with changed inputs from the ui
396 QString aSelectedImagingCamera = ui->imagingCameraSelector->itemText(ui->imagingCameraSelector->currentIndex());
397
398 // ui->cameraReadModeSelector->currentData()
399 int aSelectedReadMode = ui->cameraReadModeSelector->currentData().toInt();
400
401 double aFocalRatioValue = ui->indiFocalRatio->value();
402
403 double aSkyQualityValue = ui->userSkyQuality->value();
404 refreshSkyQualityPresentation(ui, aSkyQualityValue);
405
406 double aNoiseTolerance = ui->noiseTolerance->value();
407
408 // double aFilterCompensationValue = 1.0;
409 double aFilterCompensationValue = ((double)ui->filterBandwidth->value() / (double)300);
410
411 int aSelectedGainValue = getGainSelection(anOptimalSubExposureCalculator->getImagingCameraData().getGainSelectionType());
412
413
414 // double aSelectedGainValue = ui->gainSlider->value();
415 // qCInfo(KSTARS_EKOS_CAPTURE) << "\ta selected gain: " << aSelectedGainValue;
416
417 calculateSubExposure(aNoiseTolerance, aSkyQualityValue, aFocalRatioValue, aFilterCompensationValue, aSelectedReadMode,
419 }
420}
421
422void ExposureCalculatorDialog::hideGainSelectionWidgets()
423{
424 ui->gainSelectionFrame->setEnabled(false);
425 ui->gainSelectionFrame->setVisible(false);
426
427 ui->gainSpinnerLabel->setVisible(false);
428 ui->gainSelector->setVisible(false);
429 ui->gainSelector->setEnabled(false);
430
431 ui->gainISOSelectorLabel->setVisible(false);
432 ui->gainISODiscreteSelector->setVisible(false);
433 ui->gainISODiscreteSelector->setEnabled(false);
434
435 ui->gainSelectionFixedLabel->setVisible(false);
436
437 /*
438 ui->gainSelectionISODiscreteFrame->setEnabled(false);
439 ui->gainSelectionISODiscreteFrame->setVisible(false);
440 ui->gainSelectionFixedFrame->setEnabled(false);
441 ui->gainSelectionFixedFrame->setVisible(false);
442 */
443}
444
445
446void ExposureCalculatorDialog::showGainSelectionNormalWidgets()
447{
448 ui->gainSpinnerLabel->setVisible(true);
449 ui->gainSelector->setEnabled(true);
450 ui->gainSelector->setVisible(true);
451
452 ui->gainSelectionFrame->setEnabled(true);
453 ui->gainSelectionFrame->setVisible(true);
454}
455
456void ExposureCalculatorDialog::showGainSelectionISODiscreteWidgets()
457{
458 ui->gainISOSelectorLabel->setVisible(true);
459 ui->gainISODiscreteSelector->setEnabled(true);
460 ui->gainISODiscreteSelector->setVisible(true);
461
462 ui->gainSelectionFrame->setEnabled(true);
463 ui->gainSelectionFrame->setVisible(true);
464}
465
466void ExposureCalculatorDialog::showGainSelectionFixedWidgets()
467{
468 ui->gainSelectionFixedLabel->setVisible(true);
469
470 ui->gainSelectionFrame->setEnabled(true);
471 ui->gainSelectionFrame->setVisible(true);
472}
473
474
475void ExposureCalculatorDialog::applyInitialInputs()
476{
477 ui->exposureCalculatorFrame->setEnabled(false);
478
479 // QString aSelectedImagingCameraName = ui->imagingCameraSelector->itemText(ui->imagingCameraSelector->currentIndex());
480 QString aSelectedImagingCameraFileName = ui->imagingCameraSelector->itemData(
481 ui->imagingCameraSelector->currentIndex()).toString();
482
483
484 // qCInfo(KSTARS_EKOS_CAPTURE) << ui->cameraReadModeSelector->currentData();
485
486 int aSelectedReadMode = 0;
487
488 double aFocalRatioValue = ui->indiFocalRatio->value();
489 double aSkyQualityValue = ui->userSkyQuality->value();
490 refreshSkyQualityPresentation(ui, aSkyQualityValue);
491
492 double aNoiseTolerance = ui->noiseTolerance->value();
493
494 // double aFilterCompensationValue = 1.0;
495 // double aFilterCompensationValue = ui->filterSelection->itemData(ui->filterSelection->currentIndex()).toDouble();
496 double aFilterCompensationValue = ((double)ui->filterBandwidth->value() / (double)300);
497
498 initializeSubExposureCalculator(aNoiseTolerance, aSkyQualityValue, aFocalRatioValue, aFilterCompensationValue,
500
501 int aSelectedGainValue = ui->gainSelector->value();
502
503 calculateSubExposure(aNoiseTolerance, aSkyQualityValue, aFocalRatioValue, aFilterCompensationValue, aSelectedReadMode,
505
506 ui->exposureCalculatorFrame->setEnabled(true);
507
508}
509
510
511void plotIntegratedNoise(Ui::ExposureCalculatorDialog *ui,
512 OptimalExposure::OptimalExposureDetail *subExposureDetail)
513{
514
515 ui->qCustomPlotIntegrationNoise->graph()->data()->clear();
516
517 double aCoefficient = (subExposureDetail->getSubExposureTime() / subExposureDetail->getExposureTotalNoise());
518 /*
519 qCInfo(KSTARS_EKOS_CAPTURE) << "Noise Ratio Function: Noise Ratio = " << aCoefficient << " * Sqrt(Exposure Count)";
520
521 qCInfo(KSTARS_EKOS_CAPTURE) << "Differential of Noise Ratio Function: = " << aCoefficient << " / (2 * Sqrt(Exposure Count)";
522
523 qCInfo(KSTARS_EKOS_CAPTURE) << "Exposure Count Function (for desired Noise Ratio):"
524 << "Exposure Count = (Noise Ratio / " << aCoefficient << ") ^2 = pow(Noise Ratio / " << aCoefficient << ", 2)";
525 */
526 double aTargetNoiseRatio = ui->targetNoiseRatio->value();
527 // qCInfo(KSTARS_EKOS_CAPTURE) << "Target Noise Ratio: " << aTargetNoiseRatio;
528
529 int aRequiredExposureCount = std::max(1, (int)(pow((aTargetNoiseRatio / aCoefficient), 2)));
530 ui->exposureCount->setText(QString::number(aRequiredExposureCount));
531
533 ui->exposureCountDifferential->setText(QString::number(aDifferential, 'f', 2));
534
535
536 ui->qCustomPlotIntegrationNoise->graph()->data()->clear();
537
538 // ui->qCustomPlotIntegrationNoise
539 // ui->qCustomPlotIntegrationNoise->graph(0)->setData(ExposureCount, noise);
540
541 QVector<double> xValue((aRequiredExposureCount * 2) + 1), yValue((aRequiredExposureCount * 2) + 1);
542 for (int exposureCount = 1; exposureCount < (aRequiredExposureCount * 2) + 1; exposureCount++)
543 {
544 xValue[exposureCount] = exposureCount;
545 yValue[exposureCount] = aCoefficient * pow(exposureCount, 0.5);
546 }
547
548 ui->qCustomPlotIntegrationNoise->graph(0)->setData(xValue, yValue);
549
550 ui->qCustomPlotIntegrationNoise->xAxis->setRange(0, aRequiredExposureCount * 2);
551 ui->qCustomPlotIntegrationNoise->yAxis->setRange(0, yValue[yValue.size() - 1]);
552
553 // Also add a graph with a vertical line to show the computed integration
554 ui->qCustomPlotIntegrationNoise->addGraph();
555
561 ui->qCustomPlotIntegrationNoise->graph(1)->setData(selectedIntegrationSizeX, selectedIntegrationSizeY);
562
564 penSelectedIntegrationSize.setWidth(1);
565 // penSelectedIntegrationSize.setColor(QColor(180, 0, 0));
566 // On the black background need more contrast
567 penSelectedIntegrationSize.setColor(QColor(240, 0, 0));
568
569 ui->qCustomPlotIntegrationNoise->graph(1)->setPen(penSelectedIntegrationSize);
570
571 ui->qCustomPlotIntegrationNoise->graph(1)->setScatterStyle(QCPScatterStyle::ssCircle);
572
573 ui->qCustomPlotIntegrationNoise->graph()->rescaleAxes(true);
574 ui->qCustomPlotIntegrationNoise->replot();
575
576}
577
578// Slot for adjustments made to desired noise ratio that require a refresh of the NR graph
579void ExposureCalculatorDialog::handleStackCalculation()
580{
581 plotIntegratedNoise(ui, &aSubExposureDetail);
582}
583
584
585void plotSubExposureEnvelope(Ui::ExposureCalculatorDialog *ui,
586 OptimalExposure::OptimalSubExposureCalculator *anOptimalSubExposureCalculator,
587 OptimalExposure::OptimalExposureDetail *subExposureDetail)
588{
589
590 OptimalExposure::CameraExposureEnvelope aCameraExposureEnvelope =
591 anOptimalSubExposureCalculator->calculateCameraExposureEnvelope();
592 // qCInfo(KSTARS_EKOS_CAPTURE) << "Exposure Envelope has a set of: " << aCameraExposureEnvelope.getASubExposureVector().size();
593 // qCInfo(KSTARS_EKOS_CAPTURE) << "Exposure Envelope has a minimum Exposure Time of " << aCameraExposureEnvelope.getExposureTimeMin();
594 // qCInfo(KSTARS_EKOS_CAPTURE) << "Exposure Envelope has a maximum Exposure Time of " << aCameraExposureEnvelope.getExposureTimeMax();
595
596 // anOptimalSubExposureCalculator->getImagingCameraData()
597
598 // Reset the graph axis (But maybe this was not necessary,
599 ui->qCustomPlotSubExposure->xAxis->setRange(anOptimalSubExposureCalculator->getImagingCameraData().getGainMin(),
600 anOptimalSubExposureCalculator->getImagingCameraData().getGainMax());
601 // But for the exposure yAxis include a bit of a margin so that data is not encoaching on the axis.
602 ui->qCustomPlotSubExposure->yAxis->setRange(aCameraExposureEnvelope.getExposureTimeMin() - 10,
603 aCameraExposureEnvelope.getExposureTimeMax() + 10);
604 ui->qCustomPlotSubExposure->replot();
605
606 // Prepare for the exposure line plot, move the data to parallel arrays for the custom plotter
607 QVector<double> gain(aCameraExposureEnvelope.getASubExposureVector().size()),
608 exposuretime(aCameraExposureEnvelope.getASubExposureVector().size());
609 for(int index = 0; index < aCameraExposureEnvelope.getASubExposureVector().size(); index++)
610 {
611 OptimalExposure::CalculatedGainSubExposureTime aGainExposureTime = aCameraExposureEnvelope.getASubExposureVector()[index];
612 gain[index] = (double)aGainExposureTime.getSubExposureGain();
613 exposuretime[index] = aGainExposureTime.getSubExposureTime();
614 }
615 ui->qCustomPlotSubExposure->graph()->data()->clear();
616
617 ui->qCustomPlotSubExposure->graph(0)->setData(gain, exposuretime);
618
619 // Also add a graph with a vertical line to show the selected gain...
620 ui->qCustomPlotSubExposure->addGraph();
621
623 selectedExposureX[0] = subExposureDetail->getSelectedGain();
624 selectedExposureY[0] = 0;
625 selectedExposureX[1] = subExposureDetail->getSelectedGain();
626 selectedExposureY[1] = subExposureDetail->getSubExposureTime();
627 ui->qCustomPlotSubExposure->graph(1)->setData(selectedExposureX, selectedExposureY);
628
630 penExposureEnvelope.setWidth(1);
631 // penExposureEnvelope.setColor(QColor(0, 180, 180));
632 // On the black background need more contrast
633 penExposureEnvelope.setColor(QColor(0, 220, 220));
634 ui->qCustomPlotSubExposure->graph(0)->setPen(penExposureEnvelope);
635
637 penSelectedExposure.setWidth(1);
638 // penSelectedExposure.setColor(QColor(180, 0, 0));
639 // On the black background need more contrast
640 penSelectedExposure.setColor(QColor(240, 0, 0));
641
642 ui->qCustomPlotSubExposure->graph(1)->setPen(penSelectedExposure);
643
644 ui->qCustomPlotSubExposure->graph(1)->setScatterStyle(QCPScatterStyle::ssCircle);
645
646 // extend the x-axis slightly so that the markers aren't hidden at the extreme edges
647 ui->qCustomPlotSubExposure->xAxis->setRange(anOptimalSubExposureCalculator->getImagingCameraData().getGainMin() - 5,
648 anOptimalSubExposureCalculator->getImagingCameraData().getGainMax() + 5);
649 // force the y-axis to start at 0, (sometimes the auto rescale was making the y-axis range start a negative value
650 ui->qCustomPlotSubExposure->yAxis->setRange(0, aCameraExposureEnvelope.getExposureTimeMax());
651
652 ui->qCustomPlotSubExposure->graph()->rescaleAxes(true);
653 ui->qCustomPlotSubExposure->replot();
654
655}
656
657void ExposureCalculatorDialog::initializeSubExposureCalculator(double aNoiseTolerance, double aSkyQualityValue,
659{
660 // qCInfo(KSTARS_EKOS_CAPTURE) << "initializeSubExposureComputer";
661 // qCInfo(KSTARS_EKOS_CAPTURE) << "\taNoiseTolerance: " << aNoiseTolerance;
662 // qCInfo(KSTARS_EKOS_CAPTURE) << "\taSkyQualityValue: " << aSkyQualityValue;
663 // qCInfo(KSTARS_EKOS_CAPTURE) << "\taFocalRatioValue: " << aFocalRatioValue;
664 // qCInfo(KSTARS_EKOS_CAPTURE) << "\taFilterCompensation: " << aFilterCompensationValue;
665 // qCInfo(KSTARS_EKOS_CAPTURE) << "\taSelectedImagingCamera: " << aSelectedImagingCameraName;
666
667 // QVector<int> *aGainSelectionRange = new QVector<int>();
668
669 // QVector<OptimalExposure::CameraGainReadNoise> *aCameraGainReadNoiseVector
670 // = new QVector<OptimalExposure::CameraGainReadNoise>();
671
672 // QVector<OptimalExposure::CameraGainReadMode> *aCameraGainReadModeVector
673 // = new QVector<OptimalExposure::CameraGainReadMode>();
674
675 // // Initialize with some default values before attempting to load from file
676
677 // anImagingCameraData = new OptimalExposure::ImagingCameraData(aSelectedImagingCameraName, OptimalExposure::SENSORTYPE_COLOR,
678 // OptimalExposure::GAIN_SELECTION_TYPE_NORMAL, *aGainSelectionRange, *aCameraGainReadModeVector);
679
680 anImagingCameraData = new OptimalExposure::ImagingCameraData();
681 // Load camera data from file
682 OptimalExposure::FileUtilityCameraData::readCameraDataFile(aSelectedImagingCameraName, anImagingCameraData);
683
684 // qCInfo(KSTARS_EKOS_CAPTURE) << "Loaded Imaging Camera Data for " + anImagingCameraData->getCameraId();
685 // qCInfo(KSTARS_EKOS_CAPTURE) << "Camera Gain Selection Type " + QString::number(anImagingCameraData->getGainSelectionType());
686
687 // qCInfo(KSTARS_EKOS_CAPTURE) << "Camera Read Mode Vector Size " + QString::number(
688 // anImagingCameraData->getCameraGainReadModeVector().size());
689
690 switch(anImagingCameraData->getSensorType())
691 {
692 case OptimalExposure::SENSORTYPE_COLOR:
693 ui->SensorType->setText("Color");
694 break;
695 case OptimalExposure::SENSORTYPE_MONOCHROME:
696 ui->SensorType->setText("Mono");
697 break;
698 }
699
700 ui->cameraReadModeSelector->clear();
701 foreach(OptimalExposure::CameraGainReadMode aReadMode, anImagingCameraData->getCameraGainReadModeVector())
702 {
703 QString readModeText = QString::number(aReadMode.getCameraGainReadModeNumber()) + " : " +
704 aReadMode.getCameraGainReadModeName();
705 ui->cameraReadModeSelector->addItem(readModeText, aReadMode.getCameraGainReadModeNumber());
706 }
707 if(anImagingCameraData->getCameraGainReadModeVector().size() > 1)
708 {
709 ui->cameraReadModeSelector->setEnabled(true);
710 }
711 else
712 {
713 ui->cameraReadModeSelector->setEnabled(false);
714 }
715
716
717 // qCInfo(KSTARS_EKOS_CAPTURE) << "Camera Gain Read-Noise Vector Size "
718 // + QString::number(anImagingCameraData->getCameraGainReadNoiseVector().size());
719
720
721
722 switch( anImagingCameraData->getGainSelectionType() )
723 {
724 case OptimalExposure::GAIN_SELECTION_TYPE_FIXED:
725 // qCInfo(KSTARS_EKOS_CAPTURE) << "Gain Selection Type: GAIN_SELECTION_TYPE_FIXED";
726 hideGainSelectionWidgets();
727 showGainSelectionFixedWidgets();
728
729 break;
730
731 case OptimalExposure::GAIN_SELECTION_TYPE_ISO_DISCRETE:
732 // qCInfo(KSTARS_EKOS_CAPTURE) << "Gain Selection Type: GAIN_SELECTION_TYPE_ISO_DISCRETE";
733 hideGainSelectionWidgets();
734
735 ui->gainISODiscreteSelector->clear();
736 // Load the ISO Combo from the camera data
737 foreach(int isoSetting, anImagingCameraData->getGainSelectionRange())
738 {
739 ui->gainISODiscreteSelector->addItem(QString::number(isoSetting));
740 }
741 ui->gainISODiscreteSelector->setCurrentIndex(0);
742
743 // qCInfo(KSTARS_EKOS_CAPTURE) << "Camera Data Gain min " + QString::number(anImagingCameraData->getGainMin());
744 // qCInfo(KSTARS_EKOS_CAPTURE) << "Camera Data Gain max " + QString::number(anImagingCameraData->getGainMax());
745
746 showGainSelectionISODiscreteWidgets();
747
748 break;
749
750 case OptimalExposure::GAIN_SELECTION_TYPE_NORMAL:
751 // qCInfo(KSTARS_EKOS_CAPTURE) << "Gain Selection Type: GAIN_SELECTION_TYPE_NORMAL";
752
753 hideGainSelectionWidgets();
754 showGainSelectionNormalWidgets();
755 // qCInfo(KSTARS_EKOS_CAPTURE) << "Camera Data Gain min " + QString::number(anImagingCameraData->getGainMin());
756 // qCInfo(KSTARS_EKOS_CAPTURE) << "Camera Data Gain max " + QString::number(anImagingCameraData->getGainMax());
757 break;
758
759 }
760
761 anOptimalSubExposureCalculator = new OptimalExposure::OptimalSubExposureCalculator(aNoiseTolerance, aSkyQualityValue,
762 aFocalRatioValue, aFilterCompensationValue, *anImagingCameraData);
763
764 // qCInfo(KSTARS_EKOS_CAPTURE) << "Calculating... ";
765 // qCInfo(KSTARS_EKOS_CAPTURE) << "A Noise Tolerance " << anOptimalSubExposureCalculator->getANoiseTolerance();
766 // qCInfo(KSTARS_EKOS_CAPTURE) << "A Sky Quality " << anOptimalSubExposureCalculator->getASkyQuality();
767
768 // qCInfo(KSTARS_EKOS_CAPTURE) << "A Focal Ratio " << anOptimalSubExposureCalculator->getAFocalRatio();
769 // qCInfo(KSTARS_EKOS_CAPTURE) << "A Filter Compensation Value (ignored): " << anOptimalSubExposureCalculator->getAFilterCompensation();
770
771 // qCInfo(KSTARS_EKOS_CAPTURE) << "A Camera Gain Min " << anOptimalSubExposureCalculator->getImagingCameraData().getGainMin();
772 // qCInfo(KSTARS_EKOS_CAPTURE) << "A Camera Gain Max " << anOptimalSubExposureCalculator->getImagingCameraData().getGainMax();
773
774}
775
776
777void refreshStackTable(Ui::ExposureCalculatorDialog *ui,
778 OptimalExposure::OptimalExposureDetail *subExposureDetail)
779{
780 QTableWidget *resultStackTable = ui->exposureStackResult;
781
782 int stackSummarySize = subExposureDetail->getStackSummary().size();
784
786 {
787 OptimalExposure::OptimalExposureStack anOptimalExposureStack = subExposureDetail->getStackSummary()[stackSummaryIndex];
788
791 resultStackTable->item(stackSummaryIndex, 0)->setTextAlignment(Qt::AlignCenter);
792
795 resultStackTable->item(stackSummaryIndex, 1)->setTextAlignment(Qt::AlignRight);
796
799 resultStackTable->item(stackSummaryIndex, 2)->setTextAlignment(Qt::AlignRight);
800
802 new QTableWidgetItem(QString::number(anOptimalExposureStack.getStackTotalNoise(), 'f', 2)));
803 resultStackTable->item(stackSummaryIndex, 3)->setTextAlignment(Qt::AlignRight);
804
805 double ratio = anOptimalExposureStack.getStackTime() / anOptimalExposureStack.getStackTotalNoise();
806 resultStackTable->setItem(stackSummaryIndex, 4, new QTableWidgetItem(QString::number(ratio, 'f', 2)));
807 resultStackTable->item(stackSummaryIndex, 4)->setTextAlignment(Qt::AlignRight);
808
809 resultStackTable->setRowHeight(stackSummaryIndex, 22);
810 /*
811 qCInfo(KSTARS_EKOS_CAPTURE) << "Stack info: Hours: " << anOptimalExposureStack.getPlannedTime()
812 << " Exposure Count: " << anOptimalExposureStack.getExposureCount()
813 << " Stack Time: " << anOptimalExposureStack.getStackTime()
814 << " Stack Total Noise: " << anOptimalExposureStack.getStackTotalNoise();
815 */
816
817 /*
818 2023-10 Add plot of the ratio of Total Noise to Stack Integration Time into new plot widget qCustomPlotIntegrationNoise
819 */
820
821 }
822 resultStackTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
823
824}
825
826
827void ExposureCalculatorDialog::calculateSubExposure(double aNoiseTolerance, double aSkyQualityValue,
829{
830
831 anOptimalSubExposureCalculator->setANoiseTolerance(aNoiseTolerance);
832 anOptimalSubExposureCalculator->setASkyQuality(aSkyQualityValue);
833 anOptimalSubExposureCalculator->setAFocalRatio(aFocalRatioValue);
834 anOptimalSubExposureCalculator->setAFilterCompensation(aFilterCompensationValue);
835 anOptimalSubExposureCalculator->setASelectedCameraReadMode(aSelectedReadMode);
836 anOptimalSubExposureCalculator->setASelectedGain(aSelectedGainValue);
837
838
839 // qCInfo(KSTARS_EKOS_CAPTURE) << "initializeSubExposureComputer";
840 // qCInfo(KSTARS_EKOS_CAPTURE) << "\taNoiseTolerance: " << aNoiseTolerance;
841 // qCInfo(KSTARS_EKOS_CAPTURE) << "\taSkyQualityValue: " << aSkyQualityValue;
842 // qCInfo(KSTARS_EKOS_CAPTURE) << "\taFocalRatioValue: " << aFocalRatioValue;
843 // qCInfo(KSTARS_EKOS_CAPTURE) << "\taFilterCompensation: (ignored) " << aFilterCompensationValue;
844 // qCInfo(KSTARS_EKOS_CAPTURE) << "\taSelectedGainValue: " << aSelectedGainValue;
845
846 anOptimalSubExposureCalculator->setAFilterCompensation(aFilterCompensationValue);
847
848 // qCInfo(KSTARS_EKOS_CAPTURE) << "Calculating... ";
849 // qCInfo(KSTARS_EKOS_CAPTURE) << "A Noise Tolerance " << anOptimalSubExposureCalculator->getANoiseTolerance();
850 // qCInfo(KSTARS_EKOS_CAPTURE) << "A Sky Quality " << anOptimalSubExposureCalculator->getASkyQuality();
851
852 // qCInfo(KSTARS_EKOS_CAPTURE) << "A Focal Ratio " << anOptimalSubExposureCalculator->getAFocalRatio();
853 // qCInfo(KSTARS_EKOS_CAPTURE) << "A Filter Compensation Value (ignored): " << anOptimalSubExposureCalculator->getAFilterCompensation();
854
855 // qCInfo(KSTARS_EKOS_CAPTURE) << "A Camera Gain Min " << anOptimalSubExposureCalculator->getImagingCameraData().getGainMin();
856 // qCInfo(KSTARS_EKOS_CAPTURE) << "A Camera Gain Max " << anOptimalSubExposureCalculator->getImagingCameraData().getGainMax();
857
858
859 OptimalExposure::CameraExposureEnvelope aCameraExposureEnvelope =
860 anOptimalSubExposureCalculator->calculateCameraExposureEnvelope();
861 // qCInfo(KSTARS_EKOS_CAPTURE) << "Exposure Envelope has a set of: " << aCameraExposureEnvelope.getASubExposureVector().size();
862 // qCInfo(KSTARS_EKOS_CAPTURE) << "Exposure Envelope has a minimum Exposure Time of " << aCameraExposureEnvelope.getExposureTimeMin();
863 // qCInfo(KSTARS_EKOS_CAPTURE) << "Exposure Envelope has a maximum Exposure Time of " << aCameraExposureEnvelope.getExposureTimeMax();
864
865
866 //OptimalExposure::OptimalExposureDetail subExposureDetail = anOptimalSubExposureCalculator->calculateSubExposureDetail(
867 // aSelectedGainValue);
868
869
870 aSubExposureDetail = anOptimalSubExposureCalculator->calculateSubExposureDetail();
871 // Get the exposure details into the ui
872 //ui->exposureCalculatonResult.
873
874 plotSubExposureEnvelope(ui, anOptimalSubExposureCalculator, &aSubExposureDetail);
875
876 if(ui->gainSelector->isEnabled())
877 {
878 // realignGainSlider();
879 ui->gainSelector->setMaximum(anOptimalSubExposureCalculator->getImagingCameraData().getGainMax());
880 ui->gainSelector->setMinimum(anOptimalSubExposureCalculator->getImagingCameraData().getGainMin());
881 }
882
883 ui->subExposureTime->setText(QString::number(aSubExposureDetail.getSubExposureTime(), 'f', 2));
884 ui->subPollutionElectrons->setText(QString::number(aSubExposureDetail.getExposurePollutionElectrons(), 'f', 0));
885 ui->subShotNoise->setText(QString::number(aSubExposureDetail.getExposureShotNoise(), 'f', 2));
886 ui->subTotalNoise->setText(QString::number(aSubExposureDetail.getExposureTotalNoise(), 'f', 2));
887
888
889 // qCInfo(KSTARS_EKOS_CAPTURE) << "Exposure Pollution Electrons: " << subExposureDetail.getExposurePollutionElectrons();
890 // qCInfo(KSTARS_EKOS_CAPTURE) << "Exposure Shot Noise: " << subExposureDetail.getExposureShotNoise();
891 // qCInfo(KSTARS_EKOS_CAPTURE) << "Exposure Total Noise: " << subExposureDetail.getExposureTotalNoise();
892
893
894 QTableWidget *resultStackTable = ui->exposureStackResult;
895 resultStackTable->setColumnCount(5);
896 resultStackTable->verticalHeader()->setVisible(false);
897
899 stackDetailHeaders << "Planned Hours" << "Exposure Count" << "Stack Time" << "Noise" << "Ratio";
900 resultStackTable->setHorizontalHeaderLabels(stackDetailHeaders);
901 resultStackTable->horizontalHeader()->setDefaultAlignment(Qt::AlignCenter | (Qt::Alignment)Qt::TextWordWrap);
902 resultStackTable->horizontalHeader()->setFixedHeight(32);
903
904 resultStackTable->horizontalHeaderItem(1)->setToolTip("Sub-exposure count in stacked image");
905 resultStackTable->horizontalHeaderItem(2)->setToolTip("Integration time of stacked image (seconds)");
906 resultStackTable->horizontalHeaderItem(3)->setToolTip("Total Noise in Stacked Image");
907 resultStackTable->horizontalHeaderItem(4)->setToolTip("Integration time to noise ratio (potential quality)");
908
909 /*
910 double initializedTargetNoiseRatio = ceil((100.0 * aSubExposureDetail.getSubExposureTime()) /
911 aSubExposureDetail.getExposureTotalNoise()) / 10.0;
912
913 // Reinitialize the time/noise input in the stack calculator to produce a stack of 100 images.
914 ui->targetNoiseRatio->setValue(initializedTargetNoiseRatio);
915
916 */
917 refreshStackTable(ui, &aSubExposureDetail);
918
919 plotIntegratedNoise(ui, &aSubExposureDetail);
920
921}
922
923
924
925void ExposureCalculatorDialog::refreshCameraSelector(Ui::ExposureCalculatorDialog *ui,
926 QStringList availableCameraFileNames, const QString aPreferredCameraId)
927{
928 // Present the aCameraId in a way that hopfully matches the cameraId from the driver
929 // but set the full path in the combo box data as a QVariant
930 // Retrievable as:
931 // QString filePath = ui->imagingCameraSelector->itemData(index).toString();
932
933 int preferredIndex = 0;
934 ui->imagingCameraSelector->clear();
935
936 /*
937 * 2023-10-05 Added sorting to the filelist, but the full path is included in this
938 * list, and since camera data can come from either the applicaton folder, or a user local folder
939 * the sort result can produce two groupings of sorted camera ids.
940 * In Linux, files from the user local folder will appear first in the QCombo.
941 */
943
944 foreach(QString filename, availableCameraFileNames)
945 {
946 QString aCameraId = OptimalExposure::FileUtilityCameraData::cameraDataFileNameToCameraId(filename);
947
948 // qCInfo(KSTARS_EKOS_CAPTURE) << "Camera Filename: " << filename << " Camera Id:" << aCameraId;
949
950 ui->imagingCameraSelector->addItem(aCameraId, filename);
951 if(aPreferredCameraId != nullptr && aPreferredCameraId.length() > 0)
952 {
953 if(aCameraId == aPreferredCameraId)
954 preferredIndex = ui->imagingCameraSelector->count() - 1;
955 }
956 }
957
958 ui->imagingCameraSelector->setCurrentIndex(preferredIndex);
959
960}
961
962
963ExposureCalculatorDialog::~ExposureCalculatorDialog()
964{
965 delete ui;
966}
967
968void ExposureCalculatorDialog::on_downloadCameraB_clicked()
969{
970 // User may want to add more camera files.
971 FileUtilityCameraDataDialog aCameraDownloadDialog(this, aPreferredCameraId);
972 aCameraDownloadDialog.setWindowModality(Qt::WindowModal);
974
975 // Using refresh is causing an error because the combobox->clear is
976 // making the selection change. Need to resolve this.
977 // but for now, if a user adds more cameras they will be available
978 // in the exposure calculator on the next start.
979 // refreshCameraSelector(ui, aPreferredCameraId);
980
981}
982
@ ssCircle
\enumimage{ssCircle.png} a circle
void currentIndexChanged(int index)
void valueChanged(double d)
Int toInt() const const
void valueChanged(int i)
qsizetype length() const const
QString number(double n, char format, int precision)
AlignCenter
TextWordWrap
WindowModal
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 Fri May 3 2024 11:49:50 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.