6#include "cameraprocess.h"
7#include "capturedeviceadaptor.h"
8#include "refocusstate.h"
9#include "sequencejob.h"
10#include "sequencequeue.h"
11#include "ekos/manager.h"
12#include "ekos/auxiliary/darklibrary.h"
13#include "ekos/auxiliary/darkprocessor.h"
14#include "ekos/auxiliary/opticaltrainmanager.h"
15#include "ekos/auxiliary/profilesettings.h"
16#include "ekos/guide/guide.h"
17#include "indi/indilistener.h"
18#include "indi/indirotator.h"
19#include "indi/blobmanager.h"
20#include "indi/indilightbox.h"
21#include "ksmessagebox.h"
25#include "fitsviewer/fitsdata.h"
26#include "fitsviewer/fitstab.h"
28#include "fitsviewer/fitsviewer.h"
30#include "ksnotification.h"
31#include <ekos_capture_debug.h>
33#ifdef HAVE_STELLARSOLVER
34#include "ekos/auxiliary/stellarsolverprofileeditor.h"
42 setObjectName(
"CameraProcess");
43 m_State = newModuleState;
44 m_DeviceAdaptor = newDeviceAdaptor;
47 connect(devices().data(), &CaptureDeviceAdaptor::newCamera,
this, &CameraProcess::selectCamera);
52 state()->downloadProgressTimer().setInterval(100);
56 m_DarkProcessor =
new DarkProcessor(
this);
57 connect(m_DarkProcessor, &DarkProcessor::newLog,
this, &CameraProcess::newLog);
58 connect(m_DarkProcessor, &DarkProcessor::darkFrameCompleted,
this, &CameraProcess::darkFrameCompleted);
63 this, &CameraProcess::scriptFinished);
67 emit newLog(m_CaptureScript.errorString());
73 emit newLog(m_CaptureScript.readAllStandardError());
78 emit newLog(m_CaptureScript.readAllStandardOutput());
84 if (devices()->mount() && devices()->mount() == device)
86 updateTelescopeInfo();
90 if (devices()->mount())
91 devices()->mount()->disconnect(state().data());
93 devices()->setMount(device);
95 if (!devices()->mount())
98 devices()->mount()->disconnect(
this);
101 updateTelescopeInfo();
108 if ((devices()->rotator() == device) && (device !=
nullptr))
112 if (devices()->mount())
114 if (devices()->rotator())
115 devices()->rotator()->disconnect(
this);
118 state()->isInitialized[CameraState::ACTION_ROTATOR] =
false;
122 Manager::Instance()->createRotatorController(device);
123 connect(devices().data(), &CaptureDeviceAdaptor::rotatorReverseToggled,
this, &CameraProcess::rotatorReverseToggled,
126 devices()->setRotator(device);
134 if (devices()->dustCap() && devices()->dustCap() == device)
137 devices()->setDustCap(device);
138 state()->setDustCapState(CameraState::CAP_UNKNOWN);
147 if (devices()->lightBox() == device)
150 devices()->setLightBox(device);
151 state()->setLightBoxLightState(CameraState::CAP_LIGHT_UNKNOWN);
158 if (devices()->dome() == device)
161 devices()->setDome(device);
168 if (devices()->getActiveCamera() == device)
173 disconnect(activeCamera(), &ISD::Camera::newImage,
this, &CameraProcess::showFITSPreview);
175 devices()->setActiveCamera(device);
178 if (state()->getCaptureTimeout().isActive() && state()->getCaptureState() ==
CAPTURE_CAPTURING)
183 connect(activeCamera(), &ISD::Camera::newImage,
this, &CameraProcess::showFITSPreview);
189void CameraProcess::toggleVideo(
bool enabled)
191 if (devices()->getActiveCamera() ==
nullptr)
194 if (devices()->getActiveCamera()->isBLOBEnabled() ==
false)
196 if (Options::guiderType() != Guide::GUIDE_INTERNAL)
197 devices()->getActiveCamera()->setBLOBEnabled(
true);
203 devices()->getActiveCamera()->setBLOBEnabled(
true);
204 devices()->getActiveCamera()->setVideoStreamEnabled(enabled);
207 KSMessageBox::Instance()->questionYesNo(
i18n(
"Image transfer is disabled for this camera. Would you like to enable it?"),
208 i18n(
"Image Transfer"), 15);
214 devices()->getActiveCamera()->setVideoStreamEnabled(enabled);
218void CameraProcess::toggleSequence()
220 const CaptureState capturestate = state()->getCaptureState();
227 emit newLog(
i18n(
"Sequence resumed."));
230 switch (state()->getContinueAction())
232 case CameraState::CONTINUE_ACTION_CAPTURE_COMPLETE:
235 case CameraState::CONTINUE_ACTION_NEXT_EXPOSURE:
244 startNextPendingJob();
252void CameraProcess::startNextPendingJob()
254 if (state()->allJobs().count() > 0)
256 SequenceJob *nextJob = findNextPendingJob();
257 if (nextJob !=
nullptr)
263 emit newLog(
i18n(
"No pending jobs found. Please add a job to the sequence queue."));
273void CameraProcess::jobCreated(SequenceJob *newJob)
275 if (newJob ==
nullptr)
277 emit newLog(
i18n(
"No new job created."));
281 switch (newJob->jobType())
283 case SequenceJob::JOBTYPE_BATCH:
284 startNextPendingJob();
286 case SequenceJob::JOBTYPE_PREVIEW:
287 state()->setActiveJob(newJob);
296void CameraProcess::capturePreview(
bool loop)
298 if (state()->getFocusState() >= FOCUS_PROGRESS)
300 emit newLog(
i18n(
"Cannot capture while focus module is busy."));
302 else if (activeJob() ==
nullptr)
304 if (loop && !state()->isLooping())
306 state()->setLooping(
true);
307 emit newLog(
i18n(
"Starting framing..."));
310 emit createJob(SequenceJob::JOBTYPE_PREVIEW);
315 prepareJob(activeJob());
323 state()->resetAlignmentRetries();
327 state()->getCaptureTimeout().stop();
328 state()->getCaptureDelayTimer().stop();
329 if (activeJob() !=
nullptr)
331 if (activeJob()->getStatus() == JOB_BUSY)
337 stopText =
i18n(
"CCD capture suspended");
338 resetJobStatus(JOB_BUSY);
342 stopText =
i18n(
"CCD capture complete");
343 resetJobStatus(JOB_DONE);
347 stopText = state()->isLooping() ?
i18n(
"Framing stopped") :
i18n(
"CCD capture stopped");
348 resetJobStatus(JOB_ABORTED);
352 stopText =
i18n(
"CCD capture stopped");
353 resetJobStatus(JOB_IDLE);
356 emit captureAborted(activeJob()->getCoreProperty(SequenceJob::SJ_Exposure).toDouble());
357 KSNotification::event(
QLatin1String(
"CaptureFailed"), stopText, KSNotification::Capture, KSNotification::Alert);
358 emit newLog(stopText);
359 activeJob()->abort();
360 if (activeJob()->jobType() != SequenceJob::JOBTYPE_PREVIEW)
362 int index = state()->allJobs().indexOf(activeJob());
363 state()->changeSequenceValue(index,
"Status",
"Aborted");
364 emit updateJobTable(activeJob());
369 if (activeJob()->jobType() != SequenceJob::JOBTYPE_PREVIEW)
373 else if (activeJob()->getCalibrationStage() == SequenceJobState::CAL_CALIBRATION)
379 state()->allJobs().removeOne(activeJob());
381 activeJob()->deleteLater();
383 state()->setActiveJob(
nullptr);
391 state()->setCaptureState(targetState);
393 state()->setLooping(
false);
394 state()->setBusy(
false);
396 state()->getCaptureDelayTimer().stop();
398 state()->setActiveJob(
nullptr);
401 if (devices()->lightBox() && state()->lightBoxLightEnabled())
403 state()->setLightBoxLightEnabled(
false);
404 devices()->lightBox()->setLightEnabled(
false);
411 if (devices()->getActiveCamera() && devices()->getActiveChip()
412 && devices()->getActiveCamera()->isFastExposureEnabled())
413 devices()->getActiveChip()->abortExposure();
416 emit captureStopped();
419void CameraProcess::pauseCapturing()
426 emit newLog(
i18n(
"Pausing only possible while frame capture is running."));
427 qCInfo(KSTARS_EKOS_CAPTURE) <<
"Pause button pressed while not capturing.";
431 state()->setContinueAction(CameraState::CONTINUE_ACTION_NONE);
433 emit newLog(
i18n(
"Sequence shall be paused after current exposure is complete."));
436void CameraProcess::startJob(SequenceJob *job)
438 state()->initCapturePreparation();
442void CameraProcess::prepareJob(SequenceJob * job)
444 state()->setActiveJob(job);
448 if (job->jobType() == SequenceJob::JOBTYPE_PREVIEW && Options::useFITSViewer() ==
false
449 && Options::useSummaryPreview() ==
false)
455 Options::setUseFITSViewer(
true);
462 activeJob()->abort();
464 KSMessageBox::Instance()->questionYesNo(
i18n(
"No view available for previews. Enable FITS viewer?"),
465 i18n(
"Display preview"), 15);
470 if (state()->isLooping() ==
false)
471 qCDebug(KSTARS_EKOS_CAPTURE) <<
"Preparing capture job" << job->getSignature() <<
"for execution.";
473 if (activeJob()->jobType() != SequenceJob::JOBTYPE_PREVIEW)
477 if (activeCamera()->getUploadMode() != ISD::Camera::UPLOAD_LOCAL)
478 state()->setNextSequenceID(1);
484 QString signature = activeJob()->getSignature();
490 state()->checkSeqBoundary();
510 int count = state()->capturedFramesCount(signature);
515 for (
auto &a_job : state()->allJobs())
516 if (a_job == activeJob())
518 else if (a_job->getSignature() == activeJob()->getSignature())
519 count -= a_job->getCompleted();
522 updatedCaptureCompleted(count);
526 else if (state()->hasCapturedFramesMap())
529 updatedCaptureCompleted(0);
535 else if (state()->ignoreJobProgress()
536 && activeJob()->getJobProgressIgnored() ==
false)
538 activeJob()->setJobProgressIgnored(
true);
539 updatedCaptureCompleted(0);
544 if (activeJob()->getCoreProperty(SequenceJob::SJ_Count).toInt() <=
545 activeJob()->getCompleted())
547 updatedCaptureCompleted(activeJob()->getCoreProperty(
548 SequenceJob::SJ_Count).toInt());
549 emit newLog(
i18n(
"Job requires %1-second %2 images, has already %3/%4 captures and does not need to run.",
550 QString(
"%L1").arg(job->getCoreProperty(SequenceJob::SJ_Exposure).
toDouble(), 0,
'f', 3),
551 job->getCoreProperty(SequenceJob::SJ_Filter).
toString(),
552 activeJob()->getCompleted(),
553 activeJob()->getCoreProperty(SequenceJob::SJ_Count).toInt()));
554 processJobCompletion2();
562 emit newLog(
i18n(
"Job requires %1-second %2 images, has %3/%4 frames captured and will be processed.",
563 QString(
"%L1").arg(job->getCoreProperty(SequenceJob::SJ_Exposure).
toDouble(), 0,
'f', 3),
564 job->getCoreProperty(SequenceJob::SJ_Filter).
toString(),
565 activeJob()->getCompleted(),
566 activeJob()->getCoreProperty(SequenceJob::SJ_Count).toInt()));
571 activeCamera()->setNextSequenceID(state()->nextSequenceID());
575 if (activeCamera()->isBLOBEnabled() ==
false)
580 if (Options::guiderType() != Guide::GUIDE_INTERNAL)
582 activeCamera()->setBLOBEnabled(
true);
589 activeCamera()->setBLOBEnabled(
true);
590 prepareActiveJobStage1();
596 activeCamera()->setBLOBEnabled(
true);
597 state()->setBusy(
false);
600 KSMessageBox::Instance()->questionYesNo(
i18n(
"Image transfer is disabled for this camera. Would you like to enable it?"),
601 i18n(
"Image Transfer"), 15);
607 emit jobPrepared(job);
609 prepareActiveJobStage1();
613void CameraProcess::prepareActiveJobStage1()
615 if (activeJob() ==
nullptr)
617 qWarning(KSTARS_EKOS_CAPTURE) <<
"prepareActiveJobStage1 with null activeJob().";
623 if (runCaptureScript(
SCRIPT_PRE_JOB, activeJob()->getCompleted() == 0) == IPS_BUSY)
626 prepareActiveJobStage2();
629void CameraProcess::prepareActiveJobStage2()
632 if (activeJob() ==
nullptr)
634 qWarning(KSTARS_EKOS_CAPTURE) <<
"prepareActiveJobStage2 with null activeJob().";
637 emit newImage(activeJob(), state()->imageData(), (activeCamera() ==
nullptr ?
"" : activeCamera()->getDeviceName()));
653 prepareJobExecution();
656void CameraProcess::executeJob()
658 if (activeJob() ==
nullptr)
660 qWarning(KSTARS_EKOS_CAPTURE) <<
"executeJob with null activeJob().";
665 if (!activeCamera() || !devices()->getActiveChip())
673 if (Options::defaultObserver().isEmpty() ==
false)
675 if (activeJob()->getCoreProperty(SequenceJob::SJ_TargetName) !=
"")
676 FITSHeaders.
append(
FITSData::Record(
"Object", activeJob()->getCoreProperty(SequenceJob::SJ_TargetName).toString(),
681 activeCamera()->setFITSHeaders(FITSHeaders);
684 state()->setBusy(
true);
685 state()->setUseGuideHead((devices()->getActiveChip()->getType() == ISD::CameraChip::PRIMARY_CCD) ?
688 emit syncGUIToJob(activeJob());
692 if (activeJob()->jobType() == SequenceJob::JOBTYPE_DARKFLAT)
695 if (state()->setDarkFlatExposure(activeJob())
696 && activeCamera()->getUploadMode() != ISD::Camera::UPLOAD_LOCAL)
698 auto placeholderPath = PlaceholderPath();
700 placeholderPath.processJobInfo(activeJob());
701 state()->setNextSequenceID(1);
706 updatePreCaptureCalibrationStatus();
710void CameraProcess::prepareJobExecution()
712 if (activeJob() ==
nullptr)
714 qWarning(KSTARS_EKOS_CAPTURE) <<
"preparePreCaptureActions with null activeJob().";
719 state()->setBusy(
true);
722 activeJob()->setCoreProperty(SequenceJob::SJ_GuiderActive,
723 state()->isActivelyGuiding());
726 activeJob()->prepareCapture();
729 emit jobExecutionPreparationStarted();
732void CameraProcess::refreshOpticalTrain(
QString name)
734 auto mount = OpticalTrainManager::Instance()->getMount(name);
737 auto scope = OpticalTrainManager::Instance()->getScope(name);
738 setScope(scope[
"name"].toString());
740 auto camera = OpticalTrainManager::Instance()->getCamera(name);
743 auto filterWheel = OpticalTrainManager::Instance()->getFilterWheel(name);
744 setFilterWheel(filterWheel);
746 auto rotator = OpticalTrainManager::Instance()->getRotator(name);
749 auto dustcap = OpticalTrainManager::Instance()->getDustCap(name);
752 auto lightbox = OpticalTrainManager::Instance()->getLightBox(name);
753 setLightBox(lightbox);
756IPState CameraProcess::checkLightFramePendingTasks()
763 if (checkPausing(CameraState::CONTINUE_ACTION_NEXT_EXPOSURE) ==
true)
767 if (state()->checkMeridianFlipActive())
773 state()->getGuideState() == GUIDE_GUIDING &&
774 Options::enforceStartGuiderDrift())
778 if ((state()->getCaptureState() ==
CAPTURE_DITHERING && state()->getDitheringState() != IPS_OK)
779 || state()->checkDithering())
787 if (state()->checkFocusRunning() || state()->startFocusIfRequired())
792 if (state()->getGuideState() == GUIDE_SUSPENDED && activeJob()->getFrameType() == FRAME_LIGHT)
794 emit newLog(
i18n(
"Autoguiding resumed."));
795 emit resumeGuiding();
806void CameraProcess::captureStarted(CameraState::CAPTUREResult rc)
810 case CameraState::CAPTURE_OK:
813 state()->getCaptureTimeout().start(
static_cast<int>(activeJob()->getCoreProperty(
814 SequenceJob::SJ_Exposure).toDouble()) * 1000 +
815 CAPTURE_TIMEOUT_THRESHOLD);
817 state()->imageCountDown().setHMS(0, 0, 0);
818 double ms_left = std::ceil(activeJob()->getExposeLeft() * 1000.0);
819 state()->imageCountDownAddMSecs(
int(ms_left));
820 state()->setLastRemainingFrameTimeMS(ms_left);
821 state()->sequenceCountDown().setHMS(0, 0, 0);
822 state()->sequenceCountDownAddMSecs(activeJob()->getJobRemainingTime(state()->averageDownloadTime()) * 1000);
825 if (activeJob()->jobType() != SequenceJob::JOBTYPE_PREVIEW)
827 auto index = state()->allJobs().indexOf(activeJob());
828 if (index >= 0 && index < state()->getSequence().count())
829 state()->changeSequenceValue(index,
"Status",
"In Progress");
831 emit updateJobTable(activeJob());
833 emit captureRunning();
837 case CameraState::CAPTURE_FRAME_ERROR:
838 emit newLog(
i18n(
"Failed to set sub frame."));
842 case CameraState::CAPTURE_BIN_ERROR:
843 emit newLog((
i18n(
"Failed to set binning.")));
847 case CameraState::CAPTURE_FOCUS_ERROR:
848 emit newLog((
i18n(
"Cannot capture while focus module is busy.")));
854void CameraProcess::checkNextExposure()
856 IPState started = startNextExposure();
859 if (started == IPS_BUSY)
863IPState CameraProcess::captureImageWithDelay()
865 auto theJob = activeJob();
867 if (theJob ==
nullptr)
870 const int seqDelay = theJob->getCoreProperty(SequenceJob::SJ_Delay).toInt();
874 state()->setCaptureState(CAPTURE_WAITING);
876 state()->getCaptureDelayTimer().start(seqDelay);
880IPState CameraProcess::startNextExposure()
885 auto theJob = activeJob();
887 if (theJob ==
nullptr)
891 if (activeJob()->getFrameType() == FRAME_LIGHT)
893 IPState pending = checkLightFramePendingTasks();
894 if (pending != IPS_OK)
899 return captureImageWithDelay();
904IPState CameraProcess::resumeSequence()
907 if (checkPausing(CameraState::CONTINUE_ACTION_CAPTURE_COMPLETE) ==
true)
913 return startNextJob();
918 else if (activeJob()->getCoreProperty(SequenceJob::SJ_Count).toInt() <=
919 activeJob()->getCompleted())
921 processJobCompletion1();
929 if (state()->getGuideState() == GUIDE_SUSPENDED && state()->suspendGuidingOnDownload() &&
930 state()->getMeridianFlipState()->checkMeridianFlipActive() ==
false)
932 qCInfo(KSTARS_EKOS_CAPTURE) <<
"Resuming guiding...";
933 emit resumeGuiding();
937 if (activeCamera()->isFastExposureEnabled())
939 if (activeCamera()->getUploadMode() != ISD::Camera::UPLOAD_LOCAL)
941 state()->checkSeqBoundary();
942 activeCamera()->setNextSequenceID(state()->nextSequenceID());
952 if (activeCamera()->isFastExposureEnabled())
954 state()->setRememberFastExposure(
true);
955 activeCamera()->setFastExposureEnabled(
false);
963 if (activeCamera()->isFastExposureEnabled())
966 activeJob()->getFrameType() == FRAME_LIGHT &&
967 checkLightFramePendingTasks() == IPS_OK)
975 state()->setRememberFastExposure(
true);
976 activeCamera()->setFastExposureEnabled(
false);
992 if (data && activeCamera() && activeCamera()->getUploadMode() != ISD::Camera::UPLOAD_LOCAL)
994 if (activeJob()->jobType() != SequenceJob::JOBTYPE_PREVIEW
995 && activeJob()->getCalibrationStage() != SequenceJobState::CAL_CALIBRATION)
997 if (state()->generateFilename(extension, &filename) && activeCamera()->saveCurrentImage(filename))
999 data->setFilename(filename);
1005 qCWarning(KSTARS_EKOS_CAPTURE) <<
"Saving current image failed!";
1010 KSMessageBox::Instance()->error(
i18n(
"Failed writing image to %1\nPlease check folder, filename & permissions.",
1012 i18n(
"Image Write Failed"), 30);
1027 state()->setImageData(data);
1028 blobInfo =
QString(
"{Device: %1 Property: %2 Element: %3 Chip: %4}").
arg(data->property(
"device").toString())
1029 .
arg(data->property(
"blobVector").toString())
1030 .
arg(data->property(
"blobElement").toString())
1031 .
arg(data->property(
"chip").toInt());
1034 state()->imageData().reset();
1036 const SequenceJob *job = activeJob();
1041 qCWarning(KSTARS_EKOS_CAPTURE) << blobInfo <<
"Ignoring received FITS as active job is null.";
1043 emit processingFITSfinished(
false);
1047 if (state()->getMeridianFlipState()->getMeridianFlipStage() >= MeridianFlipState::MF_ALIGNING)
1050 qCWarning(KSTARS_EKOS_CAPTURE) << blobInfo <<
"Ignoring Received FITS as meridian flip stage is" <<
1051 state()->getMeridianFlipState()->getMeridianFlipStage();
1052 emit processingFITSfinished(
false);
1056 const SequenceJob::SequenceJobType currentJobType = activeJob()->jobType();
1058 if (activeCamera() && activeCamera()->getUploadMode() != ISD::Camera::UPLOAD_LOCAL)
1063 qCWarning(KSTARS_EKOS_CAPTURE) << blobInfo <<
"Ignoring Received FITS as current capture state is not active" <<
1064 state()->getCaptureState();
1066 emit processingFITSfinished(
false);
1072 tChip = activeCamera()->getChip(
static_cast<ISD::CameraChip::ChipType
>(data->property(
"chip").toInt()));
1073 if (tChip != devices()->getActiveChip())
1075 if (state()->getGuideState() == GUIDE_IDLE)
1076 qCWarning(KSTARS_EKOS_CAPTURE) << blobInfo <<
"Ignoring Received FITS as it does not correspond to the target chip"
1077 << devices()->getActiveChip()->getType();
1079 emit processingFITSfinished(
false);
1084 if (devices()->getActiveChip()->getCaptureMode() == FITS_FOCUS ||
1085 devices()->getActiveChip()->getCaptureMode() == FITS_GUIDE)
1087 qCWarning(KSTARS_EKOS_CAPTURE) << blobInfo <<
"Ignoring Received FITS as it has the wrong capture mode" <<
1088 devices()->getActiveChip()->getCaptureMode();
1090 emit processingFITSfinished(
false);
1095 if (data && data->property(
"device").toString() != activeCamera()->getDeviceName())
1097 qCWarning(KSTARS_EKOS_CAPTURE) << blobInfo <<
"Ignoring Received FITS as the blob device name does not equal active camera"
1098 << activeCamera()->getDeviceName();
1100 emit processingFITSfinished(
false);
1104 if (currentJobType == SequenceJob::JOBTYPE_PREVIEW)
1107 if (checkSavingReceivedImage(data, extension, filename))
1109 FITSMode captureMode = tChip->getCaptureMode();
1110 FITSScale captureFilter = tChip->getCaptureFilter();
1111 updateFITSViewer(data, captureMode, captureFilter, filename, data->property(
"device").toString());
1116 if (data && Options::autoDark() && job->jobType() == SequenceJob::JOBTYPE_PREVIEW && state()->useGuideHead() ==
false)
1118 QVariant trainID = ProfileSettings::Instance()->getOneSetting(ProfileSettings::CaptureOpticalTrain);
1121 m_DarkProcessor.data()->denoise(trainID.
toUInt(),
1122 devices()->getActiveChip(),
1123 state()->imageData(),
1124 job->getCoreProperty(SequenceJob::SJ_Exposure).
toDouble(),
1125 job->getCoreProperty(SequenceJob::SJ_ROI).
toRect().
x(),
1126 job->getCoreProperty(SequenceJob::SJ_ROI).
toRect().
y());
1129 qWarning(KSTARS_EKOS_CAPTURE) <<
"Invalid train ID for darks substraction:" << trainID.
toUInt();
1132 if (currentJobType == SequenceJob::JOBTYPE_PREVIEW)
1143 SequenceJob *thejob = activeJob();
1145 if (thejob ==
nullptr)
1150 if (activeCamera()->isFastExposureEnabled() ==
false && state()->isLooping() ==
false)
1152 disconnect(activeCamera(), &ISD::Camera::newExposureValue,
this,
1154 DarkLibrary::Instance()->disconnect(
this);
1158 bool alreadySaved =
false;
1159 switch (thejob->getFrameType())
1163 thejob->setCalibrationStage(SequenceJobState::CAL_CALIBRATION_COMPLETE);
1167 if (thejob->getFlatFieldDuration() == DURATION_ADU
1168 && thejob->getCoreProperty(SequenceJob::SJ_TargetADU).
toDouble() > 0 &&
1169 thejob->getCalibrationStage() == SequenceJobState::CAL_CALIBRATION)
1171 if (
checkFlatCalibration(state()->imageData(), state()->exposureRange().min, state()->exposureRange().max) ==
false)
1176 thejob->setCalibrationStage(SequenceJobState::CAL_CALIBRATION_COMPLETE);
1178 if (checkSavingReceivedImage(data, extension, filename))
1179 alreadySaved =
true;
1183 thejob->setCalibrationStage(SequenceJobState::CAL_CALIBRATION_COMPLETE);
1191 qWarning(KSTARS_EKOS_CAPTURE) <<
"Job completed with frametype NONE!";
1198 if (thejob->getCalibrationStage() == SequenceJobState::CAL_CALIBRATION_COMPLETE)
1199 thejob->setCalibrationStage(SequenceJobState::CAL_CAPTURING);
1201 if (activeJob() && currentJobType != SequenceJob::JOBTYPE_PREVIEW &&
1202 activeCamera() && activeCamera()->getUploadMode() != ISD::Camera::UPLOAD_LOCAL)
1205 if (alreadySaved || checkSavingReceivedImage(data, extension, filename))
1214 emit newImage(thejob, state()->imageData(), (activeCamera() ==
nullptr ?
"" : activeCamera()->getDeviceName()));
1221 if (currentJobType != SequenceJob::JOBTYPE_PREVIEW)
1225 emit processingFITSfinished(
true);
1230 ISD::CameraChip * tChip = activeCamera()->getChip(
static_cast<ISD::CameraChip::ChipType
>(data->property(
"chip").toInt()));
1232 updateFITSViewer(data, tChip->getCaptureMode(), tChip->getCaptureFilter(),
"", data->property(
"device").toString());
1237 emit newLog(
i18n(
"Remote image saved to %1", file));
1240 if (activeCamera() && activeCamera()->getUploadMode() == ISD::Camera::UPLOAD_LOCAL)
1250 if (activeJob() ==
nullptr)
1252 qCWarning(KSTARS_EKOS_CAPTURE) <<
"Processing pre capture calibration without active job, state = " <<
1253 getCaptureStatusString(state()->getCaptureState());
1260 if (activeJob()->getFrameType() != FRAME_LIGHT
1261 && state()->getGuideState() == GUIDE_GUIDING)
1263 emit newLog(
i18n(
"Autoguiding suspended."));
1264 emit suspendGuiding();
1268 switch (activeJob()->getFrameType())
1290 if (state()->isBusy() ==
false)
1292 emit newLog(
i18n(
"Warning: Calibration process was prematurely terminated."));
1298 if (rc == IPS_ALERT)
1300 else if (rc == IPS_BUSY)
1306 captureImageWithDelay();
1311 if (activeJob() ==
nullptr)
1313 qWarning(KSTARS_EKOS_CAPTURE) <<
"procesJobCompletionStage1 with null activeJob().";
1327 if (activeJob() ==
nullptr)
1329 qWarning(KSTARS_EKOS_CAPTURE) <<
"procesJobCompletionStage2 with null activeJob().";
1333 activeJob()->done();
1335 if (activeJob()->jobType() != SequenceJob::JOBTYPE_PREVIEW)
1337 int index = state()->allJobs().indexOf(activeJob());
1338 QJsonArray seqArray = state()->getSequence();
1339 QJsonObject oneSequence = seqArray[index].toObject();
1340 oneSequence[
"Status"] =
"Complete";
1341 seqArray.
replace(index, oneSequence);
1342 state()->setSequence(seqArray);
1343 emit sequenceChanged(seqArray);
1344 emit updateJobTable(activeJob());
1358 KSNotification::event(
QLatin1String(
"CaptureSuccessful"),
i18n(
"CCD capture sequence completed"),
1359 KSNotification::Capture);
1365 if (state()->getGuideState() == GUIDE_SUSPENDED && state()->suspendGuidingOnDownload())
1366 emit resumeGuiding();
1373 SequenceJob * next_job =
nullptr;
1375 for (
auto &oneJob : state()->allJobs())
1377 if (oneJob->getStatus() == JOB_IDLE || oneJob->getStatus() == JOB_ABORTED)
1391 if (state()->getGuideState() == GUIDE_SUSPENDED && state()->suspendGuidingOnDownload() &&
1392 state()->getMeridianFlipState()->checkMeridianFlipActive() ==
false)
1394 qCDebug(KSTARS_EKOS_CAPTURE) <<
"Resuming guiding...";
1395 emit resumeGuiding();
1402 qCDebug(KSTARS_EKOS_CAPTURE) <<
"All capture jobs complete.";
1409 if (activeJob() ==
nullptr)
1413 if (!activeCamera() || !activeCamera()->isConnected())
1415 emit newLog(
i18n(
"Error: Lost connection to CCD."));
1420 state()->getCaptureTimeout().stop();
1421 state()->getCaptureDelayTimer().stop();
1422 if (activeCamera()->isFastExposureEnabled())
1424 int remaining = state()->isLooping() ? 100000 : (activeJob()->getCoreProperty(
1425 SequenceJob::SJ_Count).
toInt() -
1426 activeJob()->getCompleted());
1428 activeCamera()->setFastCount(
static_cast<uint
>(remaining));
1433 if (activeJob()->getFrameType() == FRAME_FLAT)
1436 if (activeJob()->jobType() != SequenceJob::JOBTYPE_PREVIEW
1437 && activeJob()->getFlatFieldDuration() == DURATION_ADU &&
1438 activeJob()->getCalibrationStage() == SequenceJobState::CAL_NONE)
1440 if (activeCamera()->getEncodingFormat() !=
"FITS" &&
1441 activeCamera()->getEncodingFormat() !=
"XISF")
1443 emit newLog(
i18n(
"Cannot calculate ADU levels in non-FITS images."));
1448 activeJob()->setCalibrationStage(SequenceJobState::CAL_CALIBRATION);
1453 if (activeJob()->jobType() == SequenceJob::JOBTYPE_PREVIEW)
1455 if (activeCamera()->getUploadMode() != ISD::Camera::UPLOAD_CLIENT)
1456 activeCamera()->setUploadMode(ISD::Camera::UPLOAD_CLIENT);
1461 if (activeCamera()->getUploadMode() != activeJob()->getUploadMode())
1462 activeCamera()->setUploadMode(activeJob()->getUploadMode());
1465 if (activeCamera()->getUploadMode() != ISD::Camera::UPLOAD_LOCAL)
1467 state()->checkSeqBoundary();
1468 activeCamera()->setNextSequenceID(state()->nextSequenceID());
1472 if (state()->isRememberFastExposure())
1474 state()->setRememberFastExposure(
false);
1475 activeCamera()->setFastExposureEnabled(
true);
1478 if (state()->frameSettings().contains(devices()->getActiveChip()))
1480 const auto roi = activeJob()->getCoreProperty(SequenceJob::SJ_ROI).
toRect();
1481 QVariantMap settings;
1482 settings[
"x"] = roi.x();
1483 settings[
"y"] = roi.y();
1484 settings[
"w"] = roi.width();
1485 settings[
"h"] = roi.height();
1486 settings[
"binx"] = activeJob()->getCoreProperty(SequenceJob::SJ_Binning).
toPoint().
x();
1487 settings[
"biny"] = activeJob()->getCoreProperty(SequenceJob::SJ_Binning).
toPoint().
y();
1489 state()->frameSettings()[devices()->getActiveChip()] = settings;
1493 activeCamera()->setEncodingFormat(activeJob()->getCoreProperty(
1494 SequenceJob::SJ_Encoding).toString());
1496 state()->setStartingCapture(
true);
1497 state()->placeholderPath().setGenerateFilenameSettings(*activeJob());
1500 if (activeCamera()->getUploadMode() != ISD::Camera::UPLOAD_CLIENT)
1502 auto remoteUpload = state()->placeholderPath().generateSequenceFilename(*activeJob(),
false,
true, 1,
"",
"",
false,
1506 auto remoteDirectory = remoteUpload.mid(0, lastSeparator);
1507 auto remoteFilename = remoteUpload.mid(lastSeparator + 1);
1508 activeJob()->setCoreProperty(SequenceJob::SJ_RemoteFormatDirectory, remoteDirectory);
1509 activeJob()->setCoreProperty(SequenceJob::SJ_RemoteFormatFilename, remoteFilename);
1515 activeJob()->startCapturing(state()->getRefocusState()->isAutoFocusReady(),
1516 activeJob()->getCalibrationStage() == SequenceJobState::CAL_CALIBRATION ? FITS_CALIBRATE :
1520 if (state()->isRememberFastExposure())
1522 state()->setRememberFastExposure(
false);
1523 activeCamera()->setFastExposureEnabled(
true);
1526 emit captureTarget(activeJob()->getCoreProperty(SequenceJob::SJ_TargetName).toString());
1527 emit captureImageStarted();
1532 devices()->setActiveChip(state()->useGuideHead() ?
1533 devices()->getActiveCamera()->getChip(
1534 ISD::CameraChip::GUIDE_CCD) :
1535 devices()->getActiveCamera()->getChip(ISD::CameraChip::PRIMARY_CCD));
1536 devices()->getActiveChip()->resetFrame();
1537 emit updateFrameProperties(1);
1543 if (state()->checkCapturing() ==
false)
1546 if (devices()->getActiveChip() != tChip ||
1547 devices()->getActiveChip()->getCaptureMode() != FITS_NORMAL
1548 || state()->getMeridianFlipState()->getMeridianFlipStage() >= MeridianFlipState::MF_ALIGNING)
1551 double deltaMS = std::ceil(1000.0 * value - state()->lastRemainingFrameTimeMS());
1552 emit updateCaptureCountDown(
int(deltaMS));
1553 state()->setLastRemainingFrameTimeMS(state()->lastRemainingFrameTimeMS() + deltaMS);
1557 activeJob()->setExposeLeft(value);
1559 emit newExposureProgress(activeJob(), (activeCamera() ==
nullptr ?
"" : activeCamera()->getDeviceName()));
1562 if (activeJob() && ipstate == IPS_ALERT)
1564 int retries = activeJob()->getCaptureRetires() + 1;
1566 activeJob()->setCaptureRetires(retries);
1568 emit newLog(
i18n(
"Capture failed. Check INDI Control Panel for details."));
1572 activeJob()->abort();
1576 emit newLog((
i18n(
"Restarting capture attempt #%1", retries)));
1578 state()->setNextSequenceID(1);
1584 if (activeJob() !=
nullptr && ipstate == IPS_OK)
1586 activeJob()->setCaptureRetires(0);
1587 activeJob()->setExposeLeft(0);
1589 if (devices()->getActiveCamera()
1590 && devices()->getActiveCamera()->getUploadMode() == ISD::Camera::UPLOAD_LOCAL)
1592 if (activeJob()->getStatus() == JOB_BUSY)
1594 emit processingFITSfinished(
false);
1599 if (state()->getGuideState() == GUIDE_GUIDING && Options::guiderType() == 0
1600 && state()->suspendGuidingOnDownload())
1602 qCDebug(KSTARS_EKOS_CAPTURE) <<
"Autoguiding suspended until primary CCD chip completes downloading...";
1603 emit suspendGuiding();
1606 emit downloadingFrame();
1609 state()->downloadTimer().start();
1610 state()->downloadProgressTimer().start();
1618 double downloadTimeLeft = state()->averageDownloadTime() - state()->downloadTimer().elapsed() /
1620 if(downloadTimeLeft >= 0)
1622 state()->imageCountDown().setHMS(0, 0, 0);
1623 state()->imageCountDownAddMSecs(
int(std::ceil(downloadTimeLeft * 1000)));
1624 emit newDownloadProgress(downloadTimeLeft, (activeCamera() ==
nullptr ?
"" : activeCamera()->getDeviceName()));
1632 emit newImage(activeJob(), imageData, (activeCamera() ==
nullptr ?
"" : activeCamera()->getDeviceName()));
1634 if (activeCamera()->isFastExposureEnabled() ==
false)
1636 const int seqDelay = activeJob()->getCoreProperty(SequenceJob::SJ_Delay).
toInt();
1642 if (activeJob() !=
nullptr)
1643 activeJob()->startCapturing(state()->getRefocusState()->isAutoFocusReady(), FITS_NORMAL);
1646 else if (activeJob() !=
nullptr)
1647 activeJob()->startCapturing(state()->getRefocusState()->isAutoFocusReady(), FITS_NORMAL);
1657 if (activeCamera()->getUploadMode() != ISD::Camera::UPLOAD_LOCAL
1658 && state()->downloadTimer().isValid())
1661 double currentDownloadTime = state()->downloadTimer().elapsed() / 1000.0;
1662 state()->addDownloadTime(currentDownloadTime);
1664 state()->downloadTimer().invalidate();
1668 emit newLog(
i18n(
"Download Time: %1 s, New Download Time Estimate: %2 s.", dLTimeString, estimatedTimeString));
1675 if (activeJob()->jobType() == SequenceJob::JOBTYPE_PREVIEW)
1678 activeCamera()->setUploadMode(activeJob()->getUploadMode());
1680 state()->setActiveJob(
nullptr);
1682 if (state()->getGuideState() == GUIDE_SUSPENDED && state()->suspendGuidingOnDownload())
1683 emit resumeGuiding();
1694 state()->getCaptureTimeout().stop();
1695 state()->setCaptureTimeoutCounter(0);
1697 state()->downloadProgressTimer().stop();
1700 if (state()->isLooping())
1714 if (activeJob()->jobType() == SequenceJob::JOBTYPE_PREVIEW
1715 || activeJob()->getCalibrationStage() == SequenceJobState::CAL_CALIBRATION)
1719 updatedCaptureCompleted(activeJob()->getCompleted() + 1);
1721 state()->getRefocusState()->decreaseInSequenceFocusCounter();
1723 state()->getRefocusState()->setAdaptiveFocusDone(
false);
1727 if (state()->getMeridianFlipState()->getMeridianFlipStage() < MeridianFlipState::MF_FLIPPING)
1728 state()->decreaseDitherCounter();
1731 state()->addCapturedFrame(activeJob()->getSignature());
1734 emit newLog(
i18n(
"Received image %1 out of %2.", activeJob()->getCompleted(),
1735 activeJob()->getCoreProperty(SequenceJob::SJ_Count).toInt()));
1740 double hfr = -1, eccentricity = -1;
1741 int numStars = -1, median = -1;
1746 if (Options::autoHFR() && imageData && !imageData->areStarsSearched() && imageData->getRecordValue(
"FRAME", frameType)
1747 && frameType.
toString() ==
"Light")
1749#ifdef HAVE_STELLARSOLVER
1752 QVariantMap extractionSettings;
1753 extractionSettings[
"optionsProfileIndex"] = Options::hFROptionsProfile();
1754 extractionSettings[
"optionsProfileGroup"] =
static_cast<int>(Ekos::HFRProfiles);
1755 imageData->setSourceExtractorSettings(extractionSettings);
1760 hfr = imageData->getHFR(HFR_AVERAGE);
1761 numStars = imageData->getSkyBackground().starsDetected;
1762 median = imageData->getMedian();
1763 eccentricity = imageData->getEccentricity();
1764 filename = imageData->filename();
1767 if (state()->isLooping() ==
false && activeJob()->jobType() != SequenceJob::JOBTYPE_PREVIEW)
1768 emit newLog(
i18n(
"Captured %1", filename));
1770 auto remainingPlaceholders = PlaceholderPath::remainingPlaceholders(filename);
1771 if (remainingPlaceholders.size() > 0)
1774 i18n(
"WARNING: remaining and potentially unknown placeholders %1 in %2",
1775 remainingPlaceholders.join(
", "), filename));
1781 QVariantMap metadata;
1782 metadata[
"filename"] = filename;
1783 metadata[
"type"] = activeJob()->getFrameType();
1784 metadata[
"exposure"] = activeJob()->getCoreProperty(SequenceJob::SJ_Exposure).
toDouble();
1785 metadata[
"filter"] = activeJob()->getCoreProperty(SequenceJob::SJ_Filter).
toString();
1786 metadata[
"width"] = activeJob()->getCoreProperty(SequenceJob::SJ_ROI).
toRect().
width();
1787 metadata[
"height"] = activeJob()->getCoreProperty(SequenceJob::SJ_ROI).
toRect().
height();
1788 metadata[
"hfr"] = hfr;
1789 metadata[
"starCount"] = numStars;
1790 metadata[
"median"] = median;
1791 metadata[
"eccentricity"] = eccentricity;
1792 emit captureComplete(metadata);
1801 const QString captureScript = activeJob()->getScript(scriptType);
1802 if (captureScript.
isEmpty() ==
false && precond)
1804 state()->setCaptureScriptType(scriptType);
1807 emit newLog(
i18n(
"Executing capture script %1", captureScript));
1819 switch (state()->captureScriptType())
1822 emit newLog(
i18n(
"Pre capture script finished with code %1.", exitCode));
1823 if (activeJob() && activeJob()->getStatus() == JOB_IDLE)
1830 emit newLog(
i18n(
"Post capture script finished with code %1.", exitCode));
1833 if (activeJob() ==
nullptr
1834 || activeJob()->getCoreProperty(SequenceJob::SJ_Count).toInt() <=
1835 activeJob()->getCompleted())
1840 else if (state()->checkMeridianFlipReady())
1842 emit newLog(
i18n(
"Processing meridian flip..."));
1852 emit newLog(
i18n(
"Pre job script finished with code %1.", exitCode));
1857 emit newLog(
i18n(
"Post job script finished with code %1.", exitCode));
1871 QVariant trainID = ProfileSettings::Instance()->getOneSetting(ProfileSettings::CaptureOpticalTrain);
1872 if (activeCamera() && trainID.
isValid())
1875 if (devices()->filterWheel())
1877 if (activeCamera() && activeCamera()->getDeviceName() == name)
1880 emit refreshCamera(
true);
1883 emit refreshCamera(
false);
1894 if (!activeCamera())
1900 devices()->setActiveChip(
nullptr);
1903 if (activeCamera()->getDeviceName().contains(
"Guider"))
1905 state()->setUseGuideHead(
true);
1906 devices()->setActiveChip(activeCamera()->getChip(ISD::CameraChip::GUIDE_CCD));
1909 if (devices()->getActiveChip() ==
nullptr)
1911 state()->setUseGuideHead(
false);
1912 devices()->setActiveChip(activeCamera()->getChip(ISD::CameraChip::PRIMARY_CCD));
1915 emit refreshCameraSettings();
1920 auto pos = std::find_if(state()->DSLRInfos().begin(),
1923 return (oneDSLRInfo[
"Model"] == model);
1927 if (pos != state()->DSLRInfos().end())
1930 devices()->getActiveChip()->setImageInfo(camera[
"Width"].toInt(),
1931 camera[
"Height"].toInt(),
1932 camera[
"PixelW"].toDouble(),
1933 camera[
"PixelH"].toDouble(),
1940 if (activeCamera() && activeCamera()->getDeviceName() == camera)
1943 auto rememberState = state()->getCaptureState();
1946 state()->setCaptureState(rememberState);
1949 state()->setCaptureTimeoutCounter(0);
1953 devices()->setActiveChip(devices()->getActiveChip());
1967 auto name = device->getDeviceName();
1968 device->disconnect(
this);
1971 if (devices()->mount() && devices()->mount()->getDeviceName() == device->getDeviceName())
1973 devices()->mount()->disconnect(
this);
1974 devices()->setMount(
nullptr);
1975 if (activeJob() !=
nullptr)
1976 activeJob()->addMount(
nullptr);
1980 if (devices()->dome() && devices()->dome()->getDeviceName() == device->getDeviceName())
1982 devices()->dome()->disconnect(
this);
1983 devices()->setDome(
nullptr);
1987 if (devices()->rotator() && devices()->rotator()->getDeviceName() == device->getDeviceName())
1989 devices()->rotator()->disconnect(
this);
1990 devices()->setRotator(
nullptr);
1994 if (devices()->dustCap() && devices()->dustCap()->getDeviceName() == device->getDeviceName())
1996 devices()->dustCap()->disconnect(
this);
1997 devices()->setDustCap(
nullptr);
1998 state()->hasDustCap =
false;
1999 state()->setDustCapState(CameraState::CAP_UNKNOWN);
2003 if (devices()->lightBox() && devices()->lightBox()->getDeviceName() == device->getDeviceName())
2005 devices()->lightBox()->disconnect(
this);
2006 devices()->setLightBox(
nullptr);
2007 state()->hasLightBox =
false;
2008 state()->setLightBoxLightState(CameraState::CAP_LIGHT_UNKNOWN);
2012 if (activeCamera() && activeCamera()->getDeviceName() == name)
2015 devices()->setActiveCamera(
nullptr);
2016 devices()->setActiveChip(
nullptr);
2019 if (INDIListener::findDevice(name, generic))
2020 DarkLibrary::Instance()->removeDevice(generic);
2029 if (devices()->filterWheel() && devices()->filterWheel()->getDeviceName() == name)
2031 devices()->filterWheel()->disconnect(
this);
2032 devices()->setFilterWheel(
nullptr);
2036 emit refreshFilterSettings();
2043 state()->setCaptureTimeoutCounter(state()->captureTimeoutCounter() + 1);
2045 if (state()->deviceRestartCounter() >= 3)
2047 state()->setCaptureTimeoutCounter(0);
2048 state()->setDeviceRestartCounter(0);
2049 emit newLog(
i18n(
"Exposure timeout. Aborting..."));
2054 if (state()->captureTimeoutCounter() > 3 && activeCamera())
2056 emit newLog(
i18n(
"Exposure timeout. More than 3 have been detected, will restart driver."));
2057 QString camera = activeCamera()->getDeviceName();
2058 QString fw = (devices()->filterWheel() !=
nullptr) ?
2059 devices()->filterWheel()->getDeviceName() :
"";
2060 emit driverTimedout(camera);
2063 state()->setDeviceRestartCounter(state()->deviceRestartCounter() + 1);
2071 if (activeCamera() && activeJob())
2074 emit newLog(
i18n(
"Exposure timeout. Restarting exposure..."));
2075 activeCamera()->setEncodingFormat(
"FITS");
2076 auto rememberState = state()->getCaptureState();
2079 state()->setCaptureState(rememberState);
2081 auto targetChip = activeCamera()->getChip(state()->useGuideHead() ?
2082 ISD::CameraChip::GUIDE_CCD :
2083 ISD::CameraChip::PRIMARY_CCD);
2084 targetChip->abortExposure();
2085 const double exptime = activeJob()->getCoreProperty(SequenceJob::SJ_Exposure).
toDouble();
2086 targetChip->capture(exptime);
2087 state()->getCaptureTimeout().start(
static_cast<int>((exptime) * 1000 + CAPTURE_TIMEOUT_THRESHOLD));
2091 else if (state()->captureTimeoutCounter() < 40)
2093 qCDebug(KSTARS_EKOS_CAPTURE) <<
"Unable to restart exposure as camera is missing, trying again in 5 seconds...";
2098 state()->setCaptureTimeoutCounter(0);
2099 state()->setDeviceRestartCounter(0);
2100 emit newLog(
i18n(
"Exposure timeout. Too many. Aborting..."));
2113 if (type == ISD::Camera::ERROR_CAPTURE)
2115 int retries = activeJob()->getCaptureRetires() + 1;
2117 activeJob()->setCaptureRetires(retries);
2119 emit newLog(
i18n(
"Capture failed. Check INDI Control Panel for details."));
2127 emit newLog(
i18n(
"Restarting capture attempt #%1", retries));
2129 state()->setNextSequenceID(1);
2146 double currentADU = imageData->getADU();
2147 bool outOfRange =
false, saturated =
false;
2149 switch (imageData->bpp())
2152 if (activeJob()->getCoreProperty(SequenceJob::SJ_TargetADU).toDouble() > UINT8_MAX)
2154 else if (currentADU / UINT8_MAX > 0.95)
2159 if (activeJob()->getCoreProperty(SequenceJob::SJ_TargetADU).toDouble() > UINT16_MAX)
2161 else if (currentADU / UINT16_MAX > 0.95)
2166 if (activeJob()->getCoreProperty(SequenceJob::SJ_TargetADU).toDouble() > UINT32_MAX)
2168 else if (currentADU / UINT32_MAX > 0.95)
2178 emit newLog(
i18n(
"Flat calibration failed. Captured image is only %1-bit while requested ADU is %2.",
2180 ,
QString::number(activeJob()->getCoreProperty(SequenceJob::SJ_TargetADU).toDouble(),
'f', 2)));
2186 double nextExposure = activeJob()->getCoreProperty(SequenceJob::SJ_Exposure).
toDouble() * 0.1;
2187 nextExposure = qBound(exp_min, nextExposure, exp_max);
2189 emit newLog(
i18n(
"Current image is saturated (%1). Next exposure is %2 seconds.",
2192 activeJob()->setCalibrationStage(SequenceJobState::CAL_CALIBRATION);
2193 activeJob()->setCoreProperty(SequenceJob::SJ_Exposure, nextExposure);
2194 if (activeCamera()->getUploadMode() != ISD::Camera::UPLOAD_CLIENT)
2196 activeCamera()->setUploadMode(ISD::Camera::UPLOAD_CLIENT);
2202 double ADUDiff = fabs(currentADU - activeJob()->getCoreProperty(
2203 SequenceJob::SJ_TargetADU).toDouble());
2206 if (ADUDiff <= state()->targetADUTolerance())
2208 if (activeJob()->getCalibrationStage() == SequenceJobState::CAL_CALIBRATION)
2211 i18n(
"Current ADU %1 within target ADU tolerance range.",
QString::number(currentADU,
'f', 0)));
2212 activeCamera()->setUploadMode(activeJob()->getUploadMode());
2213 auto placeholderPath = PlaceholderPath();
2215 placeholderPath.processJobInfo(activeJob());
2217 activeJob()->setCalibrationStage(SequenceJobState::CAL_CALIBRATION_COMPLETE);
2222 if (activeCamera() && activeCamera()->getUploadMode() != ISD::Camera::UPLOAD_LOCAL)
2223 state()->checkSeqBoundary();
2229 double nextExposure = -1;
2232 if (std::fabs(imageData->getMax(0) - imageData->getMin(0)) < 10)
2233 nextExposure = activeJob()->getCoreProperty(SequenceJob::SJ_Exposure).
toDouble() * 0.5;
2237 if (nextExposure <= 0 || std::isnan(nextExposure))
2240 i18n(
"Unable to calculate optimal exposure settings, please capture the flats manually."));
2246 nextExposure = qBound(exp_min, nextExposure, exp_max);
2248 emit newLog(
i18n(
"Current ADU is %1 Next exposure is %2 seconds.",
QString::number(currentADU,
'f', 0),
2249 QString(
"%L1").arg(nextExposure, 0,
'f', 6)));
2251 activeJob()->setCalibrationStage(SequenceJobState::CAL_CALIBRATION);
2252 activeJob()->setCoreProperty(SequenceJob::SJ_Exposure, nextExposure);
2253 if (activeCamera()->getUploadMode() != ISD::Camera::UPLOAD_CLIENT)
2255 activeCamera()->setUploadMode(ISD::Camera::UPLOAD_CLIENT);
2266 if (activeJob() ==
nullptr)
2268 qWarning(KSTARS_EKOS_CAPTURE) <<
"setCurrentADU with null activeJob().";
2273 double nextExposure = 0;
2274 double targetADU = activeJob()->getCoreProperty(SequenceJob::SJ_TargetADU).
toDouble();
2275 std::vector<double> coeff;
2279 if(activeJob()->getCoreProperty(SequenceJob::SJ_SkyFlat).toBool() && ExpRaw.size() > 2)
2281 int remove = ExpRaw.size() - 2;
2282 ExpRaw.remove(0, remove);
2283 ADURaw.remove(0, remove);
2287 ExpRaw.append(activeJob()->getCoreProperty(SequenceJob::SJ_Exposure).toDouble());
2288 ADURaw.append(currentADU);
2290 qCDebug(KSTARS_EKOS_CAPTURE) <<
"Capture: Current ADU = " << currentADU <<
" targetADU = " << targetADU
2291 <<
" Exposure Count: " << ExpRaw.count();
2295 if (ExpRaw.count() >= 2)
2297 if (ExpRaw.count() >= 5)
2301 coeff = gsl_polynomial_fit(ADURaw.data(), ExpRaw.data(), ExpRaw.count(), 2, chisq);
2302 qCDebug(KSTARS_EKOS_CAPTURE) <<
"Running polynomial fitting. Found " << coeff.size() <<
" coefficients.";
2303 if (std::isnan(coeff[0]) || std::isinf(coeff[0]))
2305 qCDebug(KSTARS_EKOS_CAPTURE) <<
"Coefficients are invalid.";
2306 targetADUAlgorithm = ADU_LEAST_SQUARES;
2310 nextExposure = coeff[0] + (coeff[1] * targetADU) + (coeff[2] * pow(targetADU, 2));
2312 if (nextExposure < 0 || (nextExposure > ExpRaw.last() || targetADU < ADURaw.last())
2313 || (nextExposure < ExpRaw.last() || targetADU > ADURaw.last()))
2316 targetADUAlgorithm = ADU_LEAST_SQUARES;
2320 targetADUAlgorithm = ADU_POLYNOMIAL;
2321 for (
size_t i = 0; i < coeff.size(); i++)
2322 qCDebug(KSTARS_EKOS_CAPTURE) <<
"Coeff #" << i <<
"=" << coeff[i];
2327 bool looping =
false;
2328 if (ExpRaw.count() >= 10)
2330 int size = ExpRaw.count();
2331 looping = (std::fabs(ExpRaw[size - 1] - ExpRaw[size - 2] < 0.01)) &&
2332 (std::fabs(ExpRaw[size - 2] - ExpRaw[size - 3] < 0.01));
2333 if (looping && targetADUAlgorithm == ADU_POLYNOMIAL)
2335 qWarning(KSTARS_EKOS_CAPTURE) <<
"Detected looping in polynomial results. Falling back to llsqr.";
2336 targetADUAlgorithm = ADU_LEAST_SQUARES;
2343 if (targetADUAlgorithm == ADU_LEAST_SQUARES)
2345 double a = 0, b = 0;
2346 llsq(ExpRaw, ADURaw, a, b);
2351 nextExposure = (targetADU - b) / a;
2353 if (nextExposure < 0)
2361 if (nextExposure == 0.0 || nextExposure > 180)
2363 if (currentADU < targetADU)
2364 nextExposure = activeJob()->getCoreProperty(SequenceJob::SJ_Exposure).
toDouble() * 1.25;
2366 nextExposure = activeJob()->getCoreProperty(SequenceJob::SJ_Exposure).
toDouble() * .75;
2369 qCDebug(KSTARS_EKOS_CAPTURE) <<
"next flat exposure is" << nextExposure;
2371 return nextExposure;
2383 if (devices()->mount() && activeCamera() && devices()->mount()->isConnected())
2386 auto activeDevices = activeCamera()->
getText(
"ACTIVE_DEVICES");
2389 auto activeTelescope = activeDevices->findWidgetByName(
"ACTIVE_TELESCOPE");
2390 if (activeTelescope)
2392 activeTelescope->setText(devices()->mount()->getDeviceName().toLatin1().constData());
2404 all_devices.
append(activeCamera());
2405 if (devices()->dustCap())
2406 all_devices.
append(devices()->dustCap());
2408 for (
auto &oneDevice : all_devices)
2410 auto activeDevices = oneDevice->getText(
"ACTIVE_DEVICES");
2413 auto activeFilter = activeDevices->findWidgetByName(
"ACTIVE_FILTER");
2417 if (devices()->filterWheel())
2419 if (activeFilterText != devices()->filterWheel()->getDeviceName())
2421 activeFilter->setText(devices()->filterWheel()->getDeviceName().toLatin1().constData());
2422 oneDevice->sendNewProperty(activeDevices);
2426 else if (activeFilterText.
isEmpty())
2429 qCDebug(KSTARS_EKOS_CAPTURE) <<
"No active filter wheel. " << oneDevice->getDeviceName() <<
" ACTIVE_FILTER is reset.";
2430 activeFilter->setText(
"");
2431 oneDevice->sendNewProperty(activeDevices);
2438QString Ekos::CameraProcess::createTabTitle(
const FITSMode &captureMode,
const QString &deviceName)
2440 const bool isPreview = (activeJob() ==
nullptr || (activeJob() && activeJob()->jobType() == SequenceJob::JOBTYPE_PREVIEW));
2441 if (isPreview && Options::singlePreviewFITS())
2445 if (Options::singleWindowCapturedFITS())
2446 return (
i18n(
"%1 Preview", deviceName));
2449 return(
i18n(
"Preview"));
2451 else if (captureMode == FITS_CALIBRATE)
2455 const QString filtername = activeJob()->getCoreProperty(SequenceJob::SJ_Filter).toString();
2456 if (filtername ==
"")
2459 return(
QString(
"%1 %2").arg(filtername).arg(
i18n(
"Flat Calibration")));
2462 return(
i18n(
"Calibration"));
2468 const FITSScale &captureFilter,
const QString &filename,
const QString &deviceName)
2474 switch (captureMode)
2477 case FITS_CALIBRATE:
2479 if (Options::useFITSViewer())
2482 bool success =
false;
2486 QString tabTitle = createTabTitle(captureMode, deviceName);
2489 int *tabID = &m_fitsvViewerTabIDs.normalTabID;
2490 if (*tabID == -1 || Options::singlePreviewFITS() ==
false)
2493 success = getFITSViewer()->loadData(data, fileURL, &tabIndex, captureMode, captureFilter, tabTitle);
2496 auto tabs = getFITSViewer()->tabs();
2497 if (tabIndex < tabs.size() && captureMode == FITS_NORMAL)
2499 emit newView(tabs[tabIndex]->getView());
2500 tabs[tabIndex]->disconnect(
this);
2501 connect(tabs[tabIndex].get(), &FITSTab::updated,
this, [
this]
2504 emit newView(tab->getView());
2510 success = getFITSViewer()->updateData(data, fileURL, *tabID, &tabIndex, captureMode, captureFilter, tabTitle);
2517 qCCritical(KSTARS_EKOS_CAPTURE()) <<
"error adding/updating FITS";
2521 if (Options::focusFITSOnNewImage())
2522 getFITSViewer()->raise();
2535 FITSMode captureMode = tChip ==
nullptr ? FITS_UNKNOWN : tChip->getCaptureMode();
2536 FITSScale captureFilter = tChip ==
nullptr ? FITS_NONE : tChip->getCaptureFilter();
2537 updateFITSViewer(data, captureMode, captureFilter, filename, data->property(
"device").toString());
2541 const QString &targetName,
bool setOptions)
2543 state()->clearCapturedFramesMap();
2544 auto queue = state()->getSequenceQueue();
2545 if (!queue->load(fileURL, targetName, devices(), state()))
2547 QString message =
i18n(
"Unable to open file %1", fileURL);
2548 KSNotification::sorry(message,
i18n(
"Could Not Open File"));
2554 queue->setOptions();
2556 state()->updateHFRThreshold();
2559 for (
auto j : state()->allJobs())
2568 state()->getSequenceQueue()->loadOptions();
2569 return state()->getSequenceQueue()->save(path, state()->observerName());
2593 disconnect(activeCamera(), &ISD::Camera::ready,
this, &CameraProcess::cameraReady);
2600 if (devices()->filterWheel() && devices()->filterWheel() == device)
2603 if (devices()->filterWheel())
2604 devices()->filterWheel()->disconnect(
this);
2606 devices()->setFilterWheel(device);
2608 return (device !=
nullptr);
2615 emit newLog(
i18n(
"Sequence paused."));
2620 state()->setContinueAction(continueAction);
2630 SequenceJob * first_job =
nullptr;
2633 for (
auto &job : state()->allJobs())
2635 if (job->getStatus() == JOB_IDLE || job->getStatus() == JOB_ABORTED)
2644 if (first_job ==
nullptr)
2647 for (
auto &job : state()->allJobs())
2649 if (job->getStatus() != JOB_DONE)
2652 if (state()->getCaptureDelayTimer().isActive())
2654 if (state()->getCaptureDelayTimer().interval() <= 0)
2655 state()->getCaptureDelayTimer().setInterval(1000);
2662 if (!state()->ignoreJobProgress())
2665 i18n(
"All jobs are complete. Do you want to reset the status of all jobs and restart capturing?"),
2673 first_job = state()->allJobs().first();
2677 else if (state()->ignoreJobProgress())
2679 emit newLog(
i18n(
"Warning: option \"Always Reset Sequence When Starting\" is enabled and resets the sequence counts."));
2686void CameraProcess::resetJobStatus(JOBStatus newStatus)
2688 if (activeJob() !=
nullptr)
2690 activeJob()->resetStatus(newStatus);
2691 emit updateJobTable(activeJob());
2695void CameraProcess::resetAllJobs()
2697 for (
auto &job : state()->allJobs())
2702 m_State->clearCapturedFramesMap();
2704 emit updateJobTable(
nullptr);
2707void CameraProcess::updatedCaptureCompleted(
int count)
2709 activeJob()->setCompleted(count);
2710 emit updateJobTable(activeJob());
2735 for (i = 0; i < n; i++)
2740 xbar = xbar /
static_cast<double>(n);
2741 ybar = ybar /
static_cast<double>(n);
2747 for (i = 0; i < n; i++)
2749 top = top + (x[i] - xbar) * (y[i] - ybar);
2750 bot = bot + (x[i] - xbar) * (x[i] - xbar);
2755 b = ybar - a * xbar;
2767 if (devices()->getActiveCamera() && devices()->getActiveCamera()->
hasCoolerControl())
2775 if (devices()->getActiveCamera() && devices()->getActiveCamera()->
hasCoolerControl())
2776 return devices()->getActiveCamera()->setCoolerControl(enable);
2787 emit driverTimedout(name);
2794 KSMessageBox::Instance()->questionYesNo(
i18n(
"Are you sure you want to restart %1 camera driver?", name),
2795 i18n(
"Driver Restart"), 5);
2800 if (!activeCamera())
2803 ISD::CameraChip *tChip = devices()->getActiveCamera()->getChip(ISD::CameraChip::PRIMARY_CCD);
2805 return tChip->getFrameTypes();
2810 if (devices()->getFilterManager().isNull())
2813 return devices()->getFilterManager()->getFilterLabels();
2818 if (devices()->getActiveCamera()->getProperty(
"CCD_GAIN"))
2823 ccdGain[
"GAIN"] = value;
2824 propertyMap[
"CCD_GAIN"] = ccdGain;
2828 propertyMap[
"CCD_GAIN"].
remove(
"GAIN");
2829 if (propertyMap[
"CCD_GAIN"].size() == 0)
2830 propertyMap.remove(
"CCD_GAIN");
2833 else if (devices()->getActiveCamera()->getProperty(
"CCD_CONTROLS"))
2838 ccdGain[
"Gain"] = value;
2839 propertyMap[
"CCD_CONTROLS"] = ccdGain;
2843 propertyMap[
"CCD_CONTROLS"].
remove(
"Gain");
2844 if (propertyMap[
"CCD_CONTROLS"].size() == 0)
2845 propertyMap.remove(
"CCD_CONTROLS");
2852 if (devices()->getActiveCamera()->getProperty(
"CCD_OFFSET"))
2857 ccdOffset[
"OFFSET"] = value;
2858 propertyMap[
"CCD_OFFSET"] = ccdOffset;
2862 propertyMap[
"CCD_OFFSET"].
remove(
"OFFSET");
2863 if (propertyMap[
"CCD_OFFSET"].size() == 0)
2864 propertyMap.remove(
"CCD_OFFSET");
2867 else if (devices()->getActiveCamera()->getProperty(
"CCD_CONTROLS"))
2872 ccdOffset[
"Offset"] = value;
2873 propertyMap[
"CCD_CONTROLS"] = ccdOffset;
2877 propertyMap[
"CCD_CONTROLS"].
remove(
"Offset");
2878 if (propertyMap[
"CCD_CONTROLS"].size() == 0)
2879 propertyMap.remove(
"CCD_CONTROLS");
2887 if (!m_FITSViewerWindow.
isNull() && ! m_FITSViewerWindow.
isNull())
2888 return m_FITSViewerWindow;
2891 m_fitsvViewerTabIDs = {-1, -1, -1, -1, -1};
2896 connect(m_FITSViewerWindow.
get(), &FITSViewer::closed,
this, [
this](
int tabIndex)
2898 if (tabIndex == m_fitsvViewerTabIDs.normalTabID)
2899 m_fitsvViewerTabIDs.normalTabID = -1;
2900 else if (tabIndex == m_fitsvViewerTabIDs.calibrationTabID)
2901 m_fitsvViewerTabIDs.calibrationTabID = -1;
2902 else if (tabIndex == m_fitsvViewerTabIDs.focusTabID)
2903 m_fitsvViewerTabIDs.focusTabID = -1;
2904 else if (tabIndex == m_fitsvViewerTabIDs.guideTabID)
2905 m_fitsvViewerTabIDs.guideTabID = -1;
2906 else if (tabIndex == m_fitsvViewerTabIDs.alignTabID)
2907 m_fitsvViewerTabIDs.alignTabID = -1;
2911 connect(m_FITSViewerWindow.
get(), &FITSViewer::terminated,
this, [
this]()
2913 m_fitsvViewerTabIDs = {-1, -1, -1, -1, -1};
2914 m_FITSViewerWindow.
clear();
2917 return m_FITSViewerWindow;
2922 return devices()->getActiveCamera();
IPState runCaptureScript(ScriptTypes scriptType, bool precond=true)
runCaptureScript Run the pre-/post capture/job script
void updateTelescopeInfo()
updateTelescopeInfo Update the scope information in the camera's INDI driver.
void processCaptureTimeout()
processCaptureTimeout If exposure timed out, let's handle it.
void setExposureProgress(ISD::CameraChip *tChip, double value, IPState state)
setExposureProgress Manage exposure progress reported by the camera device.
IPState startNextExposure()
startNextExposure Ensure that all pending preparation tasks are be completed (focusing,...
void updatePreCaptureCalibrationStatus()
updatePreCaptureCalibrationStatus This is a wrapping loop for processPreCaptureCalibrationStage(),...
void reconnectCameraDriver(const QString &camera, const QString &filterWheel)
reconnectDriver Reconnect the camera driver
IPState checkLightFramePendingTasks()
Check all tasks that might be pending before capturing may start.
void checkNextExposure()
checkNextExposure Try to start capturing the next exposure (
void clearFlatCache()
clearFlatCache Clear the measured values for flat calibrations
bool loadSequenceQueue(const QString &fileURL, const QString &targetName="", bool setOptions=true)
Loads the Ekos Sequence Queue file in the Sequence Queue.
bool setFilterWheel(ISD::FilterWheel *device)
setFilterWheel Connect to the given filter wheel device (and deconnect the old one if existing)
Q_SCRIPTABLE void resetFrame()
resetFrame Reset frame settings of the camera
bool saveSequenceQueue(const QString &path, bool loadOptions=true)
Saves the Sequence Queue to the Ekos Sequence Queue file.
QStringList generateScriptArguments() const
generateScriptArguments Generate argument list to pass to capture script
IPState previewImageCompletedAction()
previewImageCompletedAction Activities required when a preview image has been captured.
bool setCoolerControl(bool enable)
Set the CCD cooler ON/OFF.
void updateCompletedCaptureCountersAction()
updateCompletedCaptureCounters Update counters if an image has been captured
void scriptFinished(int exitCode, QProcess::ExitStatus status)
scriptFinished Slot managing the return status of pre/post capture/job scripts
void selectCamera(QString name)
setCamera select camera device
bool checkPausing(CameraState::ContinueAction continueAction)
checkPausing check if a pause has been planned and pause subsequently
SequenceJob * findNextPendingJob()
findExecutableJob find next job to be executed
void stopCapturing(CaptureState targetState)
stopCapturing Stopping the entire capturing state (envelope for aborting, suspending,...
void updateFilterInfo()
updateFilterInfo Update the filter information in the INDI drivers of the current camera and dust cap
IPState processPreCaptureCalibrationStage()
processPreCaptureCalibrationStage Execute the tasks that need to be completed before capturing may st...
bool setCamera(ISD::Camera *device)
setCamera Connect to the given camera device (and deconnect the old one if existing)
void checkCamera()
configureCamera Refreshes the CCD information in the capture module.
void updateGain(double value, QMap< QString, QMap< QString, QVariant > > &propertyMap)
getGain Update the gain value from the custom property value.
QStringList filterLabels()
filterLabels list of currently available filter labels
IPState startNextJob()
startNextJob Select the next job that is either idle or aborted and call prepareJob(*SequenceJob) to ...
void prepareActiveJobStage2()
prepareActiveJobStage2 Reset #calibrationStage and continue with preparePreCaptureActions().
void showFITSPreview(const QSharedPointer< FITSData > &data)
showFITSPreview Directly show the FITS data as preview
void removeDevice(const QSharedPointer< ISD::GenericDevice > &device)
Generic method for removing any connected device.
IPState resumeSequence()
resumeSequence Try to continue capturing.
QStringList frameTypes()
frameTypes Retrieve the frame types from the active camera's primary chip.
void updateOffset(double value, QMap< QString, QMap< QString, QVariant > > &propertyMap)
getOffset Update the offset value from the custom property value.
void processJobCompletion2()
processJobCompletionStage2 Stop execution of the current sequence and check whether there exists a ne...
bool checkFlatCalibration(QSharedPointer< FITSData > imageData, double exp_min, double exp_max)
checkFlatCalibration check the flat calibration
IPState updateImageMetadataAction(QSharedPointer< FITSData > imageData)
updateImageMetadataAction Update meta data of a captured image
void processFITSData(const QSharedPointer< FITSData > &data, const QString &extension)
newFITS process new FITS data received from camera.
void prepareJob(SequenceJob *job)
prepareJob Update the counters of existing frames and continue with prepareActiveJob(),...
void processNewRemoteFile(QString file)
setNewRemoteFile A new image has been stored as remote file
IPState updateDownloadTimesAction()
updateDownloadTimesAction Add the current download time to the list of already measured ones
double calculateFlatExpTime(double currentADU)
calculateFlatExpTime calculate the next flat exposure time from the measured ADU value
void processCaptureError(ISD::Camera::ErrorType type)
processCaptureError Handle when image capture fails
IPState continueFramingAction(const QSharedPointer< FITSData > &imageData)
continueFramingAction If framing is running, start the next capture sequence
void syncDSLRToTargetChip(const QString &model)
syncDSLRToTargetChip Syncs INDI driver CCD_INFO property to the DSLR values.
void setDownloadProgress()
setDownloadProgress update the Capture Module and Summary Screen's estimate of how much time is left ...
void prepareJobExecution()
preparePreCaptureActions Trigger setting the filter, temperature, (if existing) the rotator angle and...
void updateFITSViewer(const QSharedPointer< FITSData > data, const FITSMode &captureMode, const FITSScale &captureFilter, const QString &filename, const QString &deviceName)
updateFITSViewer display new image in the configured FITSViewer tab.
void processJobCompletion1()
processJobCompletionStage1 Process job completion.
void captureImage()
captureImage Initiates image capture in the active job.
void restartCamera(const QString &name)
restartCamera Restarts the INDI driver associated with a camera.
bool hasCoolerControl()
Does the CCD has a cooler control (On/Off) ?
CameraChip class controls a particular chip in camera.
Camera class controls an INDI Camera device.
void sendNewProperty(INDI::Property prop)
Send new property command to server.
INDI::PropertyView< IText > * getText(const QString &name) const
Class handles control of INDI dome devices.
Handles operation of a remotely controlled dust cover cap.
Handles operation of a remotely controlled light box.
device handle controlling Mounts.
void newTargetName(const QString &name)
The mount has finished the slew to a new target.
Rotator class handles control of INDI Rotator devices.
This is the main window for KStars.
static KStars * Instance()
QString i18n(const char *text, const TYPE &arg...)
Ekos is an advanced Astrophotography tool for Linux.
CaptureState
Capture states.
@ SCRIPT_POST_CAPTURE
Script to run after a sequence capture is completed.
@ SCRIPT_POST_JOB
Script to run after a sequence job is completed.
@ SCRIPT_PRE_CAPTURE
Script to run before a sequence capture is started.
@ SCRIPT_PRE_JOB
Script to run before a sequence job is started.
ButtonCode warningContinueCancel(QWidget *parent, const QString &text, const QString &title=QString(), const KGuiItem &buttonContinue=KStandardGuiItem::cont(), const KGuiItem &buttonCancel=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Notify)
NETWORKMANAGERQT_EXPORT NetworkManager::Status status()
void replace(qsizetype i, const QJsonValue &value)
void append(QList< T > &&value)
qsizetype count() const const
bool isEmpty() const const
QStatusBar * statusBar() const const
size_type remove(const Key &key)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
T qobject_cast(QObject *object)
QObject * sender() const const
void errorOccurred(QProcess::ProcessError error)
void finished(int exitCode, QProcess::ExitStatus exitStatus)
void readyReadStandardError()
void readyReadStandardOutput()
void start(OpenMode mode)
bool isNull() const const
void showMessage(const QString &message, int timeout)
QString arg(Args &&... args) const const
bool isEmpty() const const
QString number(double n, char format, int precision)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QUrl fromLocalFile(const QString &localFile)
bool isValid() const const
double toDouble(bool *ok) const const
int toInt(bool *ok) const const
QPoint toPoint() const const
QRect toRect() const const
QString toString() const const
uint toUInt(bool *ok) const const
Object to hold FITS Header records.