File indexing completed on 2024-05-12 03:44:32

0001 /*
0002     SPDX-FileCopyrightText: 2023 Joseph McGee <joseph.mcgee@sbcglobal.net>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 
0008 #include <QLoggingCategory>
0009 #include <QDir>
0010 #include <QVectorIterator>
0011 #include <QVariant>
0012 #include <QTableWidgetItem>
0013 #include "fileutilitycameradata.h"
0014 #include "optimalsubexposurecalculator.h"
0015 #include "optimalexposuredetail.h"
0016 #include <string>
0017 #include <iostream>
0018 #include <filesystem>
0019 #include "fileutilitycameradatadialog.h"
0020 #include "exposurecalculatordialog.h"
0021 #include "./ui_exposurecalculatordialog.h"
0022 #include <kspaths.h>
0023 #include <ekos_capture_debug.h>
0024 
0025 /*
0026  *
0027  *
0028  * https://www.astrobin.com/forum/c/astrophotography/deep-sky/robin-glover-talk-questioning-length-of-single-exposure/
0029  * http://astro.physics.uiowa.edu/~kaaret/2013f_29c137/Lab03_noise.html#:~:text=The%20read%20noise%20of%20the,removing%20hot%20and%20dead%20pixels
0030  *
0031  * Resource for DSLR read-noise:
0032  * https://www.photonstophotos.net/Charts/RN_ADU.htm
0033  *
0034  */
0035 
0036 OptimalExposure::OptimalExposureDetail
0037 aSubExposureDetail;  // Added during refactoring to simplify and better support the noise graph
0038 
0039 ExposureCalculatorDialog::ExposureCalculatorDialog(QWidget *parent,
0040         double aPreferredSkyQualityValue,
0041         double aPreferredFocalRatioValue,
0042         const QString &aPreferredCameraId) :
0043     QDialog(parent),
0044     aPreferredSkyQualityValue(aPreferredSkyQualityValue),
0045     aPreferredFocalRatioValue(aPreferredFocalRatioValue),
0046     aPreferredCameraId(aPreferredCameraId),
0047     ui(new Ui::ExposureCalculatorDialog)
0048 {
0049     ui->setupUi(this);
0050 
0051     ui->exposureDiffLabel->setText(QString("\u0394="));
0052 
0053     QStringList availableCameraFiles = OptimalExposure::FileUtilityCameraData::getAvailableCameraFilesList();
0054 
0055     if(availableCameraFiles.length() == 0)
0056     {
0057         // qCWarning(KSTARS_EKOS_CAPTURE) << "Exposure Calculator - No Camera data available, closing dialog";
0058         // QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection);
0059         qCWarning(KSTARS_EKOS_CAPTURE) << "Exposure Calculator - No Camera data available, opening camera data download dialog";
0060         FileUtilityCameraDataDialog aCameraDownloadDialog(this, aPreferredCameraId);
0061         aCameraDownloadDialog.setWindowModality(Qt::WindowModal);
0062         aCameraDownloadDialog.exec();
0063         // QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection);
0064         // Look again for files...
0065         availableCameraFiles = OptimalExposure::FileUtilityCameraData::getAvailableCameraFilesList();
0066 
0067     }
0068 
0069 
0070     if(availableCameraFiles.length() > 0)
0071     {
0072         ui->imagingCameraSelector->clear();
0073         refreshCameraSelector(ui, availableCameraFiles, aPreferredCameraId);
0074 
0075         ui->exposureCalculatorFrame->setEnabled(false);
0076 
0077         hideGainSelectionWidgets();
0078 
0079         ui->gainSelector->setMinimum(0);
0080         ui->gainSelector->setMaximum(100);
0081         ui->gainSelector->setValue(50);
0082 
0083         ui->indiFocalRatio->setMinimum(1.0);
0084         ui->indiFocalRatio->setMaximum(12.0);
0085         ui->indiFocalRatio->setSingleStep(0.1);
0086         // was coded to default as ui->indiFocalRatio->setValue(5.0);
0087         ui->indiFocalRatio->setValue(aPreferredFocalRatioValue);
0088 
0089         // Setup the "user" controls.
0090         ui->userSkyQuality->setMinimum(16.00);
0091         ui->userSkyQuality->setMaximum(22.00);
0092         ui->userSkyQuality->setSingleStep(0.01);
0093         // was coded ui->userSkyQuality->setValue(20.0);
0094         ui->userSkyQuality->setValue(aPreferredSkyQualityValue);
0095 
0096         ui->noiseTolerance->setMinimum(0.02);
0097         ui->noiseTolerance->setMaximum(500.00);
0098         ui->noiseTolerance->setSingleStep(0.25);
0099         ui->noiseTolerance->setValue(5.0);
0100 
0101         ui->filterBandwidth->setMinimum(2.8);
0102         ui->filterBandwidth->setMaximum(300);
0103         ui->filterBandwidth->setValue(300);
0104         ui->filterBandwidth->setSingleStep(0.1);
0105 
0106 
0107 
0108         /*
0109             Experimental compensation for filters on light the pollution calculation are a bit tricky.
0110             Part 1...
0111 
0112             An unfiltered camera may include some IR and UV, and be able to read a bandwidth of say 360nm (at a reasonably high QE %).
0113 
0114             But for simplicity, the filter compensation calculation will be based on the range for visible light, and use a nominal
0115             bandwidth of 300, (roughly the bandwidth of a typical luminance filter).
0116 
0117             The filter compensation factor that will be applied to light pollution will be the filter bandwidth (from the UI) / 300.
0118             This means that a typical luminance filter would produce a "nuetral" compensation of 1.0 (300 / 300).
0119 
0120             But the user interface will allow selection of wider bands for true "unfiltered" exposure calculations.  Example: by selecting a
0121             bandwith of 360, the light pollution compensation will 1.2, calculated as (360 / 300).  This is to recognize that light pollution
0122             may be entering the IR and UV range of an unfiltered camera sensor.
0123 
0124             A Luminance filter may only pass 300nm, so the filter compensaton value would be 1.0 (300 / 300)
0125             An RGB filter may only pass 100nm, so the filter compensaton value would be 0.3333 = (100 / 300)
0126             An SHO filter may only pass 3nm, so the filter compensaton value would be 0.0100 = (3 / 300)
0127 
0128             Filters will reduce bandwidth, but also slightly reduce tranmission within the range that they "pass".
0129             The values stated are only for demonstration and testing purposes, further research is needed.
0130 
0131         */
0132 
0133         // Set up plot colors of Sub Exposure (night friendly)
0134         ui->qCustomPlotSubExposure->setBackground(QBrush(Qt::black));
0135         ui->qCustomPlotSubExposure->xAxis->setBasePen(QPen(Qt::white, 1));
0136         ui->qCustomPlotSubExposure->yAxis->setBasePen(QPen(Qt::white, 1));
0137         ui->qCustomPlotSubExposure->xAxis->setTickPen(QPen(Qt::white, 1));
0138         ui->qCustomPlotSubExposure->yAxis->setTickPen(QPen(Qt::white, 1));
0139         ui->qCustomPlotSubExposure->xAxis->setSubTickPen(QPen(Qt::white, 1));
0140         ui->qCustomPlotSubExposure->yAxis->setSubTickPen(QPen(Qt::white, 1));
0141         ui->qCustomPlotSubExposure->xAxis->setTickLabelColor(Qt::white);
0142         ui->qCustomPlotSubExposure->yAxis->setTickLabelColor(Qt::white);
0143         ui->qCustomPlotSubExposure->xAxis->setLabelColor(Qt::white);
0144         ui->qCustomPlotSubExposure->yAxis->setLabelColor(Qt::white);
0145 
0146         ui->qCustomPlotSubExposure->xAxis->grid()->setPen(QPen(Qt::darkGray));
0147         ui->qCustomPlotSubExposure->yAxis->grid()->setPen(QPen(Qt::darkGray));
0148 
0149         ui->qCustomPlotSubExposure->xAxis->setLabel("Gain");
0150 
0151         ui->qCustomPlotSubExposure->yAxis->setLabel("Exposure Time");
0152 
0153         ui->qCustomPlotSubExposure->addGraph();
0154 
0155 
0156         // Set up plot colors of Integrated Image Noise (night friendly)
0157         ui->qCustomPlotIntegrationNoise->setBackground(QBrush(Qt::black));
0158         ui->qCustomPlotIntegrationNoise->xAxis->setBasePen(QPen(Qt::white, 1));
0159         ui->qCustomPlotIntegrationNoise->yAxis->setBasePen(QPen(Qt::white, 1));
0160         ui->qCustomPlotIntegrationNoise->xAxis->setTickPen(QPen(Qt::white, 1));
0161         ui->qCustomPlotIntegrationNoise->yAxis->setTickPen(QPen(Qt::white, 1));
0162         ui->qCustomPlotIntegrationNoise->xAxis->setSubTickPen(QPen(Qt::white, 1));
0163         ui->qCustomPlotIntegrationNoise->yAxis->setSubTickPen(QPen(Qt::white, 1));
0164         ui->qCustomPlotIntegrationNoise->xAxis->setTickLabelColor(Qt::white);
0165         ui->qCustomPlotIntegrationNoise->yAxis->setTickLabelColor(Qt::white);
0166         ui->qCustomPlotIntegrationNoise->xAxis->setLabelColor(Qt::white);
0167         ui->qCustomPlotIntegrationNoise->yAxis->setLabelColor(Qt::white);
0168 
0169         ui->qCustomPlotIntegrationNoise->xAxis->grid()->setPen(QPen(Qt::darkGray));
0170         ui->qCustomPlotIntegrationNoise->yAxis->grid()->setPen(QPen(Qt::darkGray));
0171 
0172         ui->qCustomPlotIntegrationNoise->addGraph(ui->qCustomPlotIntegrationNoise->xAxis, ui->qCustomPlotIntegrationNoise->yAxis);
0173         ui->qCustomPlotIntegrationNoise->graph(0)->setPen(QPen(Qt::yellow));
0174         ui->qCustomPlotIntegrationNoise->graph(0)->setName("Integration Time to Noise Ratio");
0175         ui->qCustomPlotIntegrationNoise->xAxis->setLabel("Stacked Exposures");
0176         ui->qCustomPlotIntegrationNoise->yAxis->setLabel("Noise Ratio");
0177 
0178         //        ui->qCustomPlotIntegrationNoise->addGraph(ui->qCustomPlotIntegrationNoise->xAxis, ui->qCustomPlotIntegrationNoise->yAxis);
0179         //        ui->qCustomPlotIntegrationNoise->graph(1)->setPen(QPen(Qt::green));
0180         //        ui->qCustomPlotIntegrationNoise->graph(1)->setName("Integration to Noise Ratio");
0181         //        ui->qCustomPlotIntegrationNoise->yAxis2->setLabel("Noise Ratio");
0182 
0183 
0184 
0185 
0186         connect(ui->imagingCameraSelector, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
0187                 &ExposureCalculatorDialog::applyInitialInputs);
0188 
0189         connect(ui->cameraReadModeSelector, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
0190                 &ExposureCalculatorDialog::handleUserAdjustment);
0191 
0192         connect(ui->gainSelector, QOverload<int>::of(&QSpinBox::valueChanged), this,
0193                 &ExposureCalculatorDialog::handleUserAdjustment);
0194 
0195         connect(ui->userSkyQuality, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
0196                 &ExposureCalculatorDialog::handleUserAdjustment);
0197 
0198         connect(ui->indiFocalRatio, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
0199                 &ExposureCalculatorDialog::handleUserAdjustment);
0200 
0201         connect(ui->filterBandwidth, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
0202                 &ExposureCalculatorDialog::handleUserAdjustment);
0203 
0204         connect(ui->noiseTolerance, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
0205                 &ExposureCalculatorDialog::handleUserAdjustment);
0206 
0207         connect(ui->gainISODiscreteSelector, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
0208                 &ExposureCalculatorDialog::handleUserAdjustment);
0209 
0210         connect(ui->targetNoiseRatio, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
0211                 &ExposureCalculatorDialog::handleStackCalculation);
0212 
0213         // Hide the gain selector frames (until a camera is selected)
0214         hideGainSelectionWidgets();
0215 
0216         applyInitialInputs();
0217 
0218     }
0219     /*
0220         else
0221         {
0222             // qCWarning(KSTARS_EKOS_CAPTURE) << "Exposure Calculator - No Camera data available, closing dialog";
0223             // QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection);
0224             qCWarning(KSTARS_EKOS_CAPTURE) << "Exposure Calculator - No Camera data available, opening camera data download dialog";
0225             FileUtilityCameraDataDialog aCameraDownloadDialog(this, aPreferredCameraId);
0226             aCameraDownloadDialog.setWindowModality(Qt::WindowModal);
0227             aCameraDownloadDialog.exec();
0228             QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection);
0229         }
0230     */
0231 }
0232 
0233 
0234 
0235 
0236 int ExposureCalculatorDialog::getGainSelection(OptimalExposure::GainSelectionType aGainSelectionType)
0237 {
0238     int aSelectedGain = 0;
0239     switch(aGainSelectionType)
0240     {
0241 
0242         case OptimalExposure::GAIN_SELECTION_TYPE_NORMAL:
0243             // aSelectedGain = ui->gainSlider->value();
0244             aSelectedGain = ui->gainSelector->value();
0245             break;
0246 
0247         case OptimalExposure::GAIN_SELECTION_TYPE_ISO_DISCRETE:
0248             // qCInfo(KSTARS_EKOS_CAPTURE) << " iso selector text: " << ui->isoDiscreteSelector->currentText();
0249             aSelectedGain = ui->gainISODiscreteSelector->currentText().toInt();
0250             break;
0251 
0252         case OptimalExposure::GAIN_SELECTION_TYPE_FIXED:
0253             // qCInfo(KSTARS_EKOS_CAPTURE) << "Fixed read-noise camera gain set to 0";
0254             aSelectedGain = 0;  // Fixed noise cameras have read noise data at 0 gain.
0255             break;
0256 
0257     }
0258 
0259     return(aSelectedGain);
0260 }
0261 
0262 
0263 QString skyQualityToBortleClassNumber(double anSQMValue)
0264 {
0265 
0266     QString aBortleClassNumber;
0267     if(anSQMValue < 18.38)
0268     {
0269         aBortleClassNumber = "8 to 9";
0270     }
0271     else if(anSQMValue < 18.94)
0272     {
0273         aBortleClassNumber = "7";
0274     }
0275     else if(anSQMValue < 19.50)
0276     {
0277         aBortleClassNumber = "6";
0278     }
0279     else if(anSQMValue < 20.49)
0280     {
0281         aBortleClassNumber = "5";
0282     }
0283     else if(anSQMValue < 21.69)
0284     {
0285         aBortleClassNumber = "4";
0286     }
0287     else if(anSQMValue < 21.89)
0288     {
0289         aBortleClassNumber = "3";
0290     }
0291     else if(anSQMValue < 21.99)
0292     {
0293         aBortleClassNumber = "2";
0294     }
0295     else aBortleClassNumber = "1";
0296 
0297     return(aBortleClassNumber);
0298 }
0299 
0300 // calculate a Bortle style color based on SQM
0301 QColor makeASkyQualityColor(double anSQMValue)
0302 {
0303     QColor aSkyBrightnessColor;
0304     int aHueDegree, aSaturation, aValue;
0305 
0306     if(anSQMValue < 18.32)  // White Zone
0307     {
0308         aHueDegree = 40;
0309         aSaturation = 0;    // Saturation must move from
0310         aValue = 254;       // Value must move from 254 to 240
0311     }
0312     else if(anSQMValue < 18.44)  // From White Zone at 18.32 transitioning to Red Zone at 18.44
0313     {
0314         aHueDegree = 0;     // Hue is Red,
0315         // Saturation must transition from 0 to 255 as SQM moves from 18.32 to 18.44
0316         aSaturation = (int)(255 * ((anSQMValue - (double)18.32) / (18.44 - 18.32)));
0317         aValue = 254;
0318     }
0319     else if(anSQMValue < 21.82 )
0320     {
0321         // In the color range transitions hue of Bortle can be approximated with a polynomial
0322         aHueDegree = 17.351411032 * pow(anSQMValue, 4)
0323                      - 1384.0773705 * pow(anSQMValue, 3)
0324                      + 41383.66777 * pow(anSQMValue, 2)
0325                      - 549664.28976 * anSQMValue
0326                      + 2736244.0733;
0327 
0328         if(aHueDegree < 0) aHueDegree = 0;
0329 
0330         aSaturation = 255;
0331         aValue = 240;
0332 
0333     }
0334     else if(anSQMValue < 21.92) // Transition from Blue to Dark Gray between 21.82 and 21.92
0335     {
0336         aHueDegree = 240;
0337         // Saturation must transition from 255 to 0
0338         aSaturation = (int) ((2550 * (21.92 - anSQMValue)));
0339         // Value must transition from 240 to 100
0340         aValue = (int)(100 + (1400 * (21.92 - anSQMValue)));
0341 
0342     }
0343     else if(anSQMValue < 21.99)  // Dark gray zone
0344     {
0345         aHueDegree = 240;
0346         aSaturation = 0;
0347         aValue = 100;
0348     }
0349     else // Black zone should only be 21.99 and up
0350     {
0351         aHueDegree = 240;
0352         aSaturation = 0;
0353         aValue = 0;
0354     }
0355 
0356     // qCInfo(KSTARS_EKOS_CAPTURE) << "Sky Quality Color Hue: " << aHueDegree;
0357     // qCInfo(KSTARS_EKOS_CAPTURE) << "Sky Quality Color Saturation: " << aSaturation;
0358     // qCInfo(KSTARS_EKOS_CAPTURE) << "Sky Quality Color Value: " << aValue;
0359 
0360     aSkyBrightnessColor.setHsv(aHueDegree, aSaturation, aValue);
0361 
0362     return(aSkyBrightnessColor);
0363 }
0364 
0365 
0366 void refreshSkyQualityPresentation(Ui::ExposureCalculatorDialog *ui, double aSkyQualityValue)
0367 {
0368 
0369     // qCInfo(KSTARS_EKOS_CAPTURE) << "\ta selected Sky Quality: " << aSkyQualityValue;
0370     QColor aSkyQualityColor = makeASkyQualityColor(aSkyQualityValue);
0371 
0372     ui->bortleScaleValue->setText(skyQualityToBortleClassNumber(aSkyQualityValue));
0373 
0374     // Update the skyQualityColor Widget
0375     QPalette pal = QPalette();
0376 
0377     pal.setColor(QPalette::Window, aSkyQualityColor);
0378     ui->skyQualityColor->setAutoFillBackground(true);
0379     ui->skyQualityColor->setPalette(pal);
0380     ui->skyQualityColor->show();
0381 
0382 }
0383 
0384 
0385 void ExposureCalculatorDialog::handleUserAdjustment()
0386 {
0387 
0388     // This test for enabled was needed because dynamic changes to a
0389     // combo box during initialization of the calculator were firing
0390     // this method and prematurely triggering a calculation which was
0391     // crashing because the initialization was incomplete.
0392 
0393     if(ui->exposureCalculatorFrame->isEnabled())
0394     {
0395         // Recalculate and refresh the graph, with changed inputs from the ui
0396         QString aSelectedImagingCamera = ui->imagingCameraSelector->itemText(ui->imagingCameraSelector->currentIndex());
0397 
0398         // ui->cameraReadModeSelector->currentData()
0399         int aSelectedReadMode = ui->cameraReadModeSelector->currentData().toInt();
0400 
0401         double aFocalRatioValue = ui->indiFocalRatio->value();
0402 
0403         double aSkyQualityValue = ui->userSkyQuality->value();
0404         refreshSkyQualityPresentation(ui, aSkyQualityValue);
0405 
0406         double aNoiseTolerance = ui->noiseTolerance->value();
0407 
0408         // double aFilterCompensationValue = 1.0;
0409         double aFilterCompensationValue = ((double)ui->filterBandwidth->value() / (double)300);
0410 
0411         int aSelectedGainValue = getGainSelection(anOptimalSubExposureCalculator->getImagingCameraData().getGainSelectionType());
0412 
0413 
0414         // double aSelectedGainValue = ui->gainSlider->value();
0415         // qCInfo(KSTARS_EKOS_CAPTURE) << "\ta selected gain: " << aSelectedGainValue;
0416 
0417         calculateSubExposure(aNoiseTolerance, aSkyQualityValue, aFocalRatioValue, aFilterCompensationValue, aSelectedReadMode,
0418                              aSelectedGainValue);
0419     }
0420 }
0421 
0422 void ExposureCalculatorDialog::hideGainSelectionWidgets()
0423 {
0424     ui->gainSelectionFrame->setEnabled(false);
0425     ui->gainSelectionFrame->setVisible(false);
0426 
0427     ui->gainSpinnerLabel->setVisible(false);
0428     ui->gainSelector->setVisible(false);
0429     ui->gainSelector->setEnabled(false);
0430 
0431     ui->gainISOSelectorLabel->setVisible(false);
0432     ui->gainISODiscreteSelector->setVisible(false);
0433     ui->gainISODiscreteSelector->setEnabled(false);
0434 
0435     ui->gainSelectionFixedLabel->setVisible(false);
0436 
0437     /*
0438     ui->gainSelectionISODiscreteFrame->setEnabled(false);
0439     ui->gainSelectionISODiscreteFrame->setVisible(false);
0440     ui->gainSelectionFixedFrame->setEnabled(false);
0441     ui->gainSelectionFixedFrame->setVisible(false);
0442     */
0443 }
0444 
0445 
0446 void ExposureCalculatorDialog::showGainSelectionNormalWidgets()
0447 {
0448     ui->gainSpinnerLabel->setVisible(true);
0449     ui->gainSelector->setEnabled(true);
0450     ui->gainSelector->setVisible(true);
0451 
0452     ui->gainSelectionFrame->setEnabled(true);
0453     ui->gainSelectionFrame->setVisible(true);
0454 }
0455 
0456 void ExposureCalculatorDialog::showGainSelectionISODiscreteWidgets()
0457 {
0458     ui->gainISOSelectorLabel->setVisible(true);
0459     ui->gainISODiscreteSelector->setEnabled(true);
0460     ui->gainISODiscreteSelector->setVisible(true);
0461 
0462     ui->gainSelectionFrame->setEnabled(true);
0463     ui->gainSelectionFrame->setVisible(true);
0464 }
0465 
0466 void ExposureCalculatorDialog::showGainSelectionFixedWidgets()
0467 {
0468     ui->gainSelectionFixedLabel->setVisible(true);
0469 
0470     ui->gainSelectionFrame->setEnabled(true);
0471     ui->gainSelectionFrame->setVisible(true);
0472 }
0473 
0474 
0475 void ExposureCalculatorDialog::applyInitialInputs()
0476 {
0477     ui->exposureCalculatorFrame->setEnabled(false);
0478 
0479     // QString aSelectedImagingCameraName = ui->imagingCameraSelector->itemText(ui->imagingCameraSelector->currentIndex());
0480     QString aSelectedImagingCameraFileName = ui->imagingCameraSelector->itemData(
0481                 ui->imagingCameraSelector->currentIndex()).toString();
0482 
0483 
0484     // qCInfo(KSTARS_EKOS_CAPTURE) << ui->cameraReadModeSelector->currentData();
0485 
0486     int aSelectedReadMode = 0;
0487 
0488     double aFocalRatioValue = ui->indiFocalRatio->value();
0489     double aSkyQualityValue = ui->userSkyQuality->value();
0490     refreshSkyQualityPresentation(ui, aSkyQualityValue);
0491 
0492     double aNoiseTolerance = ui->noiseTolerance->value();
0493 
0494     // double aFilterCompensationValue = 1.0;
0495     // double aFilterCompensationValue = ui->filterSelection->itemData(ui->filterSelection->currentIndex()).toDouble();
0496     double aFilterCompensationValue = ((double)ui->filterBandwidth->value() / (double)300);
0497 
0498     initializeSubExposureCalculator(aNoiseTolerance, aSkyQualityValue, aFocalRatioValue, aFilterCompensationValue,
0499                                     aSelectedImagingCameraFileName);
0500 
0501     int aSelectedGainValue = ui->gainSelector->value();
0502 
0503     calculateSubExposure(aNoiseTolerance, aSkyQualityValue, aFocalRatioValue, aFilterCompensationValue, aSelectedReadMode,
0504                          aSelectedGainValue);
0505 
0506     ui->exposureCalculatorFrame->setEnabled(true);
0507 
0508 }
0509 
0510 
0511 void plotIntegratedNoise(Ui::ExposureCalculatorDialog *ui,
0512                          OptimalExposure::OptimalExposureDetail *subExposureDetail)
0513 {
0514 
0515     ui->qCustomPlotIntegrationNoise->graph()->data()->clear();
0516 
0517     double aCoefficient = (subExposureDetail->getSubExposureTime() / subExposureDetail->getExposureTotalNoise());
0518     /*
0519         qCInfo(KSTARS_EKOS_CAPTURE) << "Noise Ratio Function: Noise Ratio = " << aCoefficient << " * Sqrt(Exposure Count)";
0520 
0521         qCInfo(KSTARS_EKOS_CAPTURE) << "Differential of Noise Ratio Function: = " << aCoefficient << " / (2 * Sqrt(Exposure Count)";
0522 
0523         qCInfo(KSTARS_EKOS_CAPTURE) << "Exposure Count Function (for desired Noise Ratio):"
0524                                     << "Exposure Count = (Noise Ratio / " << aCoefficient << ") ^2  = pow(Noise Ratio / " << aCoefficient << ", 2)";
0525     */
0526     double aTargetNoiseRatio = ui->targetNoiseRatio->value();
0527     // qCInfo(KSTARS_EKOS_CAPTURE) << "Target Noise Ratio: " << aTargetNoiseRatio;
0528 
0529     int aRequiredExposureCount = std::max(1, (int)(pow((aTargetNoiseRatio / aCoefficient), 2)));
0530     ui->exposureCount->setText(QString::number(aRequiredExposureCount));
0531 
0532     double aDifferential = aCoefficient / (2 * sqrt(aRequiredExposureCount));
0533     ui->exposureCountDifferential->setText(QString::number(aDifferential, 'f', 2));
0534 
0535 
0536     ui->qCustomPlotIntegrationNoise->graph()->data()->clear();
0537 
0538     // ui->qCustomPlotIntegrationNoise
0539     // ui->qCustomPlotIntegrationNoise->graph(0)->setData(ExposureCount, noise);
0540 
0541     QVector<double> xValue((aRequiredExposureCount * 2) + 1), yValue((aRequiredExposureCount * 2) + 1);
0542     for (int exposureCount = 1; exposureCount < (aRequiredExposureCount * 2) + 1; exposureCount++)
0543     {
0544         xValue[exposureCount] = exposureCount;
0545         yValue[exposureCount] = aCoefficient * pow(exposureCount, 0.5);
0546     }
0547 
0548     ui->qCustomPlotIntegrationNoise->graph(0)->setData(xValue, yValue);
0549 
0550     ui->qCustomPlotIntegrationNoise->xAxis->setRange(0, aRequiredExposureCount * 2);
0551     ui->qCustomPlotIntegrationNoise->yAxis->setRange(0, yValue[yValue.size() - 1]);
0552 
0553     // Also add a graph with a vertical line to show the computed integration
0554     ui->qCustomPlotIntegrationNoise->addGraph();
0555 
0556     QVector<double> selectedIntegrationSizeX(2), selectedIntegrationSizeY(2);
0557     selectedIntegrationSizeX[0] = aRequiredExposureCount;
0558     selectedIntegrationSizeY[0] = 0;
0559     selectedIntegrationSizeX[1] = aRequiredExposureCount;
0560     selectedIntegrationSizeY[1] = aTargetNoiseRatio;
0561     ui->qCustomPlotIntegrationNoise->graph(1)->setData(selectedIntegrationSizeX, selectedIntegrationSizeY);
0562 
0563     QPen penSelectedIntegrationSize;
0564     penSelectedIntegrationSize.setWidth(1);
0565     // penSelectedIntegrationSize.setColor(QColor(180, 0, 0));
0566     // On the black background need more contrast
0567     penSelectedIntegrationSize.setColor(QColor(240, 0, 0));
0568 
0569     ui->qCustomPlotIntegrationNoise->graph(1)->setPen(penSelectedIntegrationSize);
0570 
0571     ui->qCustomPlotIntegrationNoise->graph(1)->setScatterStyle(QCPScatterStyle::ssCircle);
0572 
0573     ui->qCustomPlotIntegrationNoise->graph()->rescaleAxes(true);
0574     ui->qCustomPlotIntegrationNoise->replot();
0575 
0576 }
0577 
0578 // Slot for adjustments made to desired noise ratio that require a refresh of the NR graph
0579 void ExposureCalculatorDialog::handleStackCalculation()
0580 {
0581     plotIntegratedNoise(ui, &aSubExposureDetail);
0582 }
0583 
0584 
0585 void plotSubExposureEnvelope(Ui::ExposureCalculatorDialog *ui,
0586                              OptimalExposure::OptimalSubExposureCalculator *anOptimalSubExposureCalculator,
0587                              OptimalExposure::OptimalExposureDetail *subExposureDetail)
0588 {
0589 
0590     OptimalExposure::CameraExposureEnvelope aCameraExposureEnvelope =
0591         anOptimalSubExposureCalculator->calculateCameraExposureEnvelope();
0592     // qCInfo(KSTARS_EKOS_CAPTURE) << "Exposure Envelope has a set of: " << aCameraExposureEnvelope.getASubExposureVector().size();
0593     // qCInfo(KSTARS_EKOS_CAPTURE) << "Exposure Envelope has a minimum Exposure Time of " << aCameraExposureEnvelope.getExposureTimeMin();
0594     // qCInfo(KSTARS_EKOS_CAPTURE) << "Exposure Envelope has a maximum Exposure Time of " << aCameraExposureEnvelope.getExposureTimeMax();
0595 
0596     // anOptimalSubExposureCalculator->getImagingCameraData()
0597 
0598     // Reset the graph axis (But maybe this was not necessary,
0599     ui->qCustomPlotSubExposure->xAxis->setRange(anOptimalSubExposureCalculator->getImagingCameraData().getGainMin(),
0600             anOptimalSubExposureCalculator->getImagingCameraData().getGainMax());
0601     // But for the exposure yAxis include a bit of a margin so that data is not encoaching on the axis.
0602     ui->qCustomPlotSubExposure->yAxis->setRange(aCameraExposureEnvelope.getExposureTimeMin() - 10,
0603             aCameraExposureEnvelope.getExposureTimeMax() + 10);
0604     ui->qCustomPlotSubExposure->replot();
0605 
0606     // Prepare for the exposure line plot, move the data to parallel arrays for the custom plotter
0607     QVector<double> gain(aCameraExposureEnvelope.getASubExposureVector().size()),
0608             exposuretime(aCameraExposureEnvelope.getASubExposureVector().size());
0609     for(int index = 0; index < aCameraExposureEnvelope.getASubExposureVector().size(); index++)
0610     {
0611         OptimalExposure::CalculatedGainSubExposureTime aGainExposureTime = aCameraExposureEnvelope.getASubExposureVector()[index];
0612         gain[index] = (double)aGainExposureTime.getSubExposureGain();
0613         exposuretime[index] = aGainExposureTime.getSubExposureTime();
0614     }
0615     ui->qCustomPlotSubExposure->graph()->data()->clear();
0616 
0617     ui->qCustomPlotSubExposure->graph(0)->setData(gain, exposuretime);
0618 
0619     // Also add a graph with a vertical line to show the selected gain...
0620     ui->qCustomPlotSubExposure->addGraph();
0621 
0622     QVector<double> selectedExposureX(2), selectedExposureY(2);
0623     selectedExposureX[0] = subExposureDetail->getSelectedGain();
0624     selectedExposureY[0] = 0;
0625     selectedExposureX[1] = subExposureDetail->getSelectedGain();
0626     selectedExposureY[1] = subExposureDetail->getSubExposureTime();
0627     ui->qCustomPlotSubExposure->graph(1)->setData(selectedExposureX, selectedExposureY);
0628 
0629     QPen penExposureEnvelope;
0630     penExposureEnvelope.setWidth(1);
0631     // penExposureEnvelope.setColor(QColor(0, 180, 180));
0632     // On the black background need more contrast
0633     penExposureEnvelope.setColor(QColor(0, 220, 220));
0634     ui->qCustomPlotSubExposure->graph(0)->setPen(penExposureEnvelope);
0635 
0636     QPen penSelectedExposure;
0637     penSelectedExposure.setWidth(1);
0638     // penSelectedExposure.setColor(QColor(180, 0, 0));
0639     // On the black background need more contrast
0640     penSelectedExposure.setColor(QColor(240, 0, 0));
0641 
0642     ui->qCustomPlotSubExposure->graph(1)->setPen(penSelectedExposure);
0643 
0644     ui->qCustomPlotSubExposure->graph(1)->setScatterStyle(QCPScatterStyle::ssCircle);
0645 
0646     // extend the x-axis slightly so that the markers aren't hidden at the extreme edges
0647     ui->qCustomPlotSubExposure->xAxis->setRange(anOptimalSubExposureCalculator->getImagingCameraData().getGainMin() - 5,
0648             anOptimalSubExposureCalculator->getImagingCameraData().getGainMax() + 5);
0649     // force the y-axis to start at 0, (sometimes the auto rescale was making the y-axis range start a negative value
0650     ui->qCustomPlotSubExposure->yAxis->setRange(0, aCameraExposureEnvelope.getExposureTimeMax());
0651 
0652     ui->qCustomPlotSubExposure->graph()->rescaleAxes(true);
0653     ui->qCustomPlotSubExposure->replot();
0654 
0655 }
0656 
0657 void ExposureCalculatorDialog::initializeSubExposureCalculator(double aNoiseTolerance, double aSkyQualityValue,
0658         double aFocalRatioValue, double aFilterCompensationValue, QString aSelectedImagingCameraName)
0659 {
0660     // qCInfo(KSTARS_EKOS_CAPTURE) << "initializeSubExposureComputer";
0661     // qCInfo(KSTARS_EKOS_CAPTURE) << "\taNoiseTolerance: " << aNoiseTolerance;
0662     // qCInfo(KSTARS_EKOS_CAPTURE) << "\taSkyQualityValue: " << aSkyQualityValue;
0663     // qCInfo(KSTARS_EKOS_CAPTURE) << "\taFocalRatioValue: " << aFocalRatioValue;
0664     // qCInfo(KSTARS_EKOS_CAPTURE) << "\taFilterCompensation: " << aFilterCompensationValue;
0665     // qCInfo(KSTARS_EKOS_CAPTURE) << "\taSelectedImagingCamera: " << aSelectedImagingCameraName;
0666 
0667     //    QVector<int> *aGainSelectionRange = new QVector<int>();
0668 
0669     //    QVector<OptimalExposure::CameraGainReadNoise> *aCameraGainReadNoiseVector
0670     //        = new QVector<OptimalExposure::CameraGainReadNoise>();
0671 
0672     //    QVector<OptimalExposure::CameraGainReadMode>  *aCameraGainReadModeVector
0673     //        = new QVector<OptimalExposure::CameraGainReadMode>();
0674 
0675     //    // Initialize with some default values before attempting to load from file
0676 
0677     //    anImagingCameraData = new OptimalExposure::ImagingCameraData(aSelectedImagingCameraName, OptimalExposure::SENSORTYPE_COLOR,
0678     //                OptimalExposure::GAIN_SELECTION_TYPE_NORMAL, *aGainSelectionRange, *aCameraGainReadModeVector);
0679 
0680     anImagingCameraData = new OptimalExposure::ImagingCameraData();
0681     // Load camera data from file
0682     OptimalExposure::FileUtilityCameraData::readCameraDataFile(aSelectedImagingCameraName, anImagingCameraData);
0683 
0684     // qCInfo(KSTARS_EKOS_CAPTURE) << "Loaded Imaging Camera Data for " + anImagingCameraData->getCameraId();
0685     // qCInfo(KSTARS_EKOS_CAPTURE) << "Camera Gain Selection Type " +  QString::number(anImagingCameraData->getGainSelectionType());
0686 
0687     // qCInfo(KSTARS_EKOS_CAPTURE) << "Camera Read Mode Vector Size " + QString::number(
0688     //  anImagingCameraData->getCameraGainReadModeVector().size());
0689 
0690     switch(anImagingCameraData->getSensorType())
0691     {
0692         case OptimalExposure::SENSORTYPE_COLOR:
0693             ui->SensorType->setText("Color");
0694             break;
0695         case OptimalExposure::SENSORTYPE_MONOCHROME:
0696             ui->SensorType->setText("Mono");
0697             break;
0698     }
0699 
0700     ui->cameraReadModeSelector->clear();
0701     foreach(OptimalExposure::CameraGainReadMode aReadMode, anImagingCameraData->getCameraGainReadModeVector())
0702     {
0703         QString readModeText = QString::number(aReadMode.getCameraGainReadModeNumber()) + " : " +
0704                                aReadMode.getCameraGainReadModeName();
0705         ui->cameraReadModeSelector->addItem(readModeText, aReadMode.getCameraGainReadModeNumber());
0706     }
0707     if(anImagingCameraData->getCameraGainReadModeVector().size() > 1)
0708     {
0709         ui->cameraReadModeSelector->setEnabled(true);
0710     }
0711     else
0712     {
0713         ui->cameraReadModeSelector->setEnabled(false);
0714     }
0715 
0716 
0717     // qCInfo(KSTARS_EKOS_CAPTURE) << "Camera Gain Read-Noise Vector Size "
0718     // + QString::number(anImagingCameraData->getCameraGainReadNoiseVector().size());
0719 
0720 
0721 
0722     switch( anImagingCameraData->getGainSelectionType() )
0723     {
0724         case OptimalExposure::GAIN_SELECTION_TYPE_FIXED:
0725             // qCInfo(KSTARS_EKOS_CAPTURE) << "Gain Selection Type: GAIN_SELECTION_TYPE_FIXED";
0726             hideGainSelectionWidgets();
0727             showGainSelectionFixedWidgets();
0728 
0729             break;
0730 
0731         case OptimalExposure::GAIN_SELECTION_TYPE_ISO_DISCRETE:
0732             // qCInfo(KSTARS_EKOS_CAPTURE) << "Gain Selection Type: GAIN_SELECTION_TYPE_ISO_DISCRETE";
0733             hideGainSelectionWidgets();
0734 
0735             ui->gainISODiscreteSelector->clear();
0736             // Load the ISO Combo from the camera data
0737             foreach(int isoSetting, anImagingCameraData->getGainSelectionRange())
0738             {
0739                 ui->gainISODiscreteSelector->addItem(QString::number(isoSetting));
0740             }
0741             ui->gainISODiscreteSelector->setCurrentIndex(0);
0742 
0743             // qCInfo(KSTARS_EKOS_CAPTURE) << "Camera Data Gain min " +  QString::number(anImagingCameraData->getGainMin());
0744             // qCInfo(KSTARS_EKOS_CAPTURE) << "Camera Data Gain max " +  QString::number(anImagingCameraData->getGainMax());
0745 
0746             showGainSelectionISODiscreteWidgets();
0747 
0748             break;
0749 
0750         case OptimalExposure::GAIN_SELECTION_TYPE_NORMAL:
0751             // qCInfo(KSTARS_EKOS_CAPTURE) << "Gain Selection Type: GAIN_SELECTION_TYPE_NORMAL";
0752 
0753             hideGainSelectionWidgets();
0754             showGainSelectionNormalWidgets();
0755             // qCInfo(KSTARS_EKOS_CAPTURE) << "Camera Data Gain min " +  QString::number(anImagingCameraData->getGainMin());
0756             // qCInfo(KSTARS_EKOS_CAPTURE) << "Camera Data Gain max " +  QString::number(anImagingCameraData->getGainMax());
0757             break;
0758 
0759     }
0760 
0761     anOptimalSubExposureCalculator = new OptimalExposure::OptimalSubExposureCalculator(aNoiseTolerance, aSkyQualityValue,
0762             aFocalRatioValue, aFilterCompensationValue, *anImagingCameraData);
0763 
0764     // qCInfo(KSTARS_EKOS_CAPTURE) << "Calculating... ";
0765     // qCInfo(KSTARS_EKOS_CAPTURE) << "A Noise Tolerance " << anOptimalSubExposureCalculator->getANoiseTolerance();
0766     // qCInfo(KSTARS_EKOS_CAPTURE) << "A Sky Quality " << anOptimalSubExposureCalculator->getASkyQuality();
0767 
0768     // qCInfo(KSTARS_EKOS_CAPTURE) << "A Focal Ratio " << anOptimalSubExposureCalculator->getAFocalRatio();
0769     // qCInfo(KSTARS_EKOS_CAPTURE) << "A Filter Compensation Value (ignored): " << anOptimalSubExposureCalculator->getAFilterCompensation();
0770 
0771     // qCInfo(KSTARS_EKOS_CAPTURE) << "A Camera Gain Min " << anOptimalSubExposureCalculator->getImagingCameraData().getGainMin();
0772     // qCInfo(KSTARS_EKOS_CAPTURE) << "A Camera Gain Max " << anOptimalSubExposureCalculator->getImagingCameraData().getGainMax();
0773 
0774 }
0775 
0776 
0777 void refreshStackTable(Ui::ExposureCalculatorDialog *ui,
0778                        OptimalExposure::OptimalExposureDetail *subExposureDetail)
0779 {
0780     QTableWidget *resultStackTable = ui->exposureStackResult;
0781 
0782     int stackSummarySize =  subExposureDetail->getStackSummary().size();
0783     resultStackTable->setRowCount(stackSummarySize);
0784 
0785     for(int stackSummaryIndex = 0; stackSummaryIndex < stackSummarySize; stackSummaryIndex++)
0786     {
0787         OptimalExposure::OptimalExposureStack anOptimalExposureStack = subExposureDetail->getStackSummary()[stackSummaryIndex];
0788 
0789         resultStackTable->setItem(stackSummaryIndex, 0,
0790                                   new QTableWidgetItem(QString::number(anOptimalExposureStack.getPlannedTime())));
0791         resultStackTable->item(stackSummaryIndex, 0)->setTextAlignment(Qt::AlignCenter);
0792 
0793         resultStackTable->setItem(stackSummaryIndex, 1,
0794                                   new QTableWidgetItem(QString::number(anOptimalExposureStack.getExposureCount())));
0795         resultStackTable->item(stackSummaryIndex, 1)->setTextAlignment(Qt::AlignRight);
0796 
0797         resultStackTable->setItem(stackSummaryIndex, 2,
0798                                   new QTableWidgetItem(QString::number(anOptimalExposureStack.getStackTime())));
0799         resultStackTable->item(stackSummaryIndex, 2)->setTextAlignment(Qt::AlignRight);
0800 
0801         resultStackTable->setItem(stackSummaryIndex, 3,
0802                                   new QTableWidgetItem(QString::number(anOptimalExposureStack.getStackTotalNoise(), 'f', 2)));
0803         resultStackTable->item(stackSummaryIndex, 3)->setTextAlignment(Qt::AlignRight);
0804 
0805         double ratio = anOptimalExposureStack.getStackTime() / anOptimalExposureStack.getStackTotalNoise();
0806         resultStackTable->setItem(stackSummaryIndex, 4, new QTableWidgetItem(QString::number(ratio, 'f', 2)));
0807         resultStackTable->item(stackSummaryIndex, 4)->setTextAlignment(Qt::AlignRight);
0808 
0809         resultStackTable->setRowHeight(stackSummaryIndex, 22);
0810         /*
0811                 qCInfo(KSTARS_EKOS_CAPTURE) << "Stack info: Hours: " << anOptimalExposureStack.getPlannedTime()
0812                                             << " Exposure Count: " << anOptimalExposureStack.getExposureCount()
0813                                             << " Stack Time: " << anOptimalExposureStack.getStackTime()
0814                                             << " Stack Total Noise: " << anOptimalExposureStack.getStackTotalNoise();
0815         */
0816 
0817         /*
0818              2023-10 Add plot of the ratio of Total Noise to Stack Integration Time into new plot widget qCustomPlotIntegrationNoise
0819         */
0820 
0821     }
0822     resultStackTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
0823 
0824 }
0825 
0826 
0827 void ExposureCalculatorDialog::calculateSubExposure(double aNoiseTolerance, double aSkyQualityValue,
0828         double aFocalRatioValue, double aFilterCompensationValue, int aSelectedReadMode, int aSelectedGainValue)
0829 {
0830 
0831     anOptimalSubExposureCalculator->setANoiseTolerance(aNoiseTolerance);
0832     anOptimalSubExposureCalculator->setASkyQuality(aSkyQualityValue);
0833     anOptimalSubExposureCalculator->setAFocalRatio(aFocalRatioValue);
0834     anOptimalSubExposureCalculator->setAFilterCompensation(aFilterCompensationValue);
0835     anOptimalSubExposureCalculator->setASelectedCameraReadMode(aSelectedReadMode);
0836     anOptimalSubExposureCalculator->setASelectedGain(aSelectedGainValue);
0837 
0838 
0839     // qCInfo(KSTARS_EKOS_CAPTURE) << "initializeSubExposureComputer";
0840     // qCInfo(KSTARS_EKOS_CAPTURE) << "\taNoiseTolerance: " << aNoiseTolerance;
0841     // qCInfo(KSTARS_EKOS_CAPTURE) << "\taSkyQualityValue: " << aSkyQualityValue;
0842     // qCInfo(KSTARS_EKOS_CAPTURE) << "\taFocalRatioValue: " << aFocalRatioValue;
0843     // qCInfo(KSTARS_EKOS_CAPTURE) << "\taFilterCompensation: (ignored) " << aFilterCompensationValue;
0844     // qCInfo(KSTARS_EKOS_CAPTURE) << "\taSelectedGainValue: " << aSelectedGainValue;
0845 
0846     anOptimalSubExposureCalculator->setAFilterCompensation(aFilterCompensationValue);
0847 
0848     // qCInfo(KSTARS_EKOS_CAPTURE) << "Calculating... ";
0849     // qCInfo(KSTARS_EKOS_CAPTURE) << "A Noise Tolerance " << anOptimalSubExposureCalculator->getANoiseTolerance();
0850     // qCInfo(KSTARS_EKOS_CAPTURE) << "A Sky Quality " << anOptimalSubExposureCalculator->getASkyQuality();
0851 
0852     // qCInfo(KSTARS_EKOS_CAPTURE) << "A Focal Ratio " << anOptimalSubExposureCalculator->getAFocalRatio();
0853     // qCInfo(KSTARS_EKOS_CAPTURE) << "A Filter Compensation Value (ignored): " << anOptimalSubExposureCalculator->getAFilterCompensation();
0854 
0855     // qCInfo(KSTARS_EKOS_CAPTURE) << "A Camera Gain Min " << anOptimalSubExposureCalculator->getImagingCameraData().getGainMin();
0856     // qCInfo(KSTARS_EKOS_CAPTURE) << "A Camera Gain Max " << anOptimalSubExposureCalculator->getImagingCameraData().getGainMax();
0857 
0858 
0859     OptimalExposure::CameraExposureEnvelope aCameraExposureEnvelope =
0860         anOptimalSubExposureCalculator->calculateCameraExposureEnvelope();
0861     // qCInfo(KSTARS_EKOS_CAPTURE) << "Exposure Envelope has a set of: " << aCameraExposureEnvelope.getASubExposureVector().size();
0862     // qCInfo(KSTARS_EKOS_CAPTURE) << "Exposure Envelope has a minimum Exposure Time of " << aCameraExposureEnvelope.getExposureTimeMin();
0863     // qCInfo(KSTARS_EKOS_CAPTURE) << "Exposure Envelope has a maximum Exposure Time of " << aCameraExposureEnvelope.getExposureTimeMax();
0864 
0865 
0866     //OptimalExposure::OptimalExposureDetail subExposureDetail = anOptimalSubExposureCalculator->calculateSubExposureDetail(
0867     //            aSelectedGainValue);
0868 
0869 
0870     aSubExposureDetail = anOptimalSubExposureCalculator->calculateSubExposureDetail();
0871     // Get the exposure details into the ui
0872     //ui->exposureCalculatonResult.
0873 
0874     plotSubExposureEnvelope(ui, anOptimalSubExposureCalculator, &aSubExposureDetail);
0875 
0876     if(ui->gainSelector->isEnabled())
0877     {
0878         // realignGainSlider();
0879         ui->gainSelector->setMaximum(anOptimalSubExposureCalculator->getImagingCameraData().getGainMax());
0880         ui->gainSelector->setMinimum(anOptimalSubExposureCalculator->getImagingCameraData().getGainMin());
0881     }
0882 
0883     ui->subExposureTime->setText(QString::number(aSubExposureDetail.getSubExposureTime(), 'f', 2));
0884     ui->subPollutionElectrons->setText(QString::number(aSubExposureDetail.getExposurePollutionElectrons(), 'f', 0));
0885     ui->subShotNoise->setText(QString::number(aSubExposureDetail.getExposureShotNoise(), 'f', 2));
0886     ui->subTotalNoise->setText(QString::number(aSubExposureDetail.getExposureTotalNoise(), 'f', 2));
0887 
0888 
0889     // qCInfo(KSTARS_EKOS_CAPTURE) << "Exposure Pollution Electrons: " << subExposureDetail.getExposurePollutionElectrons();
0890     // qCInfo(KSTARS_EKOS_CAPTURE) << "Exposure Shot Noise: " << subExposureDetail.getExposureShotNoise();
0891     // qCInfo(KSTARS_EKOS_CAPTURE) << "Exposure Total Noise: " << subExposureDetail.getExposureTotalNoise();
0892 
0893 
0894     QTableWidget *resultStackTable = ui->exposureStackResult;
0895     resultStackTable->setColumnCount(5);
0896     resultStackTable->verticalHeader()->setVisible(false);
0897 
0898     QStringList stackDetailHeaders;
0899     stackDetailHeaders << "Planned Hours" << "Exposure Count" << "Stack Time" << "Noise" << "Ratio";
0900     resultStackTable->setHorizontalHeaderLabels(stackDetailHeaders);
0901     resultStackTable->horizontalHeader()->setDefaultAlignment(Qt::AlignCenter | (Qt::Alignment)Qt::TextWordWrap);
0902     resultStackTable->horizontalHeader()->setFixedHeight(32);
0903 
0904     resultStackTable->horizontalHeaderItem(1)->setToolTip("Sub-exposure count in stacked image");
0905     resultStackTable->horizontalHeaderItem(2)->setToolTip("Integration time of stacked image (seconds)");
0906     resultStackTable->horizontalHeaderItem(3)->setToolTip("Total Noise in Stacked Image");
0907     resultStackTable->horizontalHeaderItem(4)->setToolTip("Integration time to noise ratio (potential quality)");
0908 
0909     /*
0910     double initializedTargetNoiseRatio = ceil((100.0 * aSubExposureDetail.getSubExposureTime()) /
0911                                          aSubExposureDetail.getExposureTotalNoise()) / 10.0;
0912 
0913     // Reinitialize the time/noise input in the stack calculator to produce a stack of 100 images.
0914     ui->targetNoiseRatio->setValue(initializedTargetNoiseRatio);
0915 
0916     */
0917     refreshStackTable(ui, &aSubExposureDetail);
0918 
0919     plotIntegratedNoise(ui, &aSubExposureDetail);
0920 
0921 }
0922 
0923 
0924 
0925 void ExposureCalculatorDialog::refreshCameraSelector(Ui::ExposureCalculatorDialog *ui,
0926         QStringList availableCameraFileNames, const QString aPreferredCameraId)
0927 {
0928     // Present the aCameraId in a way that hopfully matches the cameraId from the driver
0929     // but set the full path in the combo box data as a QVariant
0930     // Retrievable as:
0931     // QString filePath = ui->imagingCameraSelector->itemData(index).toString();
0932 
0933     int preferredIndex = 0;
0934     ui->imagingCameraSelector->clear();
0935 
0936     /*
0937      *  2023-10-05 Added sorting to the filelist, but the full path is included in this
0938      *  list, and since camera data can come from either the applicaton folder, or a user local folder
0939      *  the sort result can produce two groupings of sorted camera ids.
0940      *  In Linux, files from the user local folder will appear first in the QCombo.
0941     */
0942     availableCameraFileNames.sort();
0943 
0944     foreach(QString filename, availableCameraFileNames)
0945     {
0946         QString aCameraId = OptimalExposure::FileUtilityCameraData::cameraDataFileNameToCameraId(filename);
0947 
0948         // qCInfo(KSTARS_EKOS_CAPTURE) << "Camera Filename: " << filename << " Camera Id:" << aCameraId;
0949 
0950         ui->imagingCameraSelector->addItem(aCameraId, filename);
0951         if(aPreferredCameraId != nullptr && aPreferredCameraId.length() > 0)
0952         {
0953             if(aCameraId == aPreferredCameraId)
0954                 preferredIndex = ui->imagingCameraSelector->count() - 1;
0955         }
0956     }
0957 
0958     ui->imagingCameraSelector->setCurrentIndex(preferredIndex);
0959 
0960 }
0961 
0962 
0963 ExposureCalculatorDialog::~ExposureCalculatorDialog()
0964 {
0965     delete ui;
0966 }
0967 
0968 void ExposureCalculatorDialog::on_downloadCameraB_clicked()
0969 {
0970     // User may want to add more camera files.
0971     FileUtilityCameraDataDialog aCameraDownloadDialog(this, aPreferredCameraId);
0972     aCameraDownloadDialog.setWindowModality(Qt::WindowModal);
0973     aCameraDownloadDialog.exec();
0974 
0975     // Using refresh is causing an error because the combobox->clear is
0976     // making the selection change.  Need to resolve this.
0977     // but for now, if a user adds more cameras they will be available
0978     // in the exposure calculator on the next start.
0979     // refreshCameraSelector(ui, aPreferredCameraId);
0980 
0981 }
0982