[prev in list] [next in list] [prev in thread] [next in thread]
List: kde-commits
Subject: [kstars/bleeding] kstars/ekos/capture: Moving all capture module files to their own dedicated direct
From: Jasem Mutlaq <mutlaqja () ikarustech ! com>
Date: 2016-09-24 20:20:10
Message-ID: E1bntQg-00086b-BZ () code ! kde ! org
[Download RAW message or body]
Git commit 37fecee9cca8503903c8e6e60ae74274bf5ad685 by Jasem Mutlaq.
Committed on 24/09/2016 at 20:13.
Pushed by mutlaqja into branch 'bleeding'.
Moving all capture module files to their own dedicated directly
A +277 -0 kstars/ekos/capture/calibrationoptions.ui
A +3805 -0 kstars/ekos/capture/capture.cpp [License: GPL (v2+)]
A +590 -0 kstars/ekos/capture/capture.h [License: GPL (v2+)]
A +1602 -0 kstars/ekos/capture/capture.ui
A +422 -0 kstars/ekos/capture/sequencejob.cpp [License: GPL (v2+)]
A +206 -0 kstars/ekos/capture/sequencejob.h [License: GPL (v2+)]
http://commits.kde.org/kstars/37fecee9cca8503903c8e6e60ae74274bf5ad685
diff --git a/kstars/ekos/capture/calibrationoptions.ui \
b/kstars/ekos/capture/calibrationoptions.ui new file mode 100644
index 0000000..d7411a2
--- /dev/null
+++ b/kstars/ekos/capture/calibrationoptions.ui
@@ -0,0 +1,277 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>calibrationOptions</class>
+ <widget class="QDialog" name="calibrationOptions">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>475</width>
+ <height>241</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Calibration Options</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="toolTip">
+ <string>Specify the source the flat field evenly illuminated light \
source</string> + </property>
+ <property name="title">
+ <string>Flat Source</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QRadioButton" name="manualSourceC">
+ <property name="toolTip">
+ <string>Light source triggered by the user manually</string>
+ </property>
+ <property name="text">
+ <string>Manual</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="flatDeviceSourceC">
+ <property name="toolTip">
+ <string>For dark and bias frames, close the dust cap before proceeding. \
For flat frames, close the dust cap and turn on the light source.</string> + \
</property> + <property name="text">
+ <string>Dust Cover with Built-in Flat Light</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="darkDeviceSourceC">
+ <property name="toolTip">
+ <string>For dark and bias frames, close the dust cap before proceeding. \
For flat frames, open the dust cap and turn on the light source.</string> + \
</property> + <property name="text">
+ <string>Dust Cover with External Flat Light</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QRadioButton" name="wallSourceC">
+ <property name="toolTip">
+ <string>Slew mount to the specified Azimuth/Altitude coordinates before \
taking flat field images</string> + </property>
+ <property name="text">
+ <string>Wall</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Az:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="dmsBox" name="azBox">
+ <property name="toolTip">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Alt:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="dmsBox" name="altBox">
+ <property name="toolTip">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="dawnDuskFlatsC">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="toolTip">
+ <string>Use Dawn and Dusk light</string>
+ </property>
+ <property name="text">
+ <string>Dawn/Dusk</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="title">
+ <string>Flat Duration</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QRadioButton" name="manualDurationC">
+ <property name="toolTip">
+ <string>Use the frame exposure value</string>
+ </property>
+ <property name="text">
+ <string>Manual</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QRadioButton" name="ADUC">
+ <property name="toolTip">
+ <string>Calculate optimal exposure time given the required ADU. If a \
controllable device is selected, calculate optimal brightness.</string> + \
</property> + <property name="text">
+ <string>ADU</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="ADUValue">
+ <property name="toolTip">
+ <string/>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ <property name="singleStep">
+ <number>1000</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="parkMountC">
+ <property name="text">
+ <string>Park Mount</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="parkDomeC">
+ <property name="text">
+ <string>Park Dome</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>dmsBox</class>
+ <extends>QLineEdit</extends>
+ <header>widgets/dmsbox.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <tabstops>
+ <tabstop>manualSourceC</tabstop>
+ <tabstop>flatDeviceSourceC</tabstop>
+ <tabstop>darkDeviceSourceC</tabstop>
+ <tabstop>wallSourceC</tabstop>
+ <tabstop>azBox</tabstop>
+ <tabstop>altBox</tabstop>
+ <tabstop>dawnDuskFlatsC</tabstop>
+ <tabstop>manualDurationC</tabstop>
+ <tabstop>ADUC</tabstop>
+ <tabstop>ADUValue</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>calibrationOptions</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>calibrationOptions</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/kstars/ekos/capture/capture.cpp b/kstars/ekos/capture/capture.cpp
new file mode 100644
index 0000000..0f7b1aa
--- /dev/null
+++ b/kstars/ekos/capture/capture.cpp
@@ -0,0 +1,3805 @@
+/* Ekos
+ Copyright (C) 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
+
+ This application is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+ */
+
+#include <QFileDialog>
+#include <QDirIterator>
+#include <QStandardPaths>
+
+#include <KMessageBox>
+#include <KDirWatch>
+#include <KLocalizedString>
+#include <KNotifications/KNotification>
+
+#include <basedevice.h>
+#include <lilxml.h>
+
+#include "Options.h"
+
+#include "kstars.h"
+#include "kstarsdata.h"
+
+#include "capture.h"
+#include "sequencejob.h"
+
+#include "indi/driverinfo.h"
+#include "indi/indifilter.h"
+#include "indi/clientmanager.h"
+
+#include "fitsviewer/fitsviewer.h"
+#include "fitsviewer/fitsview.h"
+
+#include "darklibrary.h"
+#include "ekosmanager.h"
+#include "captureadaptor.h"
+#include "ui_calibrationoptions.h"
+
+#include "QProgressIndicator.h"
+
+#define INVALID_TEMPERATURE 10000
+#define INVALID_HA 10000
+#define MF_TIMER_TIMEOUT 90000
+#define MF_RA_DIFF_LIMIT 4
+#define MAX_CAPTURE_RETRIES 3
+
+namespace Ekos
+{
+
+Capture::Capture()
+{
+ setupUi(this);
+
+ new CaptureAdaptor(this);
+ QDBusConnection::sessionBus().registerObject("/KStars/Ekos/Capture", this);
+
+ dirPath = QUrl(QDir::homePath());
+
+ state = CAPTURE_IDLE;
+ focusState = FOCUS_IDLE;
+ guideState = GUIDE_IDLE;
+ alignState = ALIGN_IDLE;
+
+ currentCCD = NULL;
+ currentTelescope = NULL;
+ currentFilter = NULL;
+ dustCap = NULL;
+ lightBox= NULL;
+ dome = NULL;
+
+ filterSlot = NULL;
+ filterName = NULL;
+ activeJob = NULL;
+
+ targetChip = NULL;
+ guideChip = NULL;
+
+ targetADU = 0;
+ flatFieldDuration = DURATION_MANUAL;
+ flatFieldSource = SOURCE_MANUAL;
+ calibrationStage = CAL_NONE;
+ preMountPark = false;
+ preDomePark = false;
+
+ deviationDetected = false;
+ spikeDetected = false;
+ isBusy = false;
+
+ ignoreJobProgress=true;
+
+ dustCapLightEnabled = lightBoxLightEnabled = false;
+
+ //isAutoGuiding = false;
+ guideDither = false;
+ isAutoFocus = false;
+ autoFocusStatus = false;
+ resumeAlignmentAfterFlip= false;
+
+ mDirty = false;
+ jobUnderEdit = false;
+ currentFilterPosition = -1;
+
+ //calibrationState = CALIBRATE_NONE;
+ meridianFlipStage = MF_NONE;
+ resumeGuidingAfterFlip = false;
+
+ //ADURaw1 = ADURaw2 = ExpRaw1 = ExpRaw2 = -1;
+ //ADUSlope = 0;
+
+ pi = new QProgressIndicator(this);
+
+ progressLayout->addWidget(pi, 0, 4, 1, 1);
+
+ seqFileCount = 0;
+ //seqWatcher = new KDirWatch();
+ seqTimer = new QTimer(this);
+ connect(seqTimer, SIGNAL(timeout()), this, SLOT(captureImage()));
+
+ connect(startB, SIGNAL(clicked()), this, SLOT(toggleSequence()));
+ connect(pauseB, SIGNAL(clicked()), this, SLOT(pause()));
+
+ startB->setIcon(QIcon::fromTheme("media-playback-start"));
+ pauseB->setIcon(QIcon::fromTheme("media-playback-pause"));
+
+ connect(binXIN, SIGNAL(valueChanged(int)), binYIN, SLOT(setValue(int)));
+
+ connect(CCDCaptureCombo, SIGNAL(activated(QString)), this, \
SLOT(setDefaultCCD(QString))); + connect(CCDCaptureCombo, \
SIGNAL(currentIndexChanged(int)), this, SLOT(checkCCD(int))); +
+
+ connect(FilterCaptureCombo, SIGNAL(activated(int)), this, \
SLOT(checkFilter(int))); +
+ connect(previewB, SIGNAL(clicked()), this, SLOT(captureOne()));
+
+ //connect( seqWatcher, SIGNAL(dirty(QString)), this, \
SLOT(checkSeqFile(QString))); +
+ connect(addToQueueB, SIGNAL(clicked()), this, SLOT(addJob()));
+ connect(removeFromQueueB, SIGNAL(clicked()), this, SLOT(removeJob()));
+ connect(queueUpB, SIGNAL(clicked()), this, SLOT(moveJobUp()));
+ connect(queueDownB, SIGNAL(clicked()), this, SLOT(moveJobDown()));
+ connect(selectFITSDirB, SIGNAL(clicked()), this, SLOT(saveFITSDirectory()));
+ connect(queueSaveB, SIGNAL(clicked()), this, SLOT(saveSequenceQueue()));
+ connect(queueSaveAsB, SIGNAL(clicked()), this, SLOT(saveSequenceQueueAs()));
+ connect(queueLoadB, SIGNAL(clicked()), this, SLOT(loadSequenceQueue()));
+ connect(resetB, SIGNAL(clicked()), this, SLOT(resetJobs()));
+ connect(queueTable, SIGNAL(doubleClicked(QModelIndex)), this, \
SLOT(editJob(QModelIndex))); + connect(queueTable, SIGNAL(itemSelectionChanged()), \
this, SLOT(resetJobEdit())); + connect(setTemperatureB, SIGNAL(clicked()), this, \
SLOT(setTemperature())); + connect(temperatureIN, SIGNAL(editingFinished()), \
setTemperatureB, SLOT(setFocus())); + connect(frameTypeCombo, \
SIGNAL(currentIndexChanged(int)), this, SLOT(checkFrameType(int))); + \
connect(resetFrameB, SIGNAL(clicked()), this, SLOT(resetFrame())); + \
connect(calibrationB, SIGNAL(clicked()), this, SLOT(openCalibrationDialog())); +
+ addToQueueB->setIcon(QIcon::fromTheme("list-add"));
+ removeFromQueueB->setIcon(QIcon::fromTheme("list-remove"));
+ queueUpB->setIcon(QIcon::fromTheme("go-up"));
+ queueDownB->setIcon(QIcon::fromTheme("go-down"));
+ selectFITSDirB->setIcon(QIcon::fromTheme("document-open-folder"));
+ queueLoadB->setIcon(QIcon::fromTheme("document-open"));
+ queueSaveB->setIcon(QIcon::fromTheme("document-save"));
+ queueSaveAsB->setIcon(QIcon::fromTheme("document-save-as"));
+ resetB->setIcon(QIcon::fromTheme("system-reboot"));
+ resetFrameB->setIcon(QIcon::fromTheme("view-refresh"));
+ calibrationB->setIcon(QIcon::fromTheme("run-build"));
+
+ addToQueueB->setToolTip(i18n("Add job to sequence queue"));
+ removeFromQueueB->setToolTip(i18n("Remove job from sequence queue"));
+
+ fitsDir->setText(Options::fitsDir());
+
+ seqExpose = 0;
+ seqTotalCount = 0;
+ seqCurrentCount = 0;
+ seqDelay = 0;
+ fileHFR=0;
+ useGuideHead = false;
+ guideDither = false;
+ firstAutoFocus = true;
+
+ foreach(QString filter, FITSViewer::filterTypes)
+ filterCombo->addItem(filter);
+
+ guideDeviationCheck->setChecked(Options::enforceGuideDeviation());
+ guideDeviation->setValue(Options::guideDeviation());
+ autofocusCheck->setChecked(Options::enforceAutofocus());
+ parkCheck->setChecked(Options::autoParkTelescope());
+ meridianCheck->setChecked(Options::autoMeridianFlip());
+ meridianHours->setValue(Options::autoMeridianHours());
+
+ connect(autofocusCheck, SIGNAL(toggled(bool)), this, SLOT(setDirty()));
+ connect(HFRPixels, SIGNAL(valueChanged(double)), this, SLOT(setDirty()));
+ connect(guideDeviationCheck, SIGNAL(toggled(bool)), this, SLOT(setDirty()));
+ connect(guideDeviation, SIGNAL(valueChanged(double)), this, SLOT(setDirty()));
+ connect(meridianCheck, SIGNAL(toggled(bool)), this, SLOT(setDirty()));
+ connect(meridianHours, SIGNAL(valueChanged(double)), this, SLOT(setDirty()));
+ connect(parkCheck, SIGNAL(toggled(bool)), this, SLOT(setDirty()));
+
+
+ // FIXME remove this later
+ connect(&postCaptureScript, SIGNAL(finished(int)), this, \
SLOT(postScriptFinished(int))); +}
+
+Capture::~Capture()
+{
+ qDeleteAll(jobs);
+}
+
+void Capture::setDefaultCCD(QString ccd)
+{
+ Options::setDefaultCaptureCCD(ccd);
+}
+
+void Capture::addCCD(ISD::GDInterface *newCCD)
+{
+ ISD::CCD *ccd = static_cast<ISD::CCD *> (newCCD);
+
+ if (CCDs.contains(ccd))
+ return;
+
+ CCDs.append(ccd);
+
+ CCDCaptureCombo->addItem(ccd->getDeviceName());
+
+ if (Filters.count() > 0)
+ syncFilterInfo();
+ //checkCCD(CCDs.count()-1);
+ //CCDCaptureCombo->setCurrentIndex(CCDs.count()-1);
+
+}
+
+void Capture::addGuideHead(ISD::GDInterface *newCCD)
+{
+ QString guiderName = newCCD->getDeviceName() + QString(" Guider");
+
+ if (CCDCaptureCombo->findText(guiderName) == -1)
+ {
+ CCDCaptureCombo->addItem(guiderName);
+ CCDs.append(static_cast<ISD::CCD *> (newCCD));
+ }
+}
+
+void Capture::addFilter(ISD::GDInterface *newFilter)
+{
+ foreach(ISD::GDInterface *filter, Filters)
+ {
+ if (!strcmp(filter->getDeviceName(), newFilter->getDeviceName()))
+ return;
+ }
+
+ FilterCaptureCombo->addItem(newFilter->getDeviceName());
+
+ Filters.append(static_cast<ISD::Filter *>(newFilter));
+
+ checkFilter(0);
+
+ FilterCaptureCombo->setCurrentIndex(0);
+
+}
+
+void Capture::pause()
+{
+ pauseFunction=NULL;
+ state = CAPTURE_PAUSED;
+ emit newStatus(Ekos::CAPTURE_PAUSED);
+ appendLogText(i18n("Sequence shall be paused after current exposure is \
complete.")); + pauseB->setEnabled(false);
+
+ startB->setIcon(QIcon::fromTheme("media-playback-start"));
+ startB->setToolTip(i18n("Resume Sequence"));
+}
+
+void Capture::toggleSequence()
+{
+ if (state == CAPTURE_PAUSED)
+ {
+ startB->setIcon(QIcon::fromTheme("media-playback-stop"));
+ startB->setToolTip(i18n("Stop Sequence"));
+ pauseB->setEnabled(true);
+
+ state = CAPTURE_CAPTURING;
+ emit newStatus(Ekos::CAPTURE_CAPTURING);
+
+ appendLogText(i18n("Sequence resumed."));
+
+ // Call from where ever we have left of when we paused
+ if (pauseFunction)
+ (this->*pauseFunction)();
+ }
+ else if (state == CAPTURE_IDLE || state == CAPTURE_COMPLETE)
+ {
+ start();
+ }
+ else
+ {
+ abort();
+ }
+}
+
+void Capture::start()
+{
+ if (darkSubCheck->isChecked())
+ {
+ KMessageBox::error(this, i18n("Auto dark subtract is not supported in batch \
mode.")); + return;
+ }
+
+ Options::setGuideDeviation(guideDeviation->value());
+ Options::setEnforceGuideDeviation(guideDeviationCheck->isChecked());
+ Options::setEnforceAutofocus(autofocusCheck->isChecked());
+ Options::setAutoMeridianFlip(meridianCheck->isChecked());
+ Options::setAutoMeridianHours(meridianHours->value());
+ Options::setAutoParkTelescope(parkCheck->isChecked());
+
+ if (queueTable->rowCount() ==0)
+ addJob();
+
+ SequenceJob *first_job = NULL;
+
+ foreach(SequenceJob *job, jobs)
+ {
+ if (job->getStatus() == SequenceJob::JOB_IDLE || job->getStatus() == \
SequenceJob::JOB_ABORTED) + {
+ first_job = job;
+ break;
+ }
+ }
+
+ if (first_job == NULL)
+ {
+ foreach(SequenceJob *job, jobs)
+ {
+ if (job->getStatus() != SequenceJob::JOB_DONE)
+ {
+ appendLogText(i18n("No pending jobs found. Please add a job to the \
sequence queue.")); + return;
+ }
+ }
+
+ if (KMessageBox::warningContinueCancel(NULL, i18n("All jobs are complete. Do \
you want to reset the status of all jobs and restart capturing?"), + \
i18n("Reset job status"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), + \
"reset_job_complete_status_warning") !=KMessageBox::Continue) + return;
+
+ foreach(SequenceJob *job, jobs)
+ job->resetStatus();
+
+ first_job = jobs.first();
+ }
+
+ deviationDetected = false;
+ spikeDetected = false;
+
+ initialHA = getCurrentHA();
+ meridianFlipStage = MF_NONE;
+
+ // Check if we need to update the sequence directory numbers before starting
+ /*for (int i=0; i < jobs.count(); i++)
+ {
+ QString firstDir = jobs.at(i)->getFITSDir();
+ int sequenceID=1;
+
+ for (int j=i+1; j < jobs.count(); j++)
+ {
+ if (firstDir == jobs.at(j)->getFITSDir())
+ {
+ jobs.at(i)->setFITSDir(QString("%1/Sequence_1").arg(firstDir));
+ jobs.at(j)->setFITSDir(QString("%1/Sequence_%2").arg(jobs.at(j)->getFITSDir()).arg(++sequenceID));
+ }
+ }
+ }*/
+
+ state = CAPTURE_PROGRESS;
+ emit newStatus(Ekos::CAPTURE_PROGRESS);
+
+ startB->setIcon(QIcon::fromTheme("media-playback-stop"));
+ startB->setToolTip(i18n("Stop Sequence"));
+ pauseB->setEnabled(true);
+
+ foreach (QAbstractButton *button, queueEditButtonGroup->buttons())
+ button->setEnabled(false);
+
+ prepareJob(first_job);
+
+}
+
+void Capture::stop(bool abort)
+{
+
+ retries = 0;
+ seqTotalCount = 0;
+ seqCurrentCount = 0;
+ //ADURaw1 = ADURaw2 = ExpRaw1 = ExpRaw2 = -1;
+ //ADUSlope = 0;
+ ADURaw.clear();
+ ExpRaw.clear();
+
+ calibrationStage = CAL_NONE;
+
+ if (activeJob)
+ {
+ if (activeJob->getStatus() == SequenceJob::JOB_BUSY)
+ {
+ KNotification::event( QLatin1String( "CaptureFailed"), i18n("CCD capture \
failed with errors") ); + activeJob->abort();
+ emit newStatus(Ekos::CAPTURE_ABORTED);
+ }
+
+ activeJob->disconnect(this);
+ activeJob->reset();
+ }
+
+ state = CAPTURE_IDLE;
+
+ // Turn off any calibration light, IF they were turned on by Capture module
+ if (dustCap && dustCapLightEnabled)
+ {
+ dustCapLightEnabled = false;
+ dustCap->SetLightEnabled(false);
+ }
+ if (lightBox && lightBoxLightEnabled)
+ {
+ lightBoxLightEnabled = false;
+ lightBox->SetLightEnabled(false);
+ }
+
+ secondsLabel->clear();
+ disconnect(currentCCD, SIGNAL(BLOBUpdated(IBLOB*)), this, \
SLOT(newFITS(IBLOB*))); + disconnect(currentCCD, \
SIGNAL(newImage(QImage*,ISD::CCDChip*)), this, \
SLOT(sendNewImage(QImage*,ISD::CCDChip*))); + disconnect(currentCCD, \
SIGNAL(newExposureValue(ISD::CCDChip*,double, IPState)), this, \
SLOT(updateCaptureProgress(ISD::CCDChip*,double,IPState))); +
+ currentCCD->setFITSDir("");
+
+ imgProgress->reset();
+ imgProgress->setEnabled(false);
+
+ fullImgCountOUT->setText(QString());
+ currentImgCountOUT->setText(QString());
+ exposeOUT->setText(QString());
+
+ setBusy(false);
+
+ if (abort)
+ {
+ startB->setIcon(QIcon::fromTheme("media-playback-start"));
+ startB->setToolTip(i18n("Start Sequence"));
+ pauseB->setEnabled(false);
+ }
+
+ foreach (QAbstractButton *button, queueEditButtonGroup->buttons())
+ button->setEnabled(true);
+
+ seqTimer->stop();
+
+}
+
+void Capture::sendNewImage(QImage *image, ISD::CCDChip *myChip)
+{
+ if (activeJob && myChip != guideChip)
+ emit newImage(image, activeJob);
+}
+
+bool Capture::setCCD(QString device)
+{
+ for (int i=0; i < CCDCaptureCombo->count(); i++)
+ if (device == CCDCaptureCombo->itemText(i))
+ {
+ CCDCaptureCombo->setCurrentIndex(i);
+ return true;
+ }
+
+ return false;
+}
+
+void Capture::checkCCD(int ccdNum)
+{
+ if (ccdNum == -1)
+ {
+ ccdNum = CCDCaptureCombo->currentIndex();
+
+ if (ccdNum == -1)
+ return;
+ }
+
+ foreach(ISD::CCD *ccd, CCDs)
+ {
+ disconnect(ccd, SIGNAL(numberUpdated(INumberVectorProperty*)), this, \
SLOT(processCCDNumber(INumberVectorProperty*))); + disconnect(ccd, \
SIGNAL(newTemperatureValue(double)), this, SLOT(updateCCDTemperature(double))); + \
disconnect(ccd, SIGNAL(newRemoteFile(QString)), this, \
SLOT(setNewRemoteFile(QString))); + }
+
+ if (ccdNum <= CCDs.count())
+ {
+ // Check whether main camera or guide head only
+ currentCCD = CCDs.at(ccdNum);
+
+ if (CCDCaptureCombo->itemText(ccdNum).right(6) == QString("Guider"))
+ {
+ useGuideHead = true;
+ targetChip = currentCCD->getChip(ISD::CCDChip::GUIDE_CCD);
+ }
+ else
+ {
+ currentCCD = CCDs.at(ccdNum);
+ targetChip = currentCCD->getChip(ISD::CCDChip::PRIMARY_CCD);
+ useGuideHead = false;
+ }
+
+ if (currentCCD->hasCooler())
+ {
+
+ temperatureCheck->setEnabled(true);
+ temperatureIN->setEnabled(true);
+
+ if (currentCCD->getBaseDevice()->getPropertyPermission("CCD_TEMPERATURE") \
!= IP_RO) + {
+ double min,max,step;
+ setTemperatureB->setEnabled(true);
+ temperatureIN->setReadOnly(false);
+ currentCCD->getMinMaxStep("CCD_TEMPERATURE", \
"CCD_TEMPERATURE_VALUE", &min, &max, &step); + \
temperatureIN->setMinimum(min); + temperatureIN->setMaximum(max);
+ temperatureIN->setSingleStep(1);
+ }
+ else
+ {
+ setTemperatureB->setEnabled(false);
+ temperatureIN->setReadOnly(true);
+ }
+
+ double temperature=0;
+ if (currentCCD->getTemperature(&temperature))
+ {
+ temperatureOUT->setText(QString::number(temperature, 'f', 2));
+ if (temperatureIN->cleanText().isEmpty())
+ temperatureIN->setValue(temperature);
+ }
+ }
+ else
+ {
+ temperatureCheck->setEnabled(false);
+ temperatureIN->setEnabled(false);
+ temperatureIN->clear();
+ setTemperatureB->setEnabled(false);
+ }
+
+ updateFrameProperties();
+
+ QStringList frameTypes = targetChip->getFrameTypes();
+
+ frameTypeCombo->clear();
+
+ if (frameTypes.isEmpty())
+ frameTypeCombo->setEnabled(false);
+ else
+ {
+ frameTypeCombo->setEnabled(true);
+ frameTypeCombo->addItems(frameTypes);
+ frameTypeCombo->setCurrentIndex(targetChip->getFrameType());
+ }
+
+ QStringList isoList = targetChip->getISOList();
+ ISOCombo->clear();
+
+ if (isoList.isEmpty())
+ {
+ ISOCombo->setEnabled(false);
+ ISOLabel->setEnabled(false);
+ }
+ else
+ {
+ ISOCombo->setEnabled(true);
+ ISOLabel->setEnabled(true);
+ ISOCombo->addItems(isoList);
+ ISOCombo->setCurrentIndex(targetChip->getISOIndex());
+ }
+
+ connect(currentCCD, SIGNAL(numberUpdated(INumberVectorProperty*)), this, \
SLOT(processCCDNumber(INumberVectorProperty*)), Qt::UniqueConnection); + \
connect(currentCCD, SIGNAL(newTemperatureValue(double)), this, \
SLOT(updateCCDTemperature(double)), Qt::UniqueConnection); + \
connect(currentCCD, SIGNAL(newRemoteFile(QString)), this, \
SLOT(setNewRemoteFile(QString))); + }
+}
+
+void Capture::updateFrameProperties(bool reset)
+{
+ int x,y,w,h;
+ int binx=1,biny=1;
+ double min,max,step;
+ int xstep=0, ystep=0;
+
+ QString frameProp = useGuideHead ? QString("GUIDER_FRAME") : \
QString("CCD_FRAME"); + QString exposureProp = useGuideHead ? \
QString("GUIDER_EXPOSURE") : QString("CCD_EXPOSURE"); + QString exposureElem = \
useGuideHead ? QString("GUIDER_EXPOSURE_VALUE") : QString("CCD_EXPOSURE_VALUE"); + \
targetChip = useGuideHead ? currentCCD->getChip(ISD::CCDChip::GUIDE_CCD) : \
currentCCD->getChip(ISD::CCDChip::PRIMARY_CCD); +
+ frameWIN->setEnabled(targetChip->canSubframe());
+ frameHIN->setEnabled(targetChip->canSubframe());
+ frameXIN->setEnabled(targetChip->canSubframe());
+ frameYIN->setEnabled(targetChip->canSubframe());
+
+ binXIN->setEnabled(targetChip->canBin());
+ binYIN->setEnabled(targetChip->canBin());
+
+ if (currentCCD->getMinMaxStep(exposureProp, exposureElem, &min, &max, &step))
+ {
+ exposureIN->setMinimum(min);
+ exposureIN->setMaximum(max);
+ exposureIN->setSingleStep(step);
+ }
+
+ if (currentCCD->getMinMaxStep(frameProp, "WIDTH", &min, &max, &step))
+ {
+
+ if (min == max)
+ return;
+
+ if (step == 0)
+ xstep = (int) max * 0.05;
+ else
+ xstep = step;
+
+ if (min >= 0 && max > 0)
+ {
+ frameWIN->setMinimum(min);
+ frameWIN->setMaximum(max);
+ frameWIN->setSingleStep(xstep);
+ }
+ }
+ else
+ return;
+
+ if (currentCCD->getMinMaxStep(frameProp, "HEIGHT", &min, &max, &step))
+ {
+ if (min == max)
+ return;
+
+ if (step == 0)
+ ystep = (int) max * 0.05;
+ else
+ ystep = step;
+
+ if (min >= 0 && max > 0)
+ {
+ frameHIN->setMinimum(min);
+ frameHIN->setMaximum(max);
+ frameHIN->setSingleStep(ystep);
+ }
+ }
+ else
+ return;
+
+ if (currentCCD->getMinMaxStep(frameProp, "X", &min, &max, &step))
+ {
+ if (min == max)
+ return;
+
+ if (step == 0)
+ step = xstep;
+
+ if (min >= 0 && max > 0)
+ {
+ frameXIN->setMinimum(min);
+ frameXIN->setMaximum(max);
+ frameXIN->setSingleStep(step);
+ }
+ }
+ else
+ return;
+
+ if (currentCCD->getMinMaxStep(frameProp, "Y", &min, &max, &step))
+ {
+ if (min == max)
+ return;
+
+ if (step == 0)
+ step = ystep;
+
+ if (min >= 0 && max > 0)
+ {
+ frameYIN->setMinimum(min);
+ frameYIN->setMaximum(max);
+ frameYIN->setSingleStep(step);
+ }
+ }
+ else
+ return;
+
+ if (reset || frameSettings.contains(targetChip) == false)
+ {
+ QVariantMap settings;
+
+ settings["x"] = 0;
+ settings["y"] = 0;
+ settings["w"] = frameWIN->maximum();
+ settings["h"] = frameHIN->maximum();
+ settings["binx"] = 1;
+ settings["biny"] = 1;
+
+ frameSettings[targetChip] = settings;
+ }
+
+ if (frameSettings.contains(targetChip))
+ {
+ QVariantMap settings = frameSettings[targetChip];
+
+ if (targetChip->canBin())
+ {
+ targetChip->getMaxBin(&binx, &biny);
+ binXIN->setMaximum(binx);
+ binYIN->setMaximum(biny);
+
+ binXIN->setValue(settings["binx"].toInt());
+ binYIN->setValue(settings["biny"].toInt());
+ }
+ else
+ {
+ binXIN->setValue(1);
+ binYIN->setValue(1);
+ }
+
+ x = settings["x"].toInt();
+ y = settings["y"].toInt();
+ w = settings["w"].toInt();
+ h = settings["h"].toInt();
+
+ if (x >= 0)
+ frameXIN->setValue(x);
+ if (y >= 0)
+ frameYIN->setValue(y);
+ if (w > 0)
+ frameWIN->setValue(w);
+ if (h > 0)
+ frameHIN->setValue(h);
+ }
+}
+
+void Capture::processCCDNumber(INumberVectorProperty *nvp)
+{
+ if (currentCCD && ( (!strcmp(nvp->name, "CCD_FRAME") && useGuideHead == false) \
|| (!strcmp(nvp->name, "GUIDER_FRAME") && useGuideHead))) + \
updateFrameProperties(); +}
+
+void Capture::resetFrame()
+{
+ targetChip = useGuideHead ? currentCCD->getChip(ISD::CCDChip::GUIDE_CCD) : \
currentCCD->getChip(ISD::CCDChip::PRIMARY_CCD); + targetChip->resetFrame();
+ updateFrameProperties(true);
+}
+
+void Capture::syncFrameType(ISD::GDInterface *ccd)
+{
+ if (strcmp(ccd->getDeviceName(), CCDCaptureCombo->currentText().toLatin1()))
+ return;
+
+ ISD::CCDChip *tChip = NULL;
+ tChip = (static_cast<ISD::CCD *> (ccd) )->getChip(ISD::CCDChip::PRIMARY_CCD);
+
+ QStringList frameTypes = tChip->getFrameTypes();
+
+ frameTypeCombo->clear();
+
+ if (frameTypes.isEmpty())
+ frameTypeCombo->setEnabled(false);
+ else
+ {
+ frameTypeCombo->setEnabled(true);
+ frameTypeCombo->addItems(frameTypes);
+ frameTypeCombo->setCurrentIndex(tChip->getFrameType());
+ }
+
+
+}
+
+bool Capture::setFilter(QString device, int filterSlot)
+{
+ bool deviceFound=false;
+
+ for (int i=0; i < FilterCaptureCombo->count(); i++)
+ if (device == FilterCaptureCombo->itemText(i))
+ {
+ checkFilter(i);
+ deviceFound = true;
+ break;
+ }
+
+ if (deviceFound == false)
+ return false;
+
+ if (filterSlot < FilterCaptureCombo->count())
+ FilterCaptureCombo->setCurrentIndex(filterSlot);
+
+ return true;
+}
+
+void Capture::checkFilter(int filterNum)
+{
+
+ if (filterNum == -1)
+ {
+ filterNum = FilterCaptureCombo->currentIndex();
+ if (filterNum == -1)
+ return;
+ }
+
+
+ QStringList filterAlias = Options::filterAlias();
+
+ if (filterNum <= Filters.count())
+ currentFilter = Filters.at(filterNum);
+
+ syncFilterInfo();
+
+ FilterPosCombo->clear();
+
+ filterName = currentFilter->getBaseDevice()->getText("FILTER_NAME");
+ filterSlot = currentFilter->getBaseDevice()->getNumber("FILTER_SLOT");
+
+ if (filterSlot == NULL)
+ {
+ KMessageBox::error(0, i18n("Unable to find FILTER_SLOT property in driver \
%1", currentFilter->getBaseDevice()->getDeviceName())); + return;
+ }
+
+ for (int i=0; i < filterSlot->np[0].max; i++)
+ {
+ QString item;
+
+ if (filterName != NULL && (i < filterName->ntp))
+ item = filterName->tp[i].text;
+ else if (i < filterAlias.count() && filterAlias[i].isEmpty() == false)
+ item = filterAlias.at(i);
+ else
+ item = QString("Filter_%1").arg(i+1);
+
+ FilterPosCombo->addItem(item);
+
+ }
+
+ FilterPosCombo->setCurrentIndex( (int) filterSlot->np[0].value-1);
+
+ currentFilterPosition = (int) filterSlot->np[0].value;
+
+ if (activeJob && (activeJob->getStatus() == SequenceJob::JOB_ABORTED || \
activeJob->getStatus() == SequenceJob::JOB_IDLE)) + \
activeJob->setCurrentFilter(currentFilterPosition); +
+}
+
+void Capture::syncFilterInfo()
+{
+ if (currentCCD && currentFilter)
+ {
+ ITextVectorProperty *activeDevices = \
currentCCD->getBaseDevice()->getText("ACTIVE_DEVICES"); + if (activeDevices)
+ {
+ IText *activeFilter = IUFindText(activeDevices, "ACTIVE_FILTER");
+ if (activeFilter && strcmp(activeFilter->text, \
currentFilter->getDeviceName())) + {
+ IUSaveText(activeFilter, currentFilter->getDeviceName());
+ currentCCD->getDriverInfo()->getClientManager()->sendNewText(activeDevices);
+ }
+ }
+ }
+}
+
+bool Capture::startNextExposure()
+{
+ if (state == CAPTURE_PAUSED)
+ {
+ pauseFunction = &Capture::startNextExposure;
+ appendLogText(i18n("Sequence paused."));
+ secondsLabel->setText(i18n("Paused..."));
+ return false;
+ }
+
+ if (seqDelay > 0)
+ {
+ secondsLabel->setText(i18n("Waiting..."));
+ state = CAPTURE_WAITING;
+ emit newStatus(Ekos::CAPTURE_WAITING);
+ }
+
+ seqTimer->start(seqDelay);
+
+ return true;
+}
+
+void Capture::newFITS(IBLOB *bp)
+{
+ ISD::CCDChip *tChip = NULL;
+
+ // If there is no active job, ignore
+ if (activeJob == NULL || meridianFlipStage >= MF_ALIGNING)
+ return;
+
+ if (currentCCD->getUploadMode() != ISD::CCD::UPLOAD_LOCAL)
+ {
+ if (bp == NULL)
+ {
+ abort();
+ return;
+ }
+
+ if (!strcmp(bp->name, "CCD2"))
+ tChip = currentCCD->getChip(ISD::CCDChip::GUIDE_CCD);
+ else
+ tChip = currentCCD->getChip(ISD::CCDChip::PRIMARY_CCD);
+
+ if (tChip != targetChip)
+ return;
+
+ if (targetChip->getCaptureMode() == FITS_FOCUS || \
targetChip->getCaptureMode() == FITS_GUIDE) + return;
+
+ // If this is a preview job, make sure to enable preview button after
+ // we receive the FITS
+ if (activeJob->isPreview() && previewB->isEnabled() == false)
+ previewB->setEnabled(true);
+
+ // If the FITS is not for our device, simply ignore
+ //if (QString(bp->bvp->device) != currentCCD->getDeviceName() || \
(startB->isEnabled() && previewB->isEnabled())) + if (QString(bp->bvp->device) \
!= currentCCD->getDeviceName() || state == CAPTURE_IDLE) + return;
+
+ disconnect(currentCCD, SIGNAL(BLOBUpdated(IBLOB*)), this, \
SLOT(newFITS(IBLOB*))); + disconnect(currentCCD, SIGNAL(newImage(QImage*, \
ISD::CCDChip*)), this, SLOT(sendNewImage(QImage*, ISD::CCDChip*))); +
+ if (useGuideHead == false && darkSubCheck->isChecked() && \
activeJob->isPreview()) + {
+ FITSView *currentImage = targetChip->getImage(FITS_NORMAL);
+ FITSData *darkData = NULL;
+ uint16_t offsetX = activeJob->getSubX() / activeJob->getXBin();
+ uint16_t offsetY = activeJob->getSubY() / activeJob->getYBin();
+
+ darkData = DarkLibrary::Instance()->getDarkFrame(targetChip, \
activeJob->getExposure()); +
+ connect(DarkLibrary::Instance(), SIGNAL(darkFrameCompleted(bool)), this, \
SLOT(setCaptureComplete())); + connect(DarkLibrary::Instance(), \
SIGNAL(newLog(QString)), this, SLOT(appendLogText(QString))); +
+ if (darkData)
+ DarkLibrary::Instance()->subtract(darkData, currentImage, \
activeJob->getCaptureFilter(), offsetX, offsetY); + else
+ DarkLibrary::Instance()->captureAndSubtract(targetChip, \
currentImage, activeJob->getExposure(), offsetX, offsetY); +
+ return;
+ }
+ }
+
+ setCaptureComplete();
+
+}
+
+bool Capture::setCaptureComplete()
+{
+ disconnect(currentCCD, SIGNAL(newExposureValue(ISD::CCDChip*,double,IPState)), \
this, SLOT(updateCaptureProgress(ISD::CCDChip*,double,IPState))); + \
DarkLibrary::Instance()->disconnect(this); + \
secondsLabel->setText(i18n("Complete.")); +
+ // If it was initially set as preview job
+ if (seqTotalCount <= 0)
+ {
+ jobs.removeOne(activeJob);
+ delete(activeJob);
+ // Reset active job pointer
+ activeJob = NULL;
+ abort();
+ return true;
+ }
+
+ if (state == CAPTURE_PAUSED)
+ {
+ pauseFunction = &Capture::setCaptureComplete;
+ appendLogText(i18n("Sequence paused."));
+ secondsLabel->setText(i18n("Paused..."));
+ return false;
+ }
+
+ if (activeJob->getFrameType() != FRAME_LIGHT)
+ {
+ if (processPostCaptureCalibrationStage() == false)
+ return true;
+
+ if (calibrationStage == CAL_CALIBRATION_COMPLETE)
+ calibrationStage = CAL_CAPTURING;
+ }
+
+ seqCurrentCount++;
+ activeJob->setCompleted(seqCurrentCount);
+ imgProgress->setValue(seqCurrentCount);
+
+ appendLogText(i18n("Received image %1 out of %2.", seqCurrentCount, \
seqTotalCount)); +
+ state = CAPTURE_IMAGE_RECEIVED;
+ emit newStatus(Ekos::CAPTURE_IMAGE_RECEIVED);
+
+ currentImgCountOUT->setText( QString::number(seqCurrentCount));
+
+ // if we're done
+ if (seqCurrentCount >= seqTotalCount)
+ {
+ processJobCompletion();
+ return true;
+ }
+
+ // Check if meridian condition is met
+ if (checkMeridianFlip())
+ return true;
+
+ // FIXME remove post capture script later
+ if (Options::postCaptureScript().isEmpty() == false)
+ {
+ postCaptureScript.start(Options::postCaptureScript());
+ appendLogText(i18n("Executing post capture script %1", \
Options::postCaptureScript())); + }
+ else
+ resumeSequence();
+
+ return true;
+}
+
+void Capture::processJobCompletion()
+{
+ activeJob->done();
+
+ stop();
+
+ // Check if meridian condition is met
+ if (checkMeridianFlip())
+ return;
+
+ // Check if there are more pending jobs and execute them
+ if (resumeSequence())
+ return;
+ // Otherwise, we're done. We park if required and resume guiding if no parking \
is done and autoguiding was engaged before. + else
+ {
+ KNotification::event( QLatin1String( "CaptureSuccessful"), i18n("CCD capture \
sequence completed")); +
+ abort();
+
+ state = CAPTURE_COMPLETE;
+ emit newStatus(Ekos::CAPTURE_COMPLETE);
+
+ if (parkCheck->isChecked() && currentTelescope && \
currentTelescope->canPark()) + {
+ appendLogText(i18n("Parking telescope..."));
+ //emit mountParking();
+ currentTelescope->Park();
+ return;
+ }
+
+ //Resume guiding if it was suspended before
+ //if (isAutoGuiding && currentCCD->getChip(ISD::CCDChip::GUIDE_CCD) == \
guideChip) + if (guideState == GUIDE_SUSPENDED && \
currentCCD->getChip(ISD::CCDChip::GUIDE_CCD) == guideChip) + emit \
suspendGuiding(false); + }
+
+}
+
+bool Capture::resumeSequence()
+{
+ if (state == CAPTURE_PAUSED)
+ {
+ pauseFunction = &Capture::resumeSequence;
+ appendLogText(i18n("Sequence paused."));
+ secondsLabel->setText(i18n("Paused..."));
+ return false;
+ }
+
+ // If seqTotalCount is zero, we have to find if there are more pending jobs in \
the queue + if (seqTotalCount == 0)
+ {
+ SequenceJob *next_job = NULL;
+
+ foreach(SequenceJob *job, jobs)
+ {
+ if (job->getStatus() == SequenceJob::JOB_IDLE || job->getStatus() == \
SequenceJob::JOB_ABORTED) + {
+ next_job = job;
+ break;
+ }
+ }
+
+ if (next_job)
+ {
+ prepareJob(next_job);
+
+ //Resume guiding if it was suspended before
+ //if (isAutoGuiding && currentCCD->getChip(ISD::CCDChip::GUIDE_CCD) == \
guideChip) + if (guideState == GUIDE_SUSPENDED && \
currentCCD->getChip(ISD::CCDChip::GUIDE_CCD) == guideChip) + emit \
suspendGuiding(false); +
+ return true;
+ }
+ else
+ return false;
+ }
+ // Otherwise, let's prepare for next exposure after making sure in-sequence \
focus and dithering are complete if applicable. + else
+ {
+ isAutoFocus = (autofocusCheck->isEnabled() && autofocusCheck->isChecked() && \
HFRPixels->value() > 0); + if (isAutoFocus)
+ autoFocusStatus = false;
+
+ // Reset HFR pixels to zero after merdian flip
+ if (isAutoFocus && meridianFlipStage != MF_NONE)
+ {
+ firstAutoFocus=true;
+ HFRPixels->setValue(fileHFR);
+ }
+
+ // If we suspended guiding due to primary chip download, resume guide chip \
guiding now + if (guideState == GUIDE_SUSPENDED && \
currentCCD->getChip(ISD::CCDChip::GUIDE_CCD) == guideChip) + emit \
suspendGuiding(false); +
+ //if (isAutoGuiding && guideDither && activeJob->getFrameType() == \
FRAME_LIGHT) + if (guideState == GUIDE_GUIDING && guideDither && \
activeJob->getFrameType() == FRAME_LIGHT) + {
+ secondsLabel->setText(i18n("Dithering..."));
+ //emit exposureComplete();
+
+ state = CAPTURE_DITHERING;
+ emit newStatus(Ekos::CAPTURE_DITHERING);
+ }
+ else if (isAutoFocus && activeJob->getFrameType() == FRAME_LIGHT)
+ {
+ secondsLabel->setText(i18n("Focusing..."));
+ emit checkFocus(HFRPixels->value());
+
+ state = CAPTURE_FOCUSING;
+ emit newStatus(Ekos::CAPTURE_FOCUSING);
+ }
+ else
+ startNextExposure();
+ }
+
+ return true;
+}
+
+void Capture::captureOne()
+{
+ if (currentCCD->getUploadMode() == ISD::CCD::UPLOAD_LOCAL)
+ {
+ appendLogText(i18n("Cannot take preview image while CCD upload mode is set \
to local. Please change upload mode to client and try again.")); + return;
+ }
+
+ addJob(true);
+
+ prepareJob(jobs.last());
+}
+
+void Capture::captureImage()
+{
+ seqTimer->stop();
+ //bool isDark=false;
+ SequenceJob::CAPTUREResult rc=SequenceJob::CAPTURE_OK;
+
+ if (focusState >= FOCUS_PROGRESS)
+ {
+ appendLogText(i18n("Cannot capture while focus module is busy."));
+ abort();
+ return;
+ }
+
+ //if (useGuideHead == false && darkSubCheck->isChecked() && calibrationState == \
CALIBRATE_NONE) + //isDark = true;
+
+ if (filterSlot != NULL)
+ {
+ currentFilterPosition = (int) filterSlot->np[0].value;
+ activeJob->setCurrentFilter(currentFilterPosition);
+ }
+
+ if (currentCCD->hasCooler())
+ {
+ double temperature=0;
+ currentCCD->getTemperature(&temperature);
+ activeJob->setCurrentTemperature(temperature);
+ }
+
+ connect(currentCCD, SIGNAL(BLOBUpdated(IBLOB*)), this, SLOT(newFITS(IBLOB*)), \
Qt::UniqueConnection); + connect(currentCCD, SIGNAL(newImage(QImage*, \
ISD::CCDChip*)), this, SLOT(sendNewImage(QImage*, ISD::CCDChip*)), \
Qt::UniqueConnection); +
+ if (activeJob->getFrameType() == FRAME_FLAT)
+ {
+ // If we have to calibrate ADU levels, first capture must be preview and not \
in batch mode + if (activeJob->isPreview() == false && \
activeJob->getFlatFieldDuration() == DURATION_ADU && calibrationStage == \
CAL_PRECAPTURE_COMPLETE) + {
+ calibrationStage = CAL_CALIBRATION;
+ activeJob->setPreview(true);
+ }
+ }
+
+ if (currentCCD->getUploadMode() != ISD::CCD::UPLOAD_LOCAL)
+ checkSeqBoundary(activeJob->getFITSDir());
+
+ state = CAPTURE_CAPTURING;
+
+ if (activeJob->isPreview() == false)
+ emit newStatus(Ekos::CAPTURE_CAPTURING);
+
+ if (frameSettings.contains(activeJob->getActiveChip()))
+ {
+ QVariantMap settings;
+ settings["x"] = activeJob->getSubX();
+ settings["y"] = activeJob->getSubY();
+ settings["w"] = activeJob->getSubW();
+ settings["h"] = activeJob->getSubH();
+ settings["binx"] = activeJob->getXBin();
+ settings["biny"] = activeJob->getYBin();
+
+ frameSettings[activeJob->getActiveChip()] = settings;
+ }
+
+ rc = activeJob->capture(darkSubCheck->isChecked() ? true : false);
+
+ switch (rc)
+ {
+ case SequenceJob::CAPTURE_OK:
+ connect(currentCCD, SIGNAL(newExposureValue(ISD::CCDChip*,double,IPState)), \
this, SLOT(updateCaptureProgress(ISD::CCDChip*,double,IPState)), \
Qt::UniqueConnection); + appendLogText(i18n("Capturing image..."));
+ break;
+
+ case SequenceJob::CAPTURE_FRAME_ERROR:
+ appendLogText(i18n("Failed to set sub frame."));
+ abort();
+ break;
+
+ case SequenceJob::CAPTURE_BIN_ERROR:
+ appendLogText(i18n("Failed to set binning."));
+ abort();
+ break;
+
+ case SequenceJob::CAPTURE_FILTER_BUSY:
+ // Try again in 1 second if filter is busy
+ QTimer::singleShot(1000, this, SLOT(captureImage()));
+ break;
+
+ case SequenceJob::CAPTURE_FOCUS_ERROR:
+ appendLogText(i18n("Cannot capture while focus module is busy."));
+ abort();
+ break;
+
+ }
+}
+
+bool Capture::resumeCapture()
+{
+ if (state == CAPTURE_PAUSED)
+ {
+ pauseFunction = &Capture::resumeCapture;
+ appendLogText(i18n("Sequence paused."));
+ secondsLabel->setText(i18n("Paused..."));
+ return false;
+ }
+
+ appendLogText(i18n("Dither complete."));
+
+ if (isAutoFocus && autoFocusStatus == false)
+ {
+ secondsLabel->setText(i18n("Focusing..."));
+ emit checkFocus(HFRPixels->value());
+ state = CAPTURE_FOCUSING;
+ emit newStatus(Ekos::CAPTURE_FOCUSING);
+ return true;
+ }
+
+ startNextExposure();
+
+ return true;
+}
+
+/*******************************************************************************/
+/* Update the prefix for the sequence of images to be captured */
+/*******************************************************************************/
+void Capture::updateSequencePrefix( const QString &newPrefix, const QString &dir)
+{
+ //static QString lastDir=QDir::homePath();
+
+ seqPrefix = newPrefix;
+
+ // If it doesn't exist, create it
+ QDir().mkpath(dir);
+
+ /*if (dir != lastDir)
+ {
+ seqWatcher->removeDir(lastDir);
+ lastDir = dir;
+ }
+
+ seqWatcher->addDir(dir, KDirWatch::WatchFiles);*/
+
+ nextSequenceID = 1;
+
+ //checkSeqBoundary(dir);
+}
+
+/*void Capture::checkSeqFile(const QString &path)
+{
+ checkSeqBoundary(QFileInfo(path).absolutePath());
+}*/
+
+/*******************************************************************************/
+/* Determine the next file number sequence. That is, if we have file1.png */
+/* and file2.png, then the next sequence should be file3.png */
+/*******************************************************************************/
+void Capture::checkSeqBoundary(const QString &path)
+{
+ int newFileIndex=-1;
+ QString tempName;
+ seqFileCount=0;
+
+ // No updates during meridian flip
+ if (meridianFlipStage >= MF_ALIGNING)
+ return;
+
+ QDirIterator it(path, QDir::Files);
+
+ while (it.hasNext())
+ {
+ tempName = it.next();
+ QFileInfo info(tempName);
+ tempName = info.baseName();
+
+ // find the prefix first
+ if (tempName.startsWith(seqPrefix) == false)
+ continue;
+
+ seqFileCount++;
+
+ tempName = tempName.remove(seqPrefix);
+
+ if (tempName.startsWith("_"))
+ tempName = tempName.remove(0, 1);
+
+ bool indexOK = false;
+ newFileIndex = tempName.mid(0, 3).toInt(&indexOK);
+
+ if (indexOK && newFileIndex >= nextSequenceID)
+ nextSequenceID = newFileIndex + 1;
+ }
+
+ currentCCD->setNextSequenceID(nextSequenceID);
+}
+
+void Capture::appendLogText(const QString &text)
+{
+ logText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2", \
QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss"), text)); +
+ if (Options::captureLogging())
+ qDebug() << "Capture: " << text;
+
+ emit newLog();
+}
+
+void Capture::clearLog()
+{
+ logText.clear();
+ emit newLog();
+}
+
+void Capture::updateCaptureProgress(ISD::CCDChip * tChip, double value, IPState \
state) +{
+
+ if (targetChip != tChip || targetChip->getCaptureMode() != FITS_NORMAL || \
meridianFlipStage >= MF_ALIGNING) + return;
+
+ exposeOUT->setText(QString::number(value, 'd', 2));
+
+ if (activeJob)
+ activeJob->setExposeLeft(value);
+
+ if (activeJob && state == IPS_ALERT)
+ {
+ int retries = activeJob->getCaptureRetires()+1;
+
+ activeJob->setCaptureRetires(retries);
+
+ appendLogText(i18n("Capture failed."));
+
+ if (retries == 3)
+ {
+ abort();
+ return;
+ }
+
+ appendLogText(i18n("Restarting capture attempt #%1", retries));
+
+ nextSequenceID = 1;
+
+ captureImage();
+ return;
+ }
+
+ if (value == 0)
+ {
+ activeJob->setCaptureRetires(0);
+
+ if (currentCCD && currentCCD->getUploadMode() == ISD::CCD::UPLOAD_LOCAL)
+ {
+ if (activeJob && activeJob->getStatus() == SequenceJob::JOB_BUSY && \
activeJob->getExposeLeft() == 0 && state == IPS_OK) + {
+ newFITS(0);
+ return;
+ }
+ }
+
+ //if (isAutoGuiding && Options::useEkosGuider() && \
currentCCD->getChip(ISD::CCDChip::GUIDE_CCD) == guideChip) + if (guideState == \
GUIDE_GUIDING && Options::useEkosGuider() && \
currentCCD->getChip(ISD::CCDChip::GUIDE_CCD) == guideChip) + {
+ if (Options::captureLogging())
+ qDebug() << "Capture: Autoguiding suspended until primary CCD chip \
completes downloading..."; + emit suspendGuiding(true);
+ }
+
+ secondsLabel->setText(i18n("Downloading..."));
+
+ //disconnect(currentCCD, \
SIGNAL(newExposureValue(ISD::CCDChip*,double,IPState)), this, \
SLOT(updateCaptureProgress(ISD::CCDChip*,double,IPState))); + }
+ // JM: Don't change to i18np, value is DOUBLE, not Integer.
+ else if (value <= 1)
+ secondsLabel->setText(i18n("second left"));
+ else
+ secondsLabel->setText(i18n("seconds left"));
+}
+
+void Capture::updateCCDTemperature(double value)
+{
+ if (temperatureCheck->isEnabled() == false)
+ checkCCD();
+
+ temperatureOUT->setText(QString::number(value, 'f', 2));
+
+ if (activeJob && (activeJob->getStatus() == SequenceJob::JOB_ABORTED || \
activeJob->getStatus() == SequenceJob::JOB_IDLE)) + \
activeJob->setCurrentTemperature(value); +}
+
+void Capture::addJob(bool preview)
+{
+ SequenceJob *job = NULL;
+ QString imagePrefix;
+
+ if (preview == false && darkSubCheck->isChecked())
+ {
+ KMessageBox::error(this, i18n("Auto dark subtract is not supported in batch \
mode.")); + return;
+ }
+
+ if (jobUnderEdit)
+ job = jobs.at(queueTable->currentRow());
+ else
+ job = new SequenceJob();
+
+ if (job == NULL)
+ {
+ qWarning() << "Job is NULL!" << endl;
+ return;
+ }
+
+ if (ISOCombo->isEnabled())
+ job->setISOIndex(ISOCombo->currentIndex());
+
+ job->setPreview(preview);
+
+ if (temperatureIN->isEnabled())
+ {
+ double currentTemperature;
+ currentCCD->getTemperature(¤tTemperature);
+ job->setEnforceTemperature(temperatureCheck->isChecked());
+ job->setTargetTemperature(temperatureIN->value());
+ job->setCurrentTemperature(currentTemperature);
+ }
+
+ job->setCaptureFilter((FITSScale) filterCombo->currentIndex());
+
+ job->setFlatFieldDuration(flatFieldDuration);
+ job->setFlatFieldSource(flatFieldSource);
+ job->setPreMountPark(preMountPark);
+ job->setPreDomePark(preDomePark);
+ job->setWallCoord(wallCoord);
+ job->setTargetADU(targetADU);
+
+ imagePrefix = prefixIN->text();
+
+ constructPrefix(imagePrefix);
+
+ job->setPrefixSettings(prefixIN->text(), filterCheck->isChecked(), \
expDurationCheck->isChecked(), ISOCheck->isChecked()); + \
job->setFrameType(frameTypeCombo->currentIndex(), frameTypeCombo->currentText()); + \
job->setFullPrefix(imagePrefix); +
+ if (filterSlot != NULL && currentFilter != NULL)
+ job->setTargetFilter(FilterPosCombo->currentIndex()+1, \
FilterPosCombo->currentText()); +
+ job->setExposure(exposureIN->value());
+
+ job->setCount(countIN->value());
+
+ job->setBin(binXIN->value(), binYIN->value());
+
+ job->setDelay(delayIN->value() * 1000); /* in ms */
+
+ job->setActiveChip(targetChip);
+ job->setActiveCCD(currentCCD);
+ job->setActiveFilter(currentFilter);
+
+ job->setFrame(frameXIN->value(), frameYIN->value(), frameWIN->value(), \
frameHIN->value()); +
+ job->setRootFITSDir(fitsDir->text());
+
+ if (jobUnderEdit == false)
+ {
+ jobs.append(job);
+
+ // Nothing more to do if preview
+ if (preview)
+ return;
+ }
+
+ QString finalFITSDir = fitsDir->text();
+
+ if (targetName.isEmpty())
+ finalFITSDir += QLatin1Literal("/") + frameTypeCombo->currentText();
+ else
+ finalFITSDir += QLatin1Literal("/") + targetName + QLatin1Literal("/") + \
frameTypeCombo->currentText(); + if ( (job->getFrameType() == FRAME_LIGHT || \
job->getFrameType() == FRAME_FLAT) && job->getFilterName().isEmpty() == false) + \
finalFITSDir += QLatin1Literal("/") + job->getFilterName(); +
+ job->setFITSDir(finalFITSDir);
+
+ int currentRow = 0;
+ if (jobUnderEdit == false)
+ {
+ currentRow = queueTable->rowCount();
+ queueTable->insertRow(currentRow);
+ }
+ else
+ currentRow = queueTable->currentRow();
+
+ QTableWidgetItem *status = jobUnderEdit ? queueTable->item(currentRow, 0) : new \
QTableWidgetItem(); + status->setText(job->getStatusString());
+ status->setTextAlignment(Qt::AlignHCenter);
+ status->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
+
+ job->setStatusCell(status);
+
+ QTableWidgetItem *filter = jobUnderEdit ? queueTable->item(currentRow, 1) : new \
QTableWidgetItem(); + filter->setText("--");
+ if (frameTypeCombo->currentText().compare("Bias", Qt::CaseInsensitive) &&
+ frameTypeCombo->currentText().compare("Dark", Qt::CaseInsensitive) &&
+ FilterPosCombo->count() > 0)
+ filter->setText(FilterPosCombo->currentText());
+
+ filter->setTextAlignment(Qt::AlignHCenter);
+ filter->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
+
+ QTableWidgetItem *type = jobUnderEdit ? queueTable->item(currentRow, 2) : new \
QTableWidgetItem(); + type->setText(frameTypeCombo->currentText());
+ type->setTextAlignment(Qt::AlignHCenter);
+ type->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
+
+ QTableWidgetItem *bin = jobUnderEdit ? queueTable->item(currentRow, 3) : new \
QTableWidgetItem(); + \
bin->setText(QString("%1x%2").arg(binXIN->value()).arg(binYIN->value())); + \
bin->setTextAlignment(Qt::AlignHCenter); + bin->setFlags(Qt::ItemIsSelectable | \
Qt::ItemIsEnabled); +
+ QTableWidgetItem *exp = jobUnderEdit ? queueTable->item(currentRow, 4) : new \
QTableWidgetItem(); + exp->setText(QString::number(exposureIN->value()));
+ exp->setTextAlignment(Qt::AlignHCenter);
+ exp->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
+
+ QTableWidgetItem *iso = jobUnderEdit ? queueTable->item(currentRow, 5) : new \
QTableWidgetItem(); + if (ISOCombo->currentIndex() != -1)
+ iso->setText(ISOCombo->currentText());
+ else
+ iso->setText("--");
+ iso->setTextAlignment(Qt::AlignHCenter);
+ iso->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
+
+
+ QTableWidgetItem *count = jobUnderEdit ? queueTable->item(currentRow, 6) : new \
QTableWidgetItem(); + count->setText(QString::number(countIN->value()));
+ count->setTextAlignment(Qt::AlignHCenter);
+ count->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
+
+ if (jobUnderEdit == false)
+ {
+ queueTable->setItem(currentRow, 0, status);
+ queueTable->setItem(currentRow, 1, filter);
+ queueTable->setItem(currentRow, 2, type);
+ queueTable->setItem(currentRow, 3, bin);
+ queueTable->setItem(currentRow, 4, exp);
+ queueTable->setItem(currentRow, 5, iso);
+ queueTable->setItem(currentRow, 6, count);
+ }
+
+ removeFromQueueB->setEnabled(true);
+
+ if (queueTable->rowCount() > 0)
+ {
+ queueSaveAsB->setEnabled(true);
+ queueSaveB->setEnabled(true);
+ resetB->setEnabled(true);
+ mDirty = true;
+ }
+
+ if (queueTable->rowCount() > 1)
+ {
+ queueUpB->setEnabled(true);
+ queueDownB->setEnabled(true);
+ }
+
+ if (jobUnderEdit)
+ {
+ jobUnderEdit = false;
+ resetJobEdit();
+ appendLogText(i18n("Job #%1 changes applied.", currentRow+1));
+ }
+
+}
+
+void Capture::removeJob()
+{
+ if (jobUnderEdit)
+ {
+ resetJobEdit();
+ return;
+ }
+
+ int currentRow = queueTable->currentRow();
+
+ if (currentRow < 0)
+ {
+ currentRow = queueTable->rowCount()-1;
+ if (currentRow < 0)
+ return;
+ }
+
+ queueTable->removeRow(currentRow);
+
+ SequenceJob *job = jobs.at(currentRow);
+ jobs.removeOne(job);
+ delete (job);
+
+ if (queueTable->rowCount() == 0)
+ removeFromQueueB->setEnabled(false);
+
+ if (queueTable->rowCount() == 1)
+ {
+ queueUpB->setEnabled(false);
+ queueDownB->setEnabled(false);
+ }
+
+ for (int i=0; i < jobs.count(); i++)
+ jobs.at(i)->setStatusCell(queueTable->item(i, 0));
+
+ queueTable->selectRow(queueTable->currentRow());
+
+ if (queueTable->rowCount() == 0)
+ {
+ queueSaveAsB->setEnabled(false);
+ queueSaveB->setEnabled(false);
+ resetB->setEnabled(false);
+ }
+
+ mDirty = true;
+
+
+}
+
+void Capture::moveJobUp()
+{
+ int currentRow = queueTable->currentRow();
+
+ int columnCount = queueTable->columnCount();
+
+ if (currentRow <= 0 || queueTable->rowCount() == 1)
+ return;
+
+ int destinationRow = currentRow - 1;
+
+ for (int i=0; i < columnCount; i++)
+ {
+ QTableWidgetItem *downItem = queueTable->takeItem(currentRow, i);
+ QTableWidgetItem *upItem = queueTable->takeItem(destinationRow, i);
+
+ queueTable->setItem(destinationRow, i, downItem);
+ queueTable->setItem(currentRow, i, upItem);
+ }
+
+ SequenceJob *job = jobs.takeAt(currentRow);
+
+ jobs.removeOne(job);
+ jobs.insert(destinationRow, job);
+
+ queueTable->selectRow(destinationRow);
+
+ for (int i=0; i < jobs.count(); i++)
+ jobs.at(i)->setStatusCell(queueTable->item(i, 0));
+
+
+ mDirty = true;
+
+}
+
+void Capture::moveJobDown()
+{
+ int currentRow = queueTable->currentRow();
+
+ int columnCount = queueTable->columnCount();
+
+ if (currentRow < 0 || queueTable->rowCount() == 1 || (currentRow+1) == \
queueTable->rowCount() ) + return;
+
+ int destinationRow = currentRow + 1;
+
+ for (int i=0; i < columnCount; i++)
+ {
+ QTableWidgetItem *downItem = queueTable->takeItem(currentRow, i);
+ QTableWidgetItem *upItem = queueTable->takeItem(destinationRow, i);
+
+ queueTable->setItem(destinationRow, i, downItem);
+ queueTable->setItem(currentRow, i, upItem);
+ }
+
+ SequenceJob *job = jobs.takeAt(currentRow);
+
+ jobs.removeOne(job);
+ jobs.insert(destinationRow, job);
+
+ queueTable->selectRow(destinationRow);
+
+ for (int i=0; i < jobs.count(); i++)
+ jobs.at(i)->setStatusCell(queueTable->item(i, 0));
+
+ mDirty = true;
+
+}
+
+void Capture::setBusy(bool enable)
+{
+ isBusy = enable;
+
+ enable ? pi->startAnimation() : pi->stopAnimation();
+ previewB->setEnabled(!enable);
+ //startB->setEnabled(!enable);
+ //stopB->setEnabled(enable);
+}
+
+void Capture::prepareJob(SequenceJob *job)
+{
+ activeJob = job;
+
+ // Just notification of active job stating up
+ emit newImage(NULL, activeJob);
+
+ connect(job, SIGNAL(checkFocus()), this, SLOT(startPostFilterAutoFocus()));
+
+ // Reset calibration stage
+ if (calibrationStage == CAL_CAPTURING)
+ {
+ if (job->getFrameType() != FRAME_LIGHT)
+ calibrationStage = CAL_PRECAPTURE_COMPLETE;
+ else
+ calibrationStage = CAL_NONE;
+ }
+
+ if (currentFilterPosition > 0)
+ {
+ // If we haven't performed a single autofocus yet, we stop
+ if (Options::autoFocusOnFilterChange() && (isAutoFocus == false && \
firstAutoFocus == true)) + {
+ appendLogText(i18n("Manual focusing post filter change is not supported. \
Run Autofocus process before trying again.")); + abort();
+ return;
+ }
+
+ activeJob->setCurrentFilter(currentFilterPosition);
+
+ if (currentFilterPosition != activeJob->getTargetFilter())
+ {
+ appendLogText(i18n("Changing filter to %1...", \
FilterPosCombo->itemText(activeJob->getTargetFilter()-1))); + \
secondsLabel->setText(i18n("Set filter...")); +
+ if (activeJob->isPreview() == false)
+ {
+ state = CAPTURE_CHANGING_FILTER;
+ emit newStatus(Ekos::CAPTURE_CHANGING_FILTER);
+ }
+
+ setBusy(true);
+
+ }
+ }
+
+ if (currentCCD->hasCooler() && activeJob->getEnforceTemperature())
+ {
+ if (activeJob->getCurrentTemperature() != INVALID_TEMPERATURE &&
+ fabs(activeJob->getCurrentTemperature() - \
activeJob->getTargetTemperature()) > Options::maxTemperatureDiff()) + {
+ appendLogText(i18n("Setting temperature to %1 C...", \
activeJob->getTargetTemperature())); + secondsLabel->setText(i18n("Set %1 \
C...", activeJob->getTargetTemperature())); +
+ if (activeJob->isPreview() == false)
+ {
+ state = CAPTURE_SETTING_TEMPERATURE;
+ emit newStatus(Ekos::CAPTURE_SETTING_TEMPERATURE);
+ }
+
+ setBusy(true);
+ }
+ }
+
+ connect(activeJob, SIGNAL(prepareComplete()), this, SLOT(executeJob()));
+
+ job->prepareCapture();
+}
+
+void Capture::executeJob()
+{
+ activeJob->disconnect(this);
+
+ if (activeJob->isPreview())
+ seqTotalCount = -1;
+ else
+ seqTotalCount = activeJob->getCount();
+
+ seqDelay = activeJob->getDelay();
+
+ seqCurrentCount = activeJob->getCompleted();
+
+ if (activeJob->isPreview() == false)
+ {
+ fullImgCountOUT->setText( QString::number(seqTotalCount));
+ currentImgCountOUT->setText(QString::number(seqCurrentCount));
+
+ // set the progress info
+ imgProgress->setEnabled(true);
+ imgProgress->setMaximum(seqTotalCount);
+ imgProgress->setValue(seqCurrentCount);
+
+ if (currentCCD->getUploadMode() != ISD::CCD::UPLOAD_LOCAL)
+ updateSequencePrefix(activeJob->getPrefix(), activeJob->getFITSDir());
+ }
+
+ // We check if the job is already fully or partially complete by checking how \
many files of its type exist on the file system unless ignoreJobProgress is set to \
true + if (ignoreJobProgress == false && Options::rememberJobProgress() && \
activeJob->isPreview() == false) + {
+ checkSeqBoundary(activeJob->getFITSDir());
+
+ if (seqFileCount > 0)
+ {
+ // Fully complete
+ if (seqFileCount >= seqTotalCount)
+ {
+ seqCurrentCount=seqTotalCount;
+ activeJob->setCompleted(seqCurrentCount);
+ imgProgress->setValue(seqCurrentCount);
+ processJobCompletion();
+ return;
+ }
+
+ // Partially complete
+ seqCurrentCount=seqFileCount;
+ activeJob->setCompleted(seqCurrentCount);
+ currentImgCountOUT->setText( QString::number(seqCurrentCount));
+ imgProgress->setValue(seqCurrentCount);
+
+ // Emit progress update
+ emit newImage(NULL, activeJob);
+ }
+ }
+
+ // Update button status
+ setBusy(true);
+
+ useGuideHead = (activeJob->getActiveChip()->getType() == \
ISD::CCDChip::PRIMARY_CCD) ? false : true; +
+ // Check flat field frame requirements
+ if (activeJob->getFrameType() != FRAME_LIGHT && activeJob->isPreview() == false)
+ {
+ // Make sure we don't have any pre-capture pending jobs for flat frames
+ IPState rc = processPreCaptureCalibrationStage();
+
+ if (rc == IPS_ALERT)
+ return;
+ else if (rc == IPS_BUSY)
+ {
+ secondsLabel->clear();
+ QTimer::singleShot(1000, this, SLOT(executeJob()));
+ return;
+ }
+ }
+
+ syncGUIToJob(activeJob);
+
+ captureImage();
+}
+
+void Capture::setGuideDeviation(double delta_ra, double delta_dec)
+{
+ if (guideDeviationCheck->isChecked() == false || activeJob == NULL)
+ return;
+
+ // If guiding is started after a meridian flip we will start getting guide \
deviations again + // if the guide deviations are within our limits, we resume the \
sequence + if (meridianFlipStage == MF_GUIDING)
+ {
+ double deviation_rms = sqrt(delta_ra*delta_ra + delta_dec*delta_dec);
+ if (deviation_rms < guideDeviation->value())
+ {
+ initialHA = getCurrentHA();
+ appendLogText(i18n("Post meridian flip calibration completed \
successfully.")); + resumeSequence();
+ // N.B. Set meridian flip stage AFTER resumeSequence() always
+ meridianFlipStage = MF_NONE;
+ return;
+ }
+ }
+
+ // We don't enforce limit on previews
+ if (activeJob->isPreview() || activeJob->getExposeLeft() == 0)
+ return;
+
+ double deviation_rms = sqrt(delta_ra*delta_ra + delta_dec*delta_dec);
+
+ QString deviationText = QString("%1").arg(deviation_rms, 0, 'g', 3);
+
+ if (activeJob->getStatus() == SequenceJob::JOB_BUSY)
+ {
+ if (deviation_rms > guideDeviation->value())
+ {
+ // Ignore spikes ONCE
+ if (spikeDetected == false)
+ {
+ spikeDetected = true;
+ return;
+ }
+
+ spikeDetected = false;
+ deviationDetected = true;
+ appendLogText(i18n("Guiding deviation %1 exceeded limit value of %2 \
arcsecs, aborting exposure.", deviationText, guideDeviation->value())); + \
abort(); + }
+ return;
+ }
+
+ if (activeJob->getStatus() == SequenceJob::JOB_ABORTED && deviationDetected)
+ {
+ if (deviation_rms <= guideDeviation->value())
+ {
+ if (seqDelay == 0)
+ appendLogText(i18n("Guiding deviation %1 is now lower than limit \
value of %2 arcsecs, resuming exposure.", deviationText, guideDeviation->value())); + \
else + appendLogText(i18n("Guiding deviation %1 is now lower than \
limit value of %2 arcsecs, resuming exposure in %3 seconds.", deviationText, \
guideDeviation->value(), seqDelay/1000.0)); +
+ activeJob = NULL;
+
+ QTimer::singleShot(seqDelay, this, SLOT(start()));
+ return;
+ }
+ }
+}
+
+void Capture::setGuideDither(bool enable)
+{
+ guideDither = enable;
+}
+
+void Capture::setFocusStatus(FocusState state)
+{
+ focusState = state;
+
+ if (focusState > FOCUS_ABORTED)
+ return;
+
+ if (focusState == FOCUS_COMPLETE)
+ {
+ autofocusCheck->setEnabled(true);
+ HFRPixels->setEnabled(true);
+ if (focusHFR > 0 && firstAutoFocus && HFRPixels->value() == 0 && fileHFR == \
0) + {
+ firstAutoFocus = false;
+ // Add 2.5% to the automatic initial HFR value to allow for minute \
changes in HFR without need to refocus + // in case in-sequence-focusing is \
used. + HFRPixels->setValue(focusHFR + (focusHFR * 0.025));
+ }
+ }
+
+ if (activeJob && (activeJob->getStatus() == SequenceJob::JOB_ABORTED || \
activeJob->getStatus() == SequenceJob::JOB_IDLE)) + {
+ if (focusState == FOCUS_COMPLETE)
+ {
+ HFRPixels->setValue(focusHFR+ (focusHFR * 0.025));
+ appendLogText(i18n("Focus complete."));
+ }
+ else
+ {
+ appendLogText(i18n("Autofocus failed. Aborting exposure..."));
+ secondsLabel->setText("");
+ abort();
+ }
+
+ activeJob->setFilterPostFocusReady(focusState == FOCUS_COMPLETE);
+ return;
+ }
+
+ if (isAutoFocus && activeJob && activeJob->getStatus() == SequenceJob::JOB_BUSY)
+ {
+ if (focusState == FOCUS_COMPLETE)
+ {
+ appendLogText(i18n("Focus complete."));
+ startNextExposure();
+ }
+ else
+ {
+ appendLogText(i18n("Autofocus failed. Aborting exposure..."));
+ secondsLabel->setText("");
+ abort();
+ }
+ }
+}
+
+void Capture::setTelescope(ISD::GDInterface *newTelescope)
+{
+ currentTelescope = static_cast<ISD::Telescope*> (newTelescope);
+
+ connect(currentTelescope, SIGNAL(numberUpdated(INumberVectorProperty*)), this, \
SLOT(processTelescopeNumber(INumberVectorProperty*)), Qt::UniqueConnection); +
+ meridianCheck->setEnabled(true);
+ meridianHours->setEnabled(true);
+
+ syncTelescopeInfo();
+}
+
+void Capture::syncTelescopeInfo()
+{
+ if (currentCCD && currentTelescope && currentTelescope->isConnected())
+ {
+ parkCheck->setEnabled(currentTelescope->canPark());
+
+ ITextVectorProperty *activeDevices = \
currentCCD->getBaseDevice()->getText("ACTIVE_DEVICES"); + if (activeDevices)
+ {
+ IText *activeTelescope = IUFindText(activeDevices, "ACTIVE_TELESCOPE");
+ if (activeTelescope)
+ {
+ IUSaveText(activeTelescope, currentTelescope->getDeviceName());
+
+ currentCCD->getDriverInfo()->getClientManager()->sendNewText(activeDevices);
+ }
+ }
+ }
+}
+
+void Capture::saveFITSDirectory()
+{
+ QString dir = QFileDialog::getExistingDirectory(KStars::Instance(), i18n("FITS \
Save Directory"), dirPath.toLocalFile()); +
+ if (dir.isEmpty())
+ return;
+
+ fitsDir->setText(dir);
+
+}
+
+void Capture::loadSequenceQueue()
+{
+ QUrl fileURL = QFileDialog::getOpenFileUrl(KStars::Instance(), i18n("Open Ekos \
Sequence Queue"), dirPath, "Ekos Sequence Queue (*.esq)"); + if \
(fileURL.isEmpty()) + return;
+
+ if (fileURL.isValid() == false)
+ {
+ QString message = i18n( "Invalid URL: %1", fileURL.toLocalFile() );
+ KMessageBox::sorry( 0, message, i18n( "Invalid URL" ) );
+ return;
+ }
+
+ dirPath = QUrl(fileURL.url(QUrl::RemoveFilename));
+
+ loadSequenceQueue(fileURL.toLocalFile());
+
+}
+
+bool Capture::loadSequenceQueue(const QString &fileURL)
+{
+ QFile sFile;
+ sFile.setFileName(fileURL);
+
+ if ( !sFile.open( QIODevice::ReadOnly))
+ {
+ QString message = i18n( "Unable to open file %1", fileURL);
+ KMessageBox::sorry( 0, message, i18n( "Could Not Open File" ) );
+ return false;
+ }
+
+ //QTextStream instream(&sFile);
+
+ qDeleteAll(jobs);
+ jobs.clear();
+ while (queueTable->rowCount() > 0)
+ queueTable->removeRow(0);
+
+ LilXML *xmlParser = newLilXML();
+ char errmsg[MAXRBUF];
+ XMLEle *root = NULL;
+ XMLEle *ep;
+ char c;
+
+ while ( sFile.getChar(&c))
+ {
+ root = readXMLEle(xmlParser, c, errmsg);
+
+ if (root)
+ {
+ for (ep = nextXMLEle(root, 1) ; ep != NULL ; ep = nextXMLEle(root, 0))
+ {
+ if (!strcmp(tagXMLEle(ep), "GuideDeviation"))
+ {
+ if (!strcmp(findXMLAttValu(ep, "enabled"), "true"))
+ {
+ guideDeviationCheck->setChecked(true);
+ guideDeviation->setValue(atof(pcdataXMLEle(ep)));
+ }
+ else
+ guideDeviationCheck->setChecked(false);
+
+ }
+ else if (!strcmp(tagXMLEle(ep), "Autofocus"))
+ {
+ if (!strcmp(findXMLAttValu(ep, "enabled"), "true"))
+ {
+ autofocusCheck->setChecked(true);
+ float HFRValue = atof(pcdataXMLEle(ep));
+ if (HFRValue > 0)
+ {
+ fileHFR = HFRValue;
+ HFRPixels->setValue(HFRValue);
+ }
+ else
+ fileHFR=0;
+ }
+ else
+ autofocusCheck->setChecked(false);
+
+ }
+ else if (!strcmp(tagXMLEle(ep), "MeridianFlip"))
+ {
+ if (!strcmp(findXMLAttValu(ep, "enabled"), "true"))
+ {
+ meridianCheck->setChecked(true);
+ meridianHours->setValue(atof(pcdataXMLEle(ep)));
+ }
+ else
+ meridianCheck->setChecked(false);
+
+ }
+ else if (!strcmp(tagXMLEle(ep), "Park"))
+ {
+ if (!strcmp(findXMLAttValu(ep, "enabled"), "true"))
+ parkCheck->setChecked(true);
+ else
+ parkCheck->setChecked(false);
+
+ }
+ else
+ {
+ processJobInfo(ep);
+ }
+
+ }
+ delXMLEle(root);
+ }
+ else if (errmsg[0])
+ {
+ appendLogText(QString(errmsg));
+ delLilXML(xmlParser);
+ return false;
+ }
+ }
+
+ sequenceURL = QUrl::fromLocalFile(fileURL);
+ mDirty = false;
+ delLilXML(xmlParser);
+
+ if (Options::rememberJobProgress())
+ ignoreJobProgress = false;
+
+ return true;
+
+}
+
+bool Capture::processJobInfo(XMLEle *root)
+{
+
+ XMLEle *ep;
+ XMLEle *subEP;
+
+ for (ep = nextXMLEle(root, 1) ; ep != NULL ; ep = nextXMLEle(root, 0))
+ {
+ if (!strcmp(tagXMLEle(ep), "Exposure"))
+ exposureIN->setValue(atof(pcdataXMLEle(ep)));
+ else if (!strcmp(tagXMLEle(ep), "Binning"))
+ {
+ subEP = findXMLEle(ep, "X");
+ if (subEP)
+ binXIN->setValue(atoi(pcdataXMLEle(subEP)));
+ subEP = findXMLEle(ep, "Y");
+ if (subEP)
+ binYIN->setValue(atoi(pcdataXMLEle(subEP)));
+ }
+ else if (!strcmp(tagXMLEle(ep), "Frame"))
+ {
+ subEP = findXMLEle(ep, "X");
+ if (subEP)
+ frameXIN->setValue(atoi(pcdataXMLEle(subEP)));
+ subEP = findXMLEle(ep, "Y");
+ if (subEP)
+ frameYIN->setValue(atoi(pcdataXMLEle(subEP)));
+ subEP = findXMLEle(ep, "W");
+ if (subEP)
+ frameWIN->setValue(atoi(pcdataXMLEle(subEP)));
+ subEP = findXMLEle(ep, "H");
+ if (subEP)
+ frameHIN->setValue(atoi(pcdataXMLEle(subEP)));
+ }
+ else if (!strcmp(tagXMLEle(ep), "Temperature"))
+ {
+ if (temperatureIN->isEnabled())
+ temperatureIN->setValue(atof(pcdataXMLEle(ep)));
+
+ // If force attribute exist, we change temperatureCheck, otherwise do \
nothing. + if (!strcmp(findXMLAttValu(ep, "force"), "true"))
+ temperatureCheck->setChecked(true);
+ else if (!strcmp(findXMLAttValu(ep, "force"), "false"))
+ temperatureCheck->setChecked(false);
+
+ }
+ else if (!strcmp(tagXMLEle(ep), "Filter"))
+ {
+ FilterPosCombo->setCurrentIndex(atoi(pcdataXMLEle(ep))-1);
+ }
+ else if (!strcmp(tagXMLEle(ep), "Type"))
+ {
+ frameTypeCombo->setCurrentText(pcdataXMLEle(ep));
+ }
+ else if (!strcmp(tagXMLEle(ep), "Prefix"))
+ {
+ subEP = findXMLEle(ep, "RawPrefix");
+ if (subEP)
+ prefixIN->setText(pcdataXMLEle(subEP));
+ subEP = findXMLEle(ep, "FilterEnabled");
+ if (subEP)
+ filterCheck->setChecked( !strcmp("1", pcdataXMLEle(subEP)));
+ subEP = findXMLEle(ep, "ExpEnabled");
+ if (subEP)
+ expDurationCheck->setChecked( !strcmp("1", pcdataXMLEle(subEP)));
+ subEP = findXMLEle(ep, "TimeStampEnabled");
+ if (subEP)
+ ISOCheck->setChecked( !strcmp("1", pcdataXMLEle(subEP)));
+ }
+ else if (!strcmp(tagXMLEle(ep), "Count"))
+ {
+ countIN->setValue(atoi(pcdataXMLEle(ep)));
+ }
+ else if (!strcmp(tagXMLEle(ep), "Delay"))
+ {
+ delayIN->setValue(atoi(pcdataXMLEle(ep)));
+ }
+ else if (!strcmp(tagXMLEle(ep), "FITSDirectory"))
+ {
+ fitsDir->setText(pcdataXMLEle(ep));
+ }
+ else if (!strcmp(tagXMLEle(ep), "ISOIndex"))
+ {
+ if (ISOCombo->isEnabled())
+ ISOCombo->setCurrentIndex(atoi(pcdataXMLEle(ep)));
+ }
+ else if (!strcmp(tagXMLEle(ep), "Calibration"))
+ {
+ subEP = findXMLEle(ep, "FlatSource");
+ if (subEP)
+ {
+ XMLEle *typeEP = findXMLEle(subEP, "Type");
+ if (typeEP)
+ {
+ if (!strcmp(pcdataXMLEle(typeEP), "Manual"))
+ flatFieldSource = SOURCE_MANUAL;
+ else if (!strcmp(pcdataXMLEle(typeEP), "FlatCap"))
+ flatFieldSource = SOURCE_FLATCAP;
+ else if (!strcmp(pcdataXMLEle(typeEP), "DarkCap"))
+ flatFieldSource = SOURCE_DARKCAP;
+ else if (!strcmp(pcdataXMLEle(typeEP), "Wall"))
+ {
+ XMLEle *azEP=NULL, *altEP=NULL;
+ azEP = findXMLEle(subEP, "Az");
+ altEP = findXMLEle(subEP, "Alt");
+ if (azEP && altEP)
+ {
+ flatFieldSource =SOURCE_WALL;
+ wallCoord.setAz(atof(pcdataXMLEle(azEP)));
+ wallCoord.setAlt(atof(pcdataXMLEle(altEP)));
+ }
+ }
+ else
+ flatFieldSource = SOURCE_DAWN_DUSK;
+
+ }
+ }
+
+ subEP = findXMLEle(ep, "FlatDuration");
+ if (subEP)
+ {
+ XMLEle *typeEP = findXMLEle(subEP, "Type");
+ if (typeEP)
+ {
+ if (!strcmp(pcdataXMLEle(typeEP), "Manual"))
+ flatFieldDuration = DURATION_MANUAL;
+ }
+
+ XMLEle *aduEP= findXMLEle(subEP, "Value");
+ if (aduEP)
+ {
+ flatFieldDuration = DURATION_ADU;
+ targetADU = atof(pcdataXMLEle(aduEP));
+ }
+ }
+
+ subEP = findXMLEle(ep, "PreMountPark");
+ if (subEP)
+ {
+ if (!strcmp(pcdataXMLEle(subEP), "True"))
+ preMountPark = true;
+ else
+ preMountPark = false;
+ }
+
+ subEP = findXMLEle(ep, "PreDomePark");
+ if (subEP)
+ {
+ if (!strcmp(pcdataXMLEle(subEP), "True"))
+ preDomePark = true;
+ else
+ preDomePark = false;
+ }
+ }
+ }
+
+ addJob(false);
+
+ return true;
+}
+
+void Capture::saveSequenceQueue()
+{
+ QUrl backupCurrent = sequenceURL;
+
+ if (sequenceURL.toLocalFile().startsWith("/tmp/") || \
sequenceURL.toLocalFile().contains("/Temp")) + sequenceURL.clear();
+
+ // If no changes made, return.
+ if( mDirty == false && !sequenceURL.isEmpty())
+ return;
+
+ if (sequenceURL.isEmpty())
+ {
+ sequenceURL = QFileDialog::getSaveFileUrl(KStars::Instance(), i18n("Save \
Ekos Sequence Queue"), dirPath, "Ekos Sequence Queue (*.esq)"); + // if user \
presses cancel + if (sequenceURL.isEmpty())
+ {
+ sequenceURL = backupCurrent;
+ return;
+ }
+
+ dirPath = QUrl(sequenceURL.url(QUrl::RemoveFilename));
+
+ if (sequenceURL.toLocalFile().endsWith(".esq") == false)
+ sequenceURL.setPath(sequenceURL.toLocalFile() + ".esq");
+
+ if (QFile::exists(sequenceURL.toLocalFile()))
+ {
+ int r = KMessageBox::warningContinueCancel(0,
+ i18n( "A file named \"%1\" already exists. "
+ "Overwrite it?", sequenceURL.fileName() ),
+ i18n( "Overwrite File?" ),
+ KGuiItem(i18n( "&Overwrite" )) );
+ if(r==KMessageBox::Cancel) return;
+ }
+ }
+
+ if ( sequenceURL.isValid() )
+ {
+ if ( (saveSequenceQueue(sequenceURL.toLocalFile())) == false)
+ {
+ KMessageBox::error(KStars::Instance(), i18n("Failed to save sequence \
queue"), i18n("Save")); + return;
+ }
+
+ mDirty = false;
+
+ } else
+ {
+ QString message = i18n( "Invalid URL: %1", sequenceURL.url() );
+ KMessageBox::sorry(KStars::Instance(), message, i18n( "Invalid URL" ) );
+ }
+
+}
+
+void Capture::saveSequenceQueueAs()
+{
+ sequenceURL.clear();
+ saveSequenceQueue();
+}
+
+bool Capture::saveSequenceQueue(const QString &path)
+{
+ QFile file;
+ QString rawPrefix;
+ bool filterEnabled, expEnabled, tsEnabled;
+
+ file.setFileName(path);
+
+ if ( !file.open( QIODevice::WriteOnly))
+ {
+ QString message = i18n( "Unable to write to file %1", path);
+ KMessageBox::sorry( 0, message, i18n( "Could Not Open File" ) );
+ return false;
+ }
+
+ QTextStream outstream(&file);
+
+ outstream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << endl;
+ outstream << "<SequenceQueue version='1.3'>" << endl;
+ outstream << "<GuideDeviation enabled='" << (guideDeviationCheck->isChecked() ? \
"true" : "false") << "'>" << guideDeviation->value() << "</GuideDeviation>" << endl; \
+ outstream << "<Autofocus enabled='" << (autofocusCheck->isChecked() ? "true" : \
"false") << "'>" << HFRPixels->value() << "</Autofocus>" << endl; + outstream << \
"<MeridianFlip enabled='" << (meridianCheck->isChecked() ? "true" : "false") << "'>" \
<< meridianHours->value() << "</MeridianFlip>" << endl; + outstream << "<Park \
enabled='" << (parkCheck->isChecked() ? "true" : "false") << "'></Park>" << endl; + \
foreach(SequenceJob *job, jobs) + {
+ job->getPrefixSettings(rawPrefix, filterEnabled, expEnabled, tsEnabled);
+
+ outstream << "<Job>" << endl;
+
+ outstream << "<Exposure>" << job->getExposure() << "</Exposure>" << endl;
+ outstream << "<Binning>" << endl;
+ outstream << "<X>"<< job->getXBin() << "</X>" << endl;
+ outstream << "<Y>"<< job->getXBin() << "</Y>" << endl;
+ outstream << "</Binning>" << endl;
+ outstream << "<Frame>" << endl;
+ outstream << "<X>" << job->getSubX() << "</X>" << endl;
+ outstream << "<Y>" << job->getSubY() << "</Y>" << endl;
+ outstream << "<W>" << job->getSubW() << "</W>" << endl;
+ outstream << "<H>" << job->getSubH() << "</H>" << endl;
+ outstream << "</Frame>" << endl;
+ if (job->getTargetTemperature() != INVALID_TEMPERATURE)
+ outstream << "<Temperature force='" << (job->getEnforceTemperature() ? \
"true":"false") << "'>" << job->getTargetTemperature() << "</Temperature>" << endl; + \
if (job->getTargetFilter() >= 0) + outstream << "<Filter>" << \
job->getTargetFilter() << "</Filter>" << endl; + outstream << "<Type>" << \
frameTypeCombo->itemText(job->getFrameType()) << "</Type>" << endl; + \
outstream << "<Prefix>" << endl; + //outstream << "<CompletePrefix>" << \
job->getPrefix() << "</CompletePrefix>" << endl; + outstream << \
"<RawPrefix>" << rawPrefix << "</RawPrefix>" << endl; + outstream << \
"<FilterEnabled>" << (filterEnabled ? 1 : 0) << "</FilterEnabled>" << endl; + \
outstream << "<ExpEnabled>" << (expEnabled ? 1 : 0) << "</ExpEnabled>" << endl; + \
outstream << "<TimeStampEnabled>" << (tsEnabled ? 1 : 0) << "</TimeStampEnabled>" << \
endl; + outstream << "</Prefix>" << endl;
+ outstream << "<Count>" << job->getCount() << "</Count>" << endl;
+ // ms to seconds
+ outstream << "<Delay>" << job->getDelay()/1000 << "</Delay>" << endl;
+ QString rootDir = job->getRootFITSDir();
+ outstream << "<FITSDirectory>" << rootDir << "</FITSDirectory>" << endl;
+ if (job->getISOIndex() != -1)
+ outstream << "<ISOIndex>" << (job->getISOIndex()) << "</ISOIndex>" << \
endl; +
+ outstream << "<Calibration>" << endl;
+ outstream << "<FlatSource>" << endl;
+ if (job->getFlatFieldSource() == SOURCE_MANUAL)
+ outstream << "<Type>Manual</Type>" << endl;
+ else if (job->getFlatFieldSource() == SOURCE_FLATCAP)
+ outstream << "<Type>FlatCap</Type>" << endl;
+ else if (job->getFlatFieldSource() == SOURCE_DARKCAP)
+ outstream << "<Type>DarkCap</Type>" << endl;
+ else if (job->getFlatFieldSource() == SOURCE_WALL)
+ {
+ outstream << "<Type>Wall</Type>" << endl;
+ outstream << "<Az>" << job->getWallCoord().az().Degrees() << "</Az>" << \
endl; + outstream << "<Alt>" << job->getWallCoord().alt().Degrees() << \
"</Alt>" << endl; + }
+ else
+ outstream << "<Type>DawnDust</Type>" << endl;
+ outstream << "</FlatSource>" << endl;
+
+ outstream << "<FlatDuration>" << endl;
+ if (job->getFlatFieldDuration() == DURATION_MANUAL)
+ outstream << "<Type>Manual</Type>" << endl;
+ else
+ {
+ outstream << "<Type>ADU</Type>" << endl;
+ outstream << "<Value>" << job->getTargetADU() << "</Value>" << endl;
+ }
+ outstream << "</FlatDuration>" << endl;
+
+ outstream << "<PreMountPark>" << (job->isPreMountPark() ? "True" : "False") \
<< "</PreMountPark>" << endl; + outstream << "<PreDomePark>" << \
(job->isPreDomePark() ? "True" : "False") << "</PreDomePark>" << endl; + \
outstream << "</Calibration>" << endl; +
+ outstream << "</Job>" << endl;
+ }
+
+ outstream << "</SequenceQueue>" << endl;
+
+ appendLogText(i18n("Sequence queue saved to %1", path));
+ file.close();
+ return true;
+}
+
+void Capture::resetJobs()
+{
+ if (KMessageBox::warningContinueCancel(NULL, i18n("Are you sure you want to \
reset status of all jobs?"), + i18n("Reset \
job status"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), + \
"reset_job_status_warning") !=KMessageBox::Continue) + return;
+
+ foreach(SequenceJob *job, jobs)
+ job->resetStatus();
+
+ stop();
+
+ // Reste active job pointer
+ activeJob = NULL;
+
+ ignoreJobProgress=true;
+}
+
+void Capture::ignoreSequenceHistory()
+{
+ ignoreJobProgress=true;
+}
+
+void Capture::syncGUIToJob(SequenceJob *job)
+{
+ QString rawPrefix;
+ bool filterEnabled, expEnabled, tsEnabled;
+
+ job->getPrefixSettings(rawPrefix, filterEnabled, expEnabled, tsEnabled);
+
+ exposureIN->setValue(job->getExposure());
+ binXIN->setValue(job->getXBin());
+ binYIN->setValue(job->getYBin());
+ frameXIN->setValue(job->getSubX());
+ frameYIN->setValue(job->getSubY());
+ frameWIN->setValue(job->getSubW());
+ frameHIN->setValue(job->getSubH());
+ FilterPosCombo->setCurrentIndex(job->getTargetFilter()-1);
+ frameTypeCombo->setCurrentIndex(job->getFrameType());
+ prefixIN->setText(rawPrefix);
+ filterCheck->setChecked(filterEnabled);
+ expDurationCheck->setChecked(expEnabled);
+ ISOCheck->setChecked(tsEnabled);
+ countIN->setValue(job->getCount());
+ delayIN->setValue(job->getDelay()/1000);
+
+ // Temperature Options
+ temperatureCheck->setChecked(job->getEnforceTemperature());
+
+ // Flat field options
+ calibrationB->setEnabled(job->getFrameType() != FRAME_LIGHT);
+ flatFieldDuration = job->getFlatFieldDuration();
+ flatFieldSource = job->getFlatFieldSource();
+ targetADU = job->getTargetADU();
+ wallCoord = job->getWallCoord();
+ preMountPark = job->isPreMountPark();
+ preDomePark = job->isPreDomePark();
+
+ fitsDir->setText(job->getRootFITSDir());
+
+ if (ISOCombo->isEnabled())
+ ISOCombo->setCurrentIndex(job->getISOIndex());
+}
+
+void Capture::editJob(QModelIndex i)
+{
+ SequenceJob *job = jobs.at(i.row());
+ if (job == NULL)
+ return;
+
+
+ syncGUIToJob(job);
+
+ appendLogText(i18n("Editing job #%1...", i.row()+1));
+
+ addToQueueB->setIcon(QIcon::fromTheme("dialog-ok-apply"));
+ addToQueueB->setToolTip(i18n("Apply job changes."));
+ removeFromQueueB->setToolTip(i18n("Cancel job changes."));
+
+ jobUnderEdit = true;
+
+}
+
+void Capture::resetJobEdit()
+{
+ if (jobUnderEdit)
+ appendLogText(i18n("Editing job canceled."));
+
+ jobUnderEdit = false;
+ addToQueueB->setIcon(QIcon::fromTheme("list-add"));
+
+ addToQueueB->setToolTip(i18n("Add job to sequence queue"));
+ removeFromQueueB->setToolTip(i18n("Remove job from sequence queue"));
+}
+
+void Capture::constructPrefix(QString &imagePrefix)
+{
+
+ if (imagePrefix.isEmpty() == false)
+ imagePrefix += '_';
+
+ imagePrefix += frameTypeCombo->currentText();
+
+ if (filterCheck->isChecked() && FilterPosCombo->currentText().isEmpty() == false \
&& + frameTypeCombo->currentText().compare("Bias", Qt::CaseInsensitive) &&
+ frameTypeCombo->currentText().compare("Dark", Qt::CaseInsensitive))
+ {
+ //if (imagePrefix.isEmpty() == false || frameTypeCheck->isChecked())
+ imagePrefix += '_';
+
+ imagePrefix += FilterPosCombo->currentText();
+ }
+ if (expDurationCheck->isChecked())
+ {
+ //if (imagePrefix.isEmpty() == false || frameTypeCheck->isChecked())
+ imagePrefix += '_';
+
+ imagePrefix += QString::number(exposureIN->value(), 'd', 0) + \
QString("_secs"); + }
+}
+
+double Capture::getProgressPercentage()
+{
+ int totalImageCount=0;
+ int totalImageCompleted=0;
+
+ foreach(SequenceJob *job, jobs)
+ {
+ totalImageCount += job->getCount();
+ totalImageCompleted += job->getCompleted();
+ }
+
+ if (totalImageCount != 0)
+ return ( ( (double) totalImageCompleted / totalImageCount) * 100.0);
+ else
+ return -1;
+}
+
+int Capture::getActiveJobID()
+{
+ if (activeJob == NULL)
+ return -1;
+
+ for (int i=0; i < jobs.count(); i++)
+ {
+ if (activeJob == jobs[i])
+ return i;
+ }
+
+ return -1;
+}
+
+QString Capture::getJobState(int id)
+{
+ if (id < jobs.count())
+ {
+ SequenceJob *job = jobs.at(id);
+ return job->getStatusString();
+ }
+
+ return QString();
+}
+
+int Capture::getJobImageProgress(int id)
+{
+ if (id < jobs.count())
+ {
+ SequenceJob *job = jobs.at(id);
+ return job->getCompleted();
+ }
+
+ return -1;
+}
+
+int Capture::getJobImageCount(int id)
+{
+ if (id < jobs.count())
+ {
+ SequenceJob *job = jobs.at(id);
+ return job->getCount();
+ }
+
+ return -1;
+}
+
+double Capture::getJobExposureProgress(int id)
+{
+ if (id < jobs.count())
+ {
+ SequenceJob *job = jobs.at(id);
+ return job->getExposeLeft();
+ }
+
+ return -1;
+}
+
+double Capture::getJobExposureDuration(int id)
+{
+ if (id < jobs.count())
+ {
+ SequenceJob *job = jobs.at(id);
+ return job->getExposure();
+ }
+
+ return -1;
+}
+
+int Capture::getJobRemainingTime(SequenceJob *job)
+{
+ int remaining=0;
+
+ if (job->getStatus() == SequenceJob::JOB_BUSY)
+ remaining += (job->getExposure() + job->getDelay()/1000) * (job->getCount() \
- job->getCompleted()) + job->getExposeLeft(); + else
+ remaining += (job->getExposure() + job->getDelay()/1000) * (job->getCount() \
- job->getCompleted()); +
+ return remaining;
+}
+
+int Capture::getOverallRemainingTime()
+{
+ double remaining=0;
+
+ foreach(SequenceJob *job, jobs)
+ remaining += getJobRemainingTime(job);
+
+ return remaining;
+}
+
+int Capture::getActiveJobRemainingTime()
+{
+ if (activeJob == NULL)
+ return -1;
+
+ return getJobRemainingTime(activeJob);
+}
+
+void Capture::setMaximumGuidingDeviaiton(bool enable, double value)
+{
+ if (guideDeviationCheck->isEnabled())
+ {
+ guideDeviationCheck->setChecked(enable);
+ if (enable)
+ guideDeviation->setValue(value);
+ }
+
+}
+
+void Capture::setInSequenceFocus(bool enable, double HFR)
+{
+ if (autofocusCheck->isEnabled())
+ {
+ autofocusCheck->setChecked(enable);
+ if (enable)
+ HFRPixels->setValue(HFR);
+ }
+}
+
+void Capture::setParkOnComplete(bool enable)
+{
+ if (parkCheck->isEnabled())
+ parkCheck->setChecked(enable);
+}
+
+void Capture::setTemperature()
+{
+ if (currentCCD)
+ currentCCD->setTemperature(temperatureIN->value());
+}
+
+void Capture::clearSequenceQueue()
+{
+ activeJob=NULL;
+ targetName = QString();
+ stop();
+ while (queueTable->rowCount() > 0)
+ queueTable->removeRow(0);
+ jobs.clear();
+ qDeleteAll(jobs);
+ ignoreJobProgress = true;
+
+}
+
+QString Capture::getSequenceQueueStatus()
+{
+ if (jobs.count() == 0)
+ return "Invalid";
+
+ if (isBusy)
+ return "Running";
+
+ int idle=0, error=0, complete=0, aborted=0,running=0;
+
+ foreach(SequenceJob* job, jobs)
+ {
+ switch (job->getStatus())
+ {
+ case SequenceJob::JOB_ABORTED:
+ aborted++;
+ break;
+ case SequenceJob::JOB_BUSY:
+ running++;
+ break;
+ case SequenceJob::JOB_DONE:
+ complete++;
+ break;
+ case SequenceJob::JOB_ERROR:
+ error++;
+ break;
+ case SequenceJob::JOB_IDLE:
+ idle++;
+ break;
+ }
+ }
+
+ if (error > 0)
+ return "Error";
+
+ if (aborted > 0)
+ {
+ if (guideState == GUIDE_GUIDING && deviationDetected)
+ return "Suspended";
+ else
+ return "Aborted";
+ }
+
+ if (running > 0)
+ return "Running";
+
+ if (idle == jobs.count())
+ return "Idle";
+
+ if (complete == jobs.count())
+ return "Complete";
+
+ return "Invalid";
+}
+
+void Capture::processTelescopeNumber(INumberVectorProperty *nvp)
+{
+ // If it is not ours, return.
+ if (strcmp(nvp->device, currentTelescope->getDeviceName()) || strcmp(nvp->name, \
"EQUATORIAL_EOD_COORD")) + return;
+
+ switch (meridianFlipStage)
+ {
+ case MF_NONE:
+ break;
+ case MF_INITIATED:
+ {
+ if (nvp->s == IPS_BUSY)
+ meridianFlipStage = MF_FLIPPING;
+ }
+ break;
+
+ case MF_FLIPPING:
+ {
+ double ra, dec;
+ currentTelescope->getEqCoords(&ra, &dec);
+ double diffRA = initialRA - ra;
+ if (fabs(diffRA) > MF_RA_DIFF_LIMIT || nvp->s == IPS_OK)
+ meridianFlipStage = MF_SLEWING;
+ }
+ break;
+
+ case MF_SLEWING:
+
+ if (nvp->s != IPS_OK)
+ break;
+
+ // If dome is syncing, wait until it stops
+ if (dome && dome->isMoving())
+ break;
+
+ // We are at a new initialHA
+ initialHA= getCurrentHA();
+
+ appendLogText(i18n("Telescope completed the meridian flip."));
+
+ if (resumeAlignmentAfterFlip == true)
+ {
+ appendLogText(i18n("Performing post flip re-alignment..."));
+ secondsLabel->setText(i18n("Aligning..."));
+
+ state = CAPTURE_ALIGNING;
+ emit newStatus(Ekos::CAPTURE_ALIGNING);
+
+ meridianFlipStage = MF_ALIGNING;
+ //QTimer::singleShot(Options::settlingTime(), [this]() {emit \
meridialFlipTracked();}); + //emit meridialFlipTracked();
+ return;
+ }
+
+ checkGuidingAfterFlip();
+ break;
+
+ default:
+ break;
+ }
+
+}
+
+void Capture::checkGuidingAfterFlip()
+{
+ // If we're not autoguiding then we're done
+ if (resumeGuidingAfterFlip == false)
+ {
+ resumeSequence();
+ // N.B. Set meridian flip stage AFTER resumeSequence() always
+ meridianFlipStage = MF_NONE;
+ }
+ else
+ {
+ appendLogText(i18n("Performing post flip re-calibration and guiding..."));
+ secondsLabel->setText(i18n("Calibrating..."));
+
+ state = CAPTURE_CALIBRATING;
+ emit newStatus(Ekos::CAPTURE_CALIBRATING);
+
+ meridianFlipStage = MF_GUIDING;
+ emit meridianFlipCompleted();
+ }
+}
+
+double Capture::getCurrentHA()
+{
+ double currentRA, currentDEC;
+
+ if (currentTelescope == NULL)
+ return INVALID_HA;
+
+ if (currentTelescope->getEqCoords(¤tRA, ¤tDEC) == false)
+ {
+ appendLogText(i18n("Failed to retrieve telescope coordinates. Unable to \
calculate telescope's hour angle.")); + return INVALID_HA;
+ }
+
+ dms lst = KStarsData::Instance()->geo()->GSTtoLST(KStarsData::Instance()->clock()->utc().gst());
+
+ dms ha( lst.Degrees() - currentRA*15.0 );
+
+ double HA = ha.Hours();
+
+ if (HA > 12)
+ HA -= 24;
+
+ return HA;
+}
+
+bool Capture::checkMeridianFlip()
+{
+
+ if (meridianCheck->isEnabled() == false || meridianCheck->isChecked() == false \
|| initialHA > 0) + return false;
+
+
+ double currentHA = getCurrentHA();
+
+ //appendLogText(i18n("Current hour angle %1", currentHA));
+
+ if (currentHA == INVALID_HA)
+ return false;
+
+ if (currentHA > meridianHours->value())
+ {
+ //NOTE: DO NOT make the follow sentence PLURAL as the value is in double
+ appendLogText(i18n("Current hour angle %1 hours exceeds meridian flip limit \
of %2 hours. Auto meridian flip is initiated.", QString::number(currentHA, 'f', 2), \
meridianHours->value())); + meridianFlipStage = MF_INITIATED;
+
+ // Suspend guiding first before commanding a meridian flip
+ //if (isAutoGuiding && currentCCD->getChip(ISD::CCDChip::GUIDE_CCD) == \
guideChip) +// emit suspendGuiding(false);
+
+ // If we are autoguiding, we should resume autoguiding after flip
+ resumeGuidingAfterFlip = (guideState == GUIDE_GUIDING);
+
+ if ((guideState == GUIDE_GUIDING) || isAutoFocus)
+ emit meridianFlipStarted();
+
+ double dec;
+ currentTelescope->getEqCoords(&initialRA, &dec);
+ currentTelescope->Slew(initialRA,dec);
+ secondsLabel->setText(i18n("Meridian Flip..."));
+
+ state = CAPTURE_MERIDIAN_FLIP;
+ emit newStatus(Ekos::CAPTURE_MERIDIAN_FLIP);
+
+ QTimer::singleShot(MF_TIMER_TIMEOUT, this, \
SLOT(checkMeridianFlipTimeout())); + return true;
+ }
+
+ return false;
+}
+
+void Capture::checkMeridianFlipTimeout()
+{
+ if (meridianFlipStage == MF_NONE)
+ return;
+
+ if (meridianFlipStage < MF_ALIGNING)
+ {
+ appendLogText(i18n("Telescope meridian flip timed out."));
+ abort();
+ }
+}
+
+void Capture::setAlignStatus(AlignState state)
+{
+ alignState = state;
+
+ resumeAlignmentAfterFlip = true;
+
+ switch (state)
+ {
+ case ALIGN_COMPLETE:
+ if (meridianFlipStage == MF_ALIGNING)
+ {
+ appendLogText(i18n("Post flip re-alignment completed successfully."));
+ checkGuidingAfterFlip();
+ }
+ break;
+
+ case ALIGN_FAILED:
+ // TODO run it 3 times before giving up
+ if (meridianFlipStage == MF_ALIGNING)
+ {
+ appendLogText(i18n("Post-flip alignment failed."));
+ abort();
+ }
+ break;
+
+ default:
+ break;
+
+ }
+}
+
+void Capture::setGuideStatus(GuideState state)
+{
+ switch (state)
+ {
+
+ case GUIDE_IDLE:
+ // If Autoguiding was started before and now stopped, let's abort (unless \
we're doing a meridian flip) + if (guideState == GUIDE_GUIDING && \
meridianFlipStage == MF_NONE && activeJob && activeJob->getStatus() == \
SequenceJob::JOB_BUSY) + {
+ appendLogText(i18n("Autoguiding stopped. Aborting..."));
+ abort();
+ }
+ //isAutoGuiding = false;
+ break;
+
+ case GUIDE_GUIDING:
+ break;
+ //isAutoGuiding = true;
+
+ case GUIDE_CALIBRATION_SUCESS:
+ guideDeviationCheck->setEnabled(true);
+ guideDeviation->setEnabled(true);
+ break;
+
+ case GUIDE_CALIBRATION_ERROR:
+ case GUIDE_ABORTED:
+ // TODO try restarting calibration a couple of times before giving up
+ if (meridianFlipStage == MF_GUIDING)
+ {
+ appendLogText(i18n("Post meridian flip calibration error. \
Aborting...")); + abort();
+ }
+ break;
+
+ case GUIDE_DITHERING_SUCCESS:
+ resumeCapture();
+ break;
+
+ case GUIDE_DITHERING_ERROR:
+ abort();
+ break;
+
+ default:
+ break;
+ }
+
+ guideState = state;
+}
+
+void Capture::checkFrameType(int index)
+{
+ if (index == FRAME_LIGHT)
+ calibrationB->setEnabled(false);
+ else
+ calibrationB->setEnabled(true);
+}
+
+double Capture::setCurrentADU(double value)
+{
+ double nextExposure = 0;
+
+ /*if (ExpRaw1 == -1)
+ ExpRaw1 = activeJob->getExposure();
+ else if (ExpRaw2 == -1)
+ ExpRaw2 = activeJob->getExposure();
+ else
+ {
+ ExpRaw1 = ExpRaw2;
+ ExpRaw2 = activeJob->getExposure();
+ }
+
+ if (ADURaw1 == -1)
+ ADURaw1 = value;
+ else if (ADURaw2 == -1)
+ ADURaw2 = value;
+ else
+ {
+ ADURaw1 = ADURaw2;
+ ADURaw2 = value;
+ }
+
+ qDebug() << "Exposure #1 (" << ExpRaw1 << "," << ADURaw1 << ") Exspoure #2 (" << \
ExpRaw2 << "," << ADURaw2 << ")"; +
+ // If we don't have the 2nd point, let's take another exposure with value \
relative to what we have now + if (ADURaw2 == -1 || ExpRaw2 == -1 || (ADURaw1 == \
ADURaw2)) + {
+ if (value < activeJob->getTargetADU())
+ nextExposure = activeJob->getExposure()*1.5;
+ else
+ nextExposure = activeJob->getExposure()*.75;
+
+ qDebug() << "Next Exposure: " << nextExposure;
+
+ return nextExposure;
+ }
+
+ if (fabs(ADURaw2 - ADURaw1) < 0.01)
+ ADUSlope=1e-6;
+ else
+ ADUSlope = (ExpRaw2 - ExpRaw1) / (ADURaw2 - ADURaw1);
+
+ qDebug() << "ADU Slope: " << ADUSlope;
+
+ nextExposure = ADUSlope * (activeJob->getTargetADU() - ADURaw2) + ExpRaw2;
+
+ qDebug() << "Next Exposure: " << nextExposure;
+
+ return nextExposure;*/
+
+ double a=0,b=0;
+
+ ExpRaw.append(activeJob->getExposure());
+ ADURaw.append(value);
+
+ llsq(ExpRaw, ADURaw, a, b);
+
+ if (a == 0)
+ {
+ if (value < activeJob->getTargetADU())
+ nextExposure = activeJob->getExposure()*1.5;
+ else
+ nextExposure = activeJob->getExposure()*.75;
+
+ qDebug() << "Next Exposure: " << nextExposure;
+
+ return nextExposure;
+ }
+
+ nextExposure = (activeJob->getTargetADU() - b) / a;
+
+ qDebug() << "Next Exposure: " << nextExposure;
+
+ return nextExposure;
+
+}
+
+// Based on John Burkardt LLSQ (LGPL)
+void Capture::llsq (QList<double> x, QList<double> y, double &a, double &b)
+{
+ double bot;
+ int i;
+ double top;
+ double xbar;
+ double ybar;
+ int n = x.count();
+//
+// Special case.
+//
+ if ( n == 1 )
+ {
+ a = 0.0;
+ b = y[0];
+ return;
+ }
+//
+// Average X and Y.
+//
+ xbar = 0.0;
+ ybar = 0.0;
+ for ( i = 0; i < n; i++ )
+ {
+ xbar = xbar + x[i];
+ ybar = ybar + y[i];
+ }
+ xbar = xbar / ( double ) n;
+ ybar = ybar / ( double ) n;
+//
+// Compute Beta.
+//
+ top = 0.0;
+ bot = 0.0;
+ for ( i = 0; i < n; i++ )
+ {
+ top = top + ( x[i] - xbar ) * ( y[i] - ybar );
+ bot = bot + ( x[i] - xbar ) * ( x[i] - xbar );
+ }
+
+ a = top / bot;
+
+ b = ybar - a * xbar;
+
+ return;
+}
+
+
+void Capture::setDirty()
+{
+ mDirty = true;
+}
+
+void Capture::setMeridianFlip(bool enable)
+{
+ meridianCheck->setChecked(enable);
+}
+
+void Capture::setMeridianFlipHour(double hours)
+{
+ meridianHours->setValue(hours);
+}
+
+bool Capture::hasCoolerControl()
+{
+ if (currentCCD && currentCCD->hasCoolerControl())
+ return true;
+
+ return false;
+}
+
+bool Capture::setCoolerControl(bool enable)
+{
+ if (currentCCD && currentCCD->hasCoolerControl())
+ return currentCCD->setCoolerControl(enable);
+
+ return false;
+}
+
+void Capture::clearAutoFocusHFR()
+{
+ // If HFR limit was set from file, we cannot overide it.
+ if (fileHFR > 0)
+ return;
+
+ HFRPixels->setValue(0);
+ firstAutoFocus=true;
+}
+
+void Capture::openCalibrationDialog()
+{
+ QDialog calibrationDialog;
+
+ Ui_calibrationOptions calibrationOptions;
+ calibrationOptions.setupUi(&calibrationDialog);
+
+ if (currentTelescope)
+ {
+ calibrationOptions.parkMountC->setEnabled(currentTelescope->canPark());
+ calibrationOptions.parkMountC->setChecked(preMountPark);
+ }
+ else
+ calibrationOptions.parkMountC->setEnabled(false);
+
+ if (dome)
+ {
+ calibrationOptions.parkDomeC->setEnabled(dome->canPark());
+ calibrationOptions.parkDomeC->setChecked(preDomePark);
+ }
+ else
+ calibrationOptions.parkDomeC->setEnabled(false);
+
+
+ //connect(calibrationOptions.wallSourceC, SIGNAL(toggled(bool)), \
calibrationOptions.parkC, SLOT(setDisabled(bool))); +
+ switch (flatFieldSource)
+ {
+ case SOURCE_MANUAL:
+ calibrationOptions.manualSourceC->setChecked(true);
+ break;
+
+ case SOURCE_FLATCAP:
+ calibrationOptions.flatDeviceSourceC->setChecked(true);
+ break;
+
+ case SOURCE_DARKCAP:
+ calibrationOptions.darkDeviceSourceC->setChecked(true);
+ break;
+
+ case SOURCE_WALL:
+ calibrationOptions.wallSourceC->setChecked(true);
+ calibrationOptions.azBox->setText(wallCoord.az().toDMSString());
+ calibrationOptions.altBox->setText(wallCoord.alt().toDMSString());
+ break;
+
+ case SOURCE_DAWN_DUSK:
+ calibrationOptions.dawnDuskFlatsC->setChecked(true);
+ break;
+ }
+
+ switch (flatFieldDuration)
+ {
+ case DURATION_MANUAL:
+ calibrationOptions.manualDurationC->setChecked(true);
+ break;
+
+ case DURATION_ADU:
+ calibrationOptions.ADUC->setChecked(true);
+ calibrationOptions.ADUValue->setValue(targetADU);
+ break;
+ }
+
+ if (calibrationDialog.exec() == QDialog::Accepted)
+ {
+ if (calibrationOptions.manualSourceC->isChecked())
+ flatFieldSource = SOURCE_MANUAL;
+ else if (calibrationOptions.flatDeviceSourceC->isChecked())
+ flatFieldSource = SOURCE_FLATCAP;
+ else if (calibrationOptions.darkDeviceSourceC->isChecked())
+ flatFieldSource = SOURCE_DARKCAP;
+ else if (calibrationOptions.wallSourceC->isChecked())
+ {
+ dms wallAz, wallAlt;
+ bool azOk=false, altOk=false;
+
+ wallAz = calibrationOptions.azBox->createDms(true, &azOk);
+ wallAlt = calibrationOptions.altBox->createDms(true, &altOk);
+
+ if (azOk && altOk)
+ {
+ flatFieldSource = SOURCE_WALL;
+ wallCoord.setAz(wallAz);
+ wallCoord.setAlt(wallAlt);
+ }
+ else
+ {
+ calibrationOptions.manualSourceC->setChecked(true);
+ KMessageBox::error(this, i18n("Wall coordinates are invalid."));
+ }
+ }
+ else
+ flatFieldSource = SOURCE_DAWN_DUSK;
+
+
+ if (calibrationOptions.manualDurationC->isChecked())
+ flatFieldDuration = DURATION_MANUAL;
+ else
+ {
+ flatFieldDuration = DURATION_ADU;
+ targetADU = calibrationOptions.ADUValue->value();
+ }
+
+ preMountPark = calibrationOptions.parkMountC->isChecked();
+ preDomePark = calibrationOptions.parkDomeC->isChecked();
+
+ setDirty();
+ }
+}
+
+IPState Capture::processPreCaptureCalibrationStage()
+{
+ if (guideState == GUIDE_GUIDING)
+ {
+ appendLogText(i18n("Autoguiding suspended."));
+ emit suspendGuiding(true);
+ }
+
+ // Let's check what actions to be taken, if any, for the flat field source
+ switch (activeJob->getFlatFieldSource())
+ {
+ case SOURCE_MANUAL:
+ case SOURCE_DAWN_DUSK: // Not yet implemented
+ break;
+
+ // Park cap, if not parked
+ // Turn on Light
+ case SOURCE_FLATCAP:
+ if (dustCap)
+ {
+ // If cap is not park, park it
+ if (calibrationStage < CAL_DUSTCAP_PARKING && dustCap->isParked() == \
false) + {
+ if (dustCap->Park())
+ {
+ calibrationStage = CAL_DUSTCAP_PARKING;
+ appendLogText(i18n("Parking dust cap..."));
+ return IPS_BUSY;
+ }
+ else
+ {
+ appendLogText(i18n("Parking dust cap failed, aborting..."));
+ abort();
+ return IPS_ALERT;
+ }
+ }
+
+ // Wait until cap is parked
+ if (calibrationStage == CAL_DUSTCAP_PARKING)
+ {
+ if (dustCap->isParked() == false)
+ return IPS_BUSY;
+ else
+ {
+ calibrationStage = CAL_DUSTCAP_PARKED;
+ appendLogText(i18n("Dust cap parked."));
+ }
+ }
+
+ // If light is not on, turn it on. For flat frames only
+ if (activeJob->getFrameType() == FRAME_FLAT && dustCap->isLightOn() == \
false) + {
+ dustCapLightEnabled = true;
+ dustCap->SetLightEnabled(true);
+ }
+ else if (activeJob->getFrameType() != FRAME_FLAT && dustCap->isLightOn() \
== true) + {
+ dustCapLightEnabled = false;
+ dustCap->SetLightEnabled(false);
+ }
+ }
+ break;
+
+
+ // Park cap, if not parked and not flat frame
+ // Unpark cap, if flat frame
+ // Turn on Light
+ case SOURCE_DARKCAP:
+ if (dustCap)
+ {
+ // If cap is not park, park it if not flat frame. (external lightsource)
+ if (calibrationStage < CAL_DUSTCAP_PARKING && dustCap->isParked() == \
false && activeJob->getFrameType() != FRAME_FLAT) + {
+ if (dustCap->Park())
+ {
+ calibrationStage = CAL_DUSTCAP_PARKING;
+ appendLogText(i18n("Parking dust cap..."));
+ return IPS_BUSY;
+ }
+ else
+ {
+ appendLogText(i18n("Parking dust cap failed, aborting..."));
+ abort();
+ return IPS_ALERT;
+ }
+ }
+
+ // Wait until cap is parked
+ if (calibrationStage == CAL_DUSTCAP_PARKING)
+ {
+ if (dustCap->isParked() == false)
+ return IPS_BUSY;
+ else
+ {
+ calibrationStage = CAL_DUSTCAP_PARKED;
+ appendLogText(i18n("Dust cap parked."));
+ }
+ }
+
+ // If cap is parked, unpark it if flat frame. (external lightsource)
+ if (calibrationStage < CAL_DUSTCAP_UNPARKING && dustCap->isParked() == \
true && activeJob->getFrameType() == FRAME_FLAT) + {
+ if (dustCap->UnPark())
+ {
+ calibrationStage = CAL_DUSTCAP_UNPARKING;
+ appendLogText(i18n("UnParking dust cap..."));
+ return IPS_BUSY;
+ }
+ else
+ {
+ appendLogText(i18n("UnParking dust cap failed, aborting..."));
+ abort();
+ return IPS_ALERT;
+ }
+ }
+
+ // Wait until cap is parked
+ if (calibrationStage == CAL_DUSTCAP_UNPARKING)
+ {
+ if (dustCap->isParked() == true)
+ return IPS_BUSY;
+ else
+ {
+ calibrationStage = CAL_DUSTCAP_UNPARKED;
+ appendLogText(i18n("Dust cap unparked."));
+ }
+ }
+
+ // If light is not on, turn it on. For flat frames only
+ if (activeJob->getFrameType() == FRAME_FLAT && dustCap->isLightOn() == \
false) + {
+ dustCapLightEnabled = true;
+ dustCap->SetLightEnabled(true);
+ }
+ else if (activeJob->getFrameType() != FRAME_FLAT && dustCap->isLightOn() \
== true) + {
+ dustCapLightEnabled = false;
+ dustCap->SetLightEnabled(false);
+ }
+ }
+ break;
+
+ // Go to wall coordinates
+ case SOURCE_WALL:
+ if (currentTelescope)
+ {
+ if (calibrationStage < CAL_SLEWING)
+ {
+ wallCoord = activeJob->getWallCoord();
+ wallCoord.HorizontalToEquatorial(KStarsData::Instance()->lst(), \
KStarsData::Instance()->geo()->lat()); + \
currentTelescope->Slew(&wallCoord); + appendLogText(i18n("Mount \
slewing to wall position...")); + calibrationStage = CAL_SLEWING;
+ return IPS_BUSY;
+ }
+
+ // Check if slewing is complete
+ if (calibrationStage == CAL_SLEWING)
+ {
+ if (currentTelescope->isSlewing() == false)
+ {
+ calibrationStage = CAL_SLEWING_COMPLETE;
+ appendLogText(i18n("Slew to wall position complete."));
+ }
+ else
+ return IPS_BUSY;
+ }
+
+ if (lightBox)
+ {
+ // Check if we have a light box to turn on
+ if (activeJob->getFrameType() == FRAME_FLAT && \
lightBox->isLightOn() == false) + {
+ lightBoxLightEnabled = true;
+ lightBox->SetLightEnabled(true);
+ }
+ else if (activeJob->getFrameType() != FRAME_FLAT && \
lightBox->isLightOn() == true) + {
+ lightBoxLightEnabled = false;
+ lightBox->SetLightEnabled(false);
+ }
+ }
+ }
+ break;
+ }
+
+ // Check if we need to perform mount prepark
+ if (preMountPark && currentTelescope && activeJob->getFlatFieldSource() != \
SOURCE_WALL) + {
+ if (calibrationStage < CAL_MOUNT_PARKING && currentTelescope->isParked() == \
false) + {
+ if (currentTelescope->Park())
+ {
+ calibrationStage = CAL_MOUNT_PARKING;
+ //emit mountParking();
+ appendLogText(i18n("Parking mount prior to calibration frames \
capture...")); + return IPS_BUSY;
+ }
+ else
+ {
+ appendLogText(i18n("Parking mount failed, aborting..."));
+ abort();
+ return IPS_ALERT;
+ }
+ }
+
+ if (calibrationStage == CAL_MOUNT_PARKING)
+ {
+ // If not parked yet, check again in 1 second
+ // Otherwise proceed to the rest of the algorithm
+ if (currentTelescope->isParked() == false)
+ return IPS_BUSY;
+ else
+ {
+ calibrationStage = CAL_MOUNT_PARKED;
+ appendLogText(i18n("Mount parked."));
+ }
+ }
+ }
+
+ // Check if we need to perform dome prepark
+ if (preDomePark && dome)
+ {
+ if (calibrationStage < CAL_DOME_PARKING && dome->isParked() == false)
+ {
+ if (dome->Park())
+ {
+ calibrationStage = CAL_DOME_PARKING;
+ //emit mountParking();
+ appendLogText(i18n("Parking dome..."));
+ return IPS_BUSY;
+ }
+ else
+ {
+ appendLogText(i18n("Parking dome failed, aborting..."));
+ abort();
+ return IPS_ALERT;
+ }
+ }
+
+ if (calibrationStage == CAL_DOME_PARKING)
+ {
+ // If not parked yet, check again in 1 second
+ // Otherwise proceed to the rest of the algorithm
+ if (dome->isParked() == false)
+ return IPS_BUSY;
+ else
+ {
+ calibrationStage = CAL_DOME_PARKED;
+ appendLogText(i18n("Dome parked."));
+ }
+ }
+ }
+
+ calibrationStage = CAL_PRECAPTURE_COMPLETE;
+ return IPS_OK;
+}
+
+bool Capture::processPostCaptureCalibrationStage()
+{
+ // Check if we need to do flat field slope calculation if the user specified a \
desired ADU value + if (activeJob->getFrameType() == FRAME_FLAT && \
activeJob->getFlatFieldDuration() == DURATION_ADU && activeJob->getTargetADU() > 0) + \
{ + FITSData *image_data = NULL;
+ FITSView *currentImage = targetChip->getImage(FITS_NORMAL);
+ if (currentImage)
+ {
+ image_data = currentImage->getImageData();
+ double currentADU = image_data->getADU();
+ //double currentSlope = ADUSlope;
+
+ double percentageDiff=0;
+ if (currentADU > activeJob->getTargetADU())
+ percentageDiff = activeJob->getTargetADU()/currentADU;
+ else
+ percentageDiff = currentADU / activeJob->getTargetADU();
+
+ // If it is within 2% of target ADU
+ if (percentageDiff >= 0.98)
+ {
+ if (calibrationStage == CAL_CALIBRATION)
+ {
+ appendLogText(i18n("Current ADU %1 within target ADU tolerance \
range.", QString::number(currentADU, 'f', 0))); + \
activeJob->setPreview(false); + calibrationStage = \
CAL_CALIBRATION_COMPLETE; + startNextExposure();
+ return false;
+ }
+
+ return true;
+ }
+
+ double nextExposure = setCurrentADU(currentADU);
+
+ if (nextExposure <= 0)
+ {
+ appendLogText(i18n("Unable to calculate optimal exposure settings, \
please take the flats manually.")); + //activeJob->setTargetADU(0);
+ //targetADU = 0;
+ abort();
+ return false;
+ }
+
+ appendLogText(i18n("Current ADU is %1 Next exposure is %2 seconds.", \
QString::number(currentADU, 'f', 0), QString::number(nextExposure, 'f', 3))); +
+ calibrationStage = CAL_CALIBRATION;
+ activeJob->setExposure(nextExposure);
+ activeJob->setPreview(true);
+
+ startNextExposure();
+ return false;
+
+ // Start next exposure in case ADU Slope is not calculated yet
+ /*if (currentSlope == 0)
+ {
+ startNextExposure();
+ return;
+ }*/
+ }
+ else
+ {
+ appendLogText(i18n("An empty image is received, aborting..."));
+ abort();
+ return false;
+ }
+ }
+
+ calibrationStage = CAL_CALIBRATION_COMPLETE;
+ return true;
+}
+
+bool Capture::isSequenceFileComplete(const QString &fileURL)
+{
+ // If we don't remember job progress, then no sequence would be complete
+ if (Options::rememberJobProgress() == false)
+ return false;
+
+ // We cannot know if the job is complete if the upload mode is local since we \
cannot inspect the files + if (currentCCD && currentCCD->getUploadMode() == \
ISD::CCD::UPLOAD_LOCAL) + return false;
+
+ if (Options::captureLogging())
+ {
+ qDebug() << "Capture: Loading sequence to check for completion: " << \
fileURL; + }
+
+ bool rc = loadSequenceQueue(fileURL);
+
+ if (rc == false)
+ return false;
+
+ ignoreJobProgress = false;
+
+ QStringList jobDirs;
+ int totalJobCount = 0, totalFileCount=0;
+ foreach(SequenceJob *job, jobs)
+ {
+ jobDirs << job->getFITSDir();
+ totalJobCount += job->getCount();
+ }
+
+ jobDirs.removeDuplicates();
+
+ if (Options::captureLogging())
+ {
+ qDebug() << "Capture: Total Job Count --> " << totalFileCount;
+ qDebug() << "Capture: isSequenceFileComplete directories --> " << jobDirs;
+ }
+
+ foreach(QString dir, jobDirs)
+ {
+ QDir oneDir(dir);
+ oneDir.setFilter(QDir::Files | QDir::NoDotAndDotDot);
+ totalFileCount += oneDir.count();
+
+ if (Options::captureLogging())
+ {
+ qDebug() << "Capture: Directory " << dir << " file count is " << \
oneDir.count() << " and total count is " << totalFileCount; + }
+ }
+
+ clearSequenceQueue();
+
+ return (totalFileCount >= totalJobCount);
+}
+
+void Capture::setNewRemoteFile(QString file)
+{
+ appendLogText(i18n("Remote image saved to %1", file));
+}
+
+void Capture::startPostFilterAutoFocus()
+{
+ if (focusState >= FOCUS_PROGRESS)
+ return;
+
+ secondsLabel->setText(i18n("Focusing..."));
+
+ state = CAPTURE_FOCUSING;
+ emit newStatus(Ekos::CAPTURE_FOCUSING);
+
+ appendLogText(i18n("Post filter change Autofocus..."));
+
+ // Force it to always run autofocus routine
+ emit checkFocus(0.1);
+}
+
+void Capture::postScriptFinished(int exitCode)
+{
+ appendLogText(i18n("Post capture script finished with code %1. Resuming \
sequence...", exitCode)); + resumeSequence();
+}
+
+}
diff --git a/kstars/ekos/capture/capture.h b/kstars/ekos/capture/capture.h
new file mode 100644
index 0000000..34335b3
--- /dev/null
+++ b/kstars/ekos/capture/capture.h
@@ -0,0 +1,590 @@
+/* Ekos Capture tool
+ Copyright (C) 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
+
+ This application is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+ */
+
+#ifndef CAPTURE_H
+#define CAPTURE_H
+
+#include <QTimer>
+#include <QUrl>
+#include <QtDBus/QtDBus>
+
+#include "ui_capture.h"
+
+#include "ekos.h"
+#include "fitsviewer/fitscommon.h"
+#include "indi/indistd.h"
+#include "indi/indiccd.h"
+#include "indi/indicap.h"
+#include "indi/indidome.h"
+#include "indi/indilightbox.h"
+#include "indi/inditelescope.h"
+
+class QProgressIndicator;
+class QTableWidgetItem;
+class KDirWatch;
+
+/**
+ *@namespace Ekos
+ *@short Ekos is an advanced Astrophotography tool for Linux.
+ * It is based on a modular extensible framework to perform common astrophotography \
tasks. This includes highly accurate GOTOs using astrometry solver, ability to \
measure and correct polar alignment errors , + * auto-focus & auto-guide \
capabilities, and capture of single or stack of images with filter wheel support.\n + \
* Features: + * - Control your telescope, CCD (& DSLRs), filter wheel, focuser, \
guider, adaptive optics unit, and any INDI-compatible auxiliary device from Ekos. + * \
- Extremely accurate GOTOs using astrometry.net solver (both Online and Offline \
solvers supported). + * - Load & Slew: Load a FITS image, slew to solved coordinates, \
and center the mount on the exact image coordinates in order to get the same desired \
frame. + * - Measure & Correct Polar Alignment errors using astromety.net solver.
+ * - Auto and manual focus modes using Half-Flux-Radius (HFR) method.
+ * - Automated unattended meridian flip. Ekos performs post meridian flip alignment, \
calibration, and guiding to resume the capture session. + * - Automatic focus between \
exposures when a user-configurable HFR limit is exceeded. + * - Auto guiding with \
support for automatic dithering between exposures and support for Adaptive Optics \
devices in addition to traditional guiders. + * - Powerful sequence queue for batch \
capture of images with optional prefixes, timestamps, filter wheel selection, and \
much more! + * - Export and import sequence queue sets as Ekos Sequence Queue (.esq) \
files. + * - Center the telescope anywhere in a captured FITS image or any FITS with \
World Coordinate System (WCS) header. + * - Automatic flat field capture, just set \
the desired ADU and let Ekos does the rest! + * - Automatic abort and resumption of \
exposure tasks if guiding errors exceed a user-configurable value. + * - Support for \
dome slaving. + * - Complete integration with KStars Observation Planner and SkyMap
+ * - Integrate with all INDI native devices.
+ * - Powerful scripting capabilities via \ref EkosDBusInterface "DBus."
+ *
+ * The primary class is EkosManager. It handles startup and shutdown of local and \
remote INDI devices, manages and orchesterates the various Ekos modules, and provides \
advanced DBus + * interface to enable unattended scripting.
+*@author Jasem Mutlaq
+ *@version 1.3
+ */
+namespace Ekos
+{
+
+class SequenceJob;
+
+/**
+ *@class Capture
+ *@short Captures single or sequence of images from a CCD.
+ * The capture class support capturing single or multiple images from a CCD, it \
provides a powerful sequence queue with filter wheel selection. Any sequence queue \
can be saved as Ekos Sequence Queue (.esq). + * All image capture operations are \
saved as Sequence Jobs that encapsulate all the different options in a capture \
process. The user may select in sequence autofocusing by setting a maximum HFR limit. \
When the limit + * is exceeded, it automatically trigger autofocus operation. The \
capture process can also be linked with guide module. If guiding deviations exceed a \
certain threshold, the capture operation aborts until + * the guiding deviation \
resume to acceptable levels and the capture operation is resumed. + *@author Jasem \
Mutlaq + *@version 1.2
+ */
+class Capture : public QWidget, public Ui::Capture
+{
+
+ Q_OBJECT
+ Q_CLASSINFO("D-Bus Interface", "org.kde.kstars.Ekos.Capture")
+
+public:
+
+ enum { CALIBRATE_NONE, CALIBRATE_START, CALIBRATE_DONE };
+ typedef enum { MF_NONE, MF_INITIATED, MF_FLIPPING, MF_SLEWING, MF_ALIGNING, \
MF_GUIDING } MFStage; + typedef enum { CAL_NONE, CAL_DUSTCAP_PARKING, \
CAL_DUSTCAP_PARKED, CAL_LIGHTBOX_ON, CAL_SLEWING, CAL_SLEWING_COMPLETE, \
CAL_MOUNT_PARKING, CAL_MOUNT_PARKED, CAL_DOME_PARKING, CAL_DOME_PARKED, \
CAL_PRECAPTURE_COMPLETE, CAL_CALIBRATION, CAL_CALIBRATION_COMPLETE, CAL_CAPTURING, \
CAL_DUSTCAP_UNPARKING, CAL_DUSTCAP_UNPARKED} CalibrationStage; + typedef bool \
(Capture::*PauseFunctionPointer)(); +
+ Capture();
+ ~Capture();
+
+ /** @defgroup CaptureDBusInterface Ekos DBus Interface - Capture Module
+ * Ekos::Capture interface provides advanced scripting capabilities to capture \
image sequences. + */
+
+ /*@{*/
+
+ /** DBUS interface function.
+ * select the CCD device from the available CCD drivers.
+ * @param device The CCD device name
+ */
+ Q_SCRIPTABLE bool setCCD(QString device);
+
+ /** DBUS interface function.
+ * select the filter device from the available filter drivers. The filter device \
can be the same as the CCD driver if the filter functionality was embedded within the \
driver. + * @param device The filter device name
+ */
+ Q_SCRIPTABLE bool setFilter(QString device, int filterSlot);
+
+ /** DBUS interface function.
+ * Aborts any current jobs and remove all sequence queue jobs.
+ */
+ Q_SCRIPTABLE Q_NOREPLY void clearSequenceQueue();
+
+ /** DBUS interface function.
+ * Returns the overall sequence queue status. If there are no jobs pending, it \
returns "Invalid". If all jobs are idle, it returns "Idle". If all jobs are complete, \
it returns "Complete". If one or more jobs are aborted + * it returns "Aborted" \
unless it was temporarily aborted due to guiding deviations, then it would return \
"Suspended". If one or more jobs have errors, it returns "Error". If any jobs is \
under progress, returns "Running". + */
+ Q_SCRIPTABLE QString getSequenceQueueStatus();
+
+ /** DBUS interface function.
+ * Opens a sequence files and checks whether the jobs contained within are \
complete or not. The check is done by quering the file system for the produced files \
for each job. + * If returns true if all jobs are complete, false otherwise.sudo
+ */
+ Q_SCRIPTABLE bool isSequenceFileComplete(const QString &fileURL);
+
+ /** DBUS interface function.
+ * Loads the Ekos Sequence Queue file in the Sequence Queue. Jobs are appended \
to existing jobs. + * @param fileURL full URL of the filename
+ */
+ Q_SCRIPTABLE bool loadSequenceQueue(const QString &fileURL);
+
+ /** DBUS interface function.
+ * Sets target name. The target name shall be appended to the root directory \
specified by the user. + * e.g. If root directory is /home/jasem and target is \
M31, then root directory becomes /home/jasem/M31 + * @param name Name of desired \
target + */
+ Q_SCRIPTABLE Q_NOREPLY void setTargetName(const QString &name) { targetName = \
name; } +
+ /** DBUS interface function.
+ * Enables or disables the maximum guiding deviation and sets its value.
+ * @param enable If true, enable the guiding deviation check, otherwise, disable \
it. + * @param if enable is true, it sets the maximum guiding deviation in \
arcsecs. If the value is exceeded, the capture operation is aborted until the value \
falls below the value threshold. + */
+ Q_SCRIPTABLE Q_NOREPLY void setMaximumGuidingDeviaiton(bool enable, double \
value); +
+ /** DBUS interface function.
+ * Enables or disables the in sequence focus and sets Half-Flux-Radius (HFR) \
limit. + * @param enable If true, enable the in sequence auto focus check, \
otherwise, disable it. + * @param if enable is true, it sets HFR in pixels. After \
each exposure, the HFR is re-measured and if it exceeds the specified value, an \
autofocus operation will be commanded. + */
+ Q_SCRIPTABLE Q_NOREPLY void setInSequenceFocus(bool enable, double HFR);
+
+ /** DBUS interface function.
+ * Enables or disables park on complete option.
+ * @param enable If true, mount shall be commanded to parking position after all \
jobs are complete in the sequence queue. + */
+ Q_SCRIPTABLE Q_NOREPLY void setParkOnComplete(bool enable);
+
+ /** DBUS interface function.
+ * Enable or Disable meridian flip, and sets its activation hour.
+ * @param enable If true, meridian flip will be command after user-configurable \
number of hours. + */
+ Q_SCRIPTABLE Q_NOREPLY void setMeridianFlip(bool enable);
+
+ /** DBUS interface function.
+ * Sets meridian flip trigger hour.
+ * @param hours Number of hours after the meridian at which the mount is \
commanded to flip. + */
+ Q_SCRIPTABLE Q_NOREPLY void setMeridianFlipHour(double hours);
+
+ /** DBUS interface function.
+ * Does the CCD has a cooler control (On/Off) ?
+ */
+ Q_SCRIPTABLE bool hasCoolerControl();
+
+ /** DBUS interface function.
+ * Set the CCD cooler ON/OFF
+ *
+ */
+ Q_SCRIPTABLE bool setCoolerControl(bool enable);
+
+ /** DBUS interface function.
+ * @return Returns the percentage of completed captures in all active jobs
+ */
+ Q_SCRIPTABLE double getProgressPercentage();
+
+ /** DBUS interface function.
+ * @return Returns the number of jobs in the sequence queue.
+ */
+ Q_SCRIPTABLE int getJobCount() { return jobs.count(); }
+
+ /** DBUS interface function.
+ * @return Returns ID of current active job if any, or -1 if there are no active \
jobs. + */
+ Q_SCRIPTABLE int getActiveJobID();
+
+ /** DBUS interface function.
+ * @return Returns time left in seconds until active job is estimated to be \
complete. + */
+ Q_SCRIPTABLE int getActiveJobRemainingTime();
+
+ /** DBUS interface function.
+ * @return Returns overall time left in seconds until all jobs are estimated to \
be complete + */
+ Q_SCRIPTABLE int getOverallRemainingTime();
+
+ /** DBUS interface function.
+ * @param id job number. Job IDs start from 0 to N-1.
+ * @return Returns the job state (Idle, In Progress, Error, Aborted, Complete)
+ */
+ Q_SCRIPTABLE QString getJobState(int id);
+
+ /** DBUS interface function.
+ * @param id job number. Job IDs start from 0 to N-1.
+ * @return Returns The number of images completed capture in the job.
+ */
+ Q_SCRIPTABLE int getJobImageProgress(int id);
+
+ /** DBUS interface function.
+ * @param id job number. Job IDs start from 0 to N-1.
+ * @return Returns the total number of images to capture in the job.
+ */
+ Q_SCRIPTABLE int getJobImageCount(int id);
+
+ /** DBUS interface function.
+ * @param id job number. Job IDs start from 0 to N-1.
+ * @return Returns the number of seconds left in an exposure operation.
+ */
+ Q_SCRIPTABLE double getJobExposureProgress(int id);
+
+ /** DBUS interface function.
+ * @param id job number. Job IDs start from 0 to N-1.
+ * @return Returns the total requested exposure duration in the job.
+ */
+ Q_SCRIPTABLE double getJobExposureDuration(int id);
+
+ /** DBUS interface function.
+ * Clear in-sequence focus settings. It sets the autofocus HFR to zero so that \
next autofocus value is remembered for the in-sequence focusing. + */
+ Q_SCRIPTABLE Q_NOREPLY void clearAutoFocusHFR();
+
+
+ /** DBUS interface function.
+ * Jobs will NOT be checked for progress against the file system and will be \
always assumed as new jobs. + */
+ Q_SCRIPTABLE Q_NOREPLY void ignoreSequenceHistory();
+
+ /** @}*/
+
+ void addCCD(ISD::GDInterface *newCCD);
+ void addFilter(ISD::GDInterface *newFilter);
+ void setDome(ISD::GDInterface *device) { dome = \
dynamic_cast<ISD::Dome*>(device); } + void setDustCap(ISD::GDInterface *device) { \
dustCap = dynamic_cast<ISD::DustCap*>(device); } + void \
setLightBox(ISD::GDInterface *device) { lightBox = \
dynamic_cast<ISD::LightBox*>(device); } + void addGuideHead(ISD::GDInterface \
*newCCD); + void syncFrameType(ISD::GDInterface *ccd);
+ void setTelescope(ISD::GDInterface *newTelescope);
+ void syncTelescopeInfo();
+ void syncFilterInfo();
+
+ void clearLog();
+ QString getLogText() { return logText.join("\n"); }
+
+ /* Capture */
+ void updateSequencePrefix( const QString &newPrefix, const QString &dir);
+
+public slots:
+
+ /** \addtogroup CaptureDBusInterface
+ * @{
+ */
+
+ /* Capture */
+ /** DBUS interface function.
+ * Starts the sequence queue capture procedure sequentially by starting all jobs \
that are either Idle or Aborted in order. + */
+ Q_SCRIPTABLE Q_NOREPLY void start();
+
+ /** DBUS interface function.
+ * Stop all jobs and set current job status to aborted if abort is set to true, \
otherwise status is idle until + * sequence is resumed or restarted.
+ * @param abort abort jobs if in progress
+ */
+ Q_SCRIPTABLE Q_NOREPLY void stop(bool abort=false);
+
+ /** DBUS interface function.
+ * Aborts all jobs. It simply calls stop(true)
+ */
+ Q_SCRIPTABLE Q_NOREPLY void abort() { stop(true); }
+
+ /** @}*/
+
+ /**
+ * @brief captureOne Capture one preview image
+ */
+ void captureOne();
+
+ /**
+ * @brief captureImage Initiates image capture in the active job.
+ */
+ void captureImage();
+
+ /**
+ * @brief newFITS process new FITS data received from camera. Update status of \
active job and overall sequence. + * @param bp pointer to blob contianing FITS \
data + */
+ void newFITS(IBLOB *bp);
+
+ /**
+ * @brief checkCCD Refreshes the CCD information in the capture module.
+ * @param CCDNum The CCD index in the CCD combo box to select as the active CCD.
+ */
+ void checkCCD(int CCDNum=-1);
+
+ /**
+ * @brief checkFilter Refreshes the filter wheel information in the capture \
module. + * @param filterNum The filter wheel index in the filter device combo \
box to set as the active filter. + */
+ void checkFilter(int filterNum=-1);
+
+ /**
+ * @brief processCCDNumber Process number properties arriving from CCD. \
Currently, only CCD and Guider frames are processed. + * @param nvp pointer to \
number property. + */
+ void processCCDNumber(INumberVectorProperty *nvp);
+
+ /**
+ * @brief processTelescopeNumber Process number properties arriving from \
telescope for meridian flip purposes. + * @param nvp pointer to number property.
+ */
+ void processTelescopeNumber(INumberVectorProperty *nvp);
+
+ /**
+ * @brief addJob Add a new job to the sequence queue given the settings in the \
GUI. + * @param preview True if the job is a preview job, otherwise, it is added \
as a batch job. + */
+ void addJob(bool preview=false);
+
+ /**
+ * @brief removeJob Remove a job from the currently selected row. If no row is \
selected, it remove the last job in the queue. + */
+ void removeJob();
+
+ /**
+ * @brief moveJobUp Move the job in the sequence queue one place up.
+ */
+ void moveJobUp();
+
+ /**
+ * @brief moveJobDown Move the job in the sequence queue one place down.
+ */
+ void moveJobDown();
+
+ /**
+ * @brief setGuideDeviation Set the guiding deviaiton as measured by the guiding \
module. Abort capture if deviation exceeds user value. Resume capture if capture was \
aborted and guiding deviations are below user value. + * @param delta_ra \
Deviation in RA in arcsecs from the selected guide star. + * @param delta_dec \
Deviation in DEC in arcsecs from the selected guide star. + */
+ void setGuideDeviation(double delta_ra, double delta_dec);
+
+ /**
+ * @brief setGuideDither Set whether dithering is enable/disabled in guiding \
module. + * @param enable True if dithering is enabled, false otherwise.
+ */
+ void setGuideDither(bool enable);
+
+ /**
+ * @brief resumeCapture Resume capture after dither and/or focusing processes \
are complete. + */
+ bool resumeCapture();
+
+ /**
+ * @brief updateCCDTemperature Update CCD temperature in capture module.
+ * @param value Temperature in celcius.
+ */
+ void updateCCDTemperature(double value);
+
+ /**
+ * @brief setTemperature Set CCD temperature from the user GUI settings.
+ */
+ void setTemperature();
+
+ // Pause Sequence Queue
+ void pause();
+
+ // Logs
+ void appendLogText(const QString &);
+
+ // Auto Focus
+ void setFocusStatus(Ekos::FocusState state);
+ // Guide
+ void setGuideStatus(Ekos::GuideState state);
+ // Align
+ void setAlignStatus(Ekos::AlignState state);
+
+private slots:
+
+ /**
+ * @brief setDirty Set dirty bit to indicate sequence queue file was modified \
and needs saving. + */
+ void setDirty();
+
+ void toggleSequence();
+
+
+ void checkFrameType(int index);
+ void resetFrame();
+ void updateCaptureProgress(ISD::CCDChip *tChip, double value, IPState state);
+ void checkSeqBoundary(const QString &path);
+ void saveFITSDirectory();
+ void setDefaultCCD(QString ccd);
+ void setNewRemoteFile(QString file);
+ void setGuideChip(ISD::CCDChip* chip) { guideChip = chip; }
+
+ // Sequence Queue
+ void loadSequenceQueue();
+ void saveSequenceQueue();
+ void saveSequenceQueueAs();
+
+ // Jobs
+ void resetJobs();
+ void editJob(QModelIndex i);
+ void resetJobEdit();
+ void executeJob();
+
+ // Meridian Flip
+ void checkMeridianFlipTimeout();
+ //void checkAlignmentSlewComplete();
+
+ // Auto Focus
+ //void updateAutofocusStatus(bool status, double HFR);
+ void setHFR(double newHFR) { focusHFR = newHFR; }
+ void startPostFilterAutoFocus();
+
+ // Flat field
+ void openCalibrationDialog();
+ IPState processPreCaptureCalibrationStage();
+ bool processPostCaptureCalibrationStage();
+
+ // Send image info
+ void sendNewImage(QImage *image, ISD::CCDChip *myChip);
+
+ // Capture
+ bool setCaptureComplete();
+
+ // Temporary for post capture script
+ void postScriptFinished(int exitCode);
+
+signals:
+ void newLog();
+ //void exposureComplete();
+ void checkFocus(double);
+ //void mountParking();
+ void suspendGuiding(bool);
+ void meridianFlipStarted();
+ void meridianFlipCompleted();
+ void newStatus(Ekos::CaptureState status);
+ void newImage(QImage *image, Ekos::SequenceJob *job);
+
+private:
+
+ void setBusy(bool enable);
+ bool resumeSequence();
+ bool startNextExposure();
+ void updateFrameProperties(bool reset=false);
+ void prepareJob(SequenceJob *job);
+ void syncGUIToJob(SequenceJob *job);
+ bool processJobInfo(XMLEle *root);
+ void processJobCompletion();
+ bool saveSequenceQueue(const QString &path);
+ void constructPrefix(QString &imagePrefix);
+ double setCurrentADU(double value);
+ void llsq (QList<double> x, QList<double> y, double &a, double &b);
+
+ /* Meridian Flip */
+ bool checkMeridianFlip();
+ void checkGuidingAfterFlip();
+ double getCurrentHA();
+
+ // Remaining Time in seconds
+ int getJobRemainingTime(SequenceJob *job);
+
+ /* Capture */
+ double seqExpose;
+ int seqTotalCount;
+ int seqCurrentCount;
+ int seqDelay;
+ int retries;
+ QTimer *seqTimer;
+ QString seqPrefix;
+ int nextSequenceID;
+ int seqFileCount;
+ bool isBusy;
+
+ //int calibrationState;
+ bool useGuideHead;
+
+ QString targetName;
+
+ SequenceJob *activeJob;
+
+ QList<ISD::CCD *> CCDs;
+
+ ISD::CCDChip *targetChip;
+ ISD::CCDChip *guideChip;
+
+ // They're generic GDInterface because they could be either ISD::CCD or \
ISD::Filter + QList<ISD::GDInterface *> Filters;
+
+ QList<SequenceJob *> jobs;
+
+ ISD::Telescope *currentTelescope;
+ ISD::CCD *currentCCD;
+ ISD::GDInterface *currentFilter;
+ ISD::DustCap *dustCap;
+ ISD::LightBox *lightBox;
+ ISD::Dome *dome;
+
+ ITextVectorProperty *filterName;
+ INumberVectorProperty *filterSlot;
+
+ QStringList logText;
+ QUrl sequenceURL;
+ bool mDirty;
+ bool jobUnderEdit;
+ int currentFilterPosition;
+ QProgressIndicator *pi;
+
+ // Guide Deviation
+ bool deviationDetected;
+ bool spikeDetected;
+
+ // Dither
+ bool guideDither;
+ //bool isAutoGuiding;
+
+ // Autofocus
+ bool isAutoFocus;
+ bool autoFocusStatus;
+ bool firstAutoFocus;
+ double focusHFR; // HFR value as received from the Ekos focus \
module + double fileHFR; // HFR value as loaded from the \
sequence file +
+ //Meridan flip
+ double initialHA;
+ double initialRA;
+ bool resumeAlignmentAfterFlip;
+ bool resumeGuidingAfterFlip;
+ MFStage meridianFlipStage;
+
+ // Flat field automation
+ /*double ExpRaw1, ExpRaw2;
+ double ADURaw1, ADURaw2;
+ double ADUSlope;*/
+ QList<double> ExpRaw;
+ QList<double> ADURaw;
+ double targetADU;
+ SkyPoint wallCoord;
+ bool preMountPark, preDomePark;
+ FlatFieldDuration flatFieldDuration;
+ FlatFieldSource flatFieldSource;
+ CalibrationStage calibrationStage;
+ bool dustCapLightEnabled, lightBoxLightEnabled;
+
+ QUrl dirPath;
+
+ // Misc
+ bool ignoreJobProgress;
+
+ // State
+ CaptureState state;
+ FocusState focusState;
+ GuideState guideState;
+ AlignState alignState;
+
+ PauseFunctionPointer pauseFunction;
+
+ // CCD Chip frame settings
+ QMap<ISD::CCDChip *, QVariantMap> frameSettings;
+
+ // Temporary Only
+ QProcess postCaptureScript;
+
+
+};
+
+}
+
+#endif // CAPTURE_H
diff --git a/kstars/ekos/capture/capture.ui b/kstars/ekos/capture/capture.ui
new file mode 100644
index 0000000..ff64248
--- /dev/null
+++ b/kstars/ekos/capture/capture.ui
@@ -0,0 +1,1602 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Capture</class>
+ <widget class="QWidget" name="Capture">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>728</width>
+ <height>413</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_11">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="CCDFWGroup">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="title">
+ <string>CCD && Filter Wheel</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="textLabel1_6">
+ <property name="toolTip">
+ <string/>
+ </property>
+ <property name="whatsThis">
+ <string/>
+ </property>
+ <property name="text">
+ <string>CCD:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="CCDCaptureCombo"/>
+ </item>
+ <item row="0" column="2">
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="textLabel1_5_3">
+ <property name="toolTip">
+ <string>Filter Wheel</string>
+ </property>
+ <property name="whatsThis">
+ <string/>
+ </property>
+ <property name="text">
+ <string>FW:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="FilterCaptureCombo">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="textLabel1_2_3">
+ <property name="toolTip">
+ <string>Number of images to capture</string>
+ </property>
+ <property name="whatsThis">
+ <string/>
+ </property>
+ <property name="text">
+ <string>Filter:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="FilterPosCombo">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="exposureLabel_2">
+ <property name="toolTip">
+ <string>Set the exposure time in seconds for individual images, if \
applicable</string> + </property>
+ <property name="whatsThis">
+ <string/>
+ </property>
+ <property name="text">
+ <string>Exposure:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QDoubleSpinBox" name="exposureIN">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="decimals">
+ <number>3</number>
+ </property>
+ <property name="minimum">
+ <double>0.001000000000000</double>
+ </property>
+ <property name="maximum">
+ <double>3600.000000000000000</double>
+ </property>
+ <property name="value">
+ <double>1.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="textLabel1_9">
+ <property name="toolTip">
+ <string>Horizontal and Vertical binning</string>
+ </property>
+ <property name="whatsThis">
+ <string/>
+ </property>
+ <property name="text">
+ <string>Binning:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="textLabel1_10">
+ <property name="toolTip">
+ <string/>
+ </property>
+ <property name="whatsThis">
+ <string/>
+ </property>
+ <property name="text">
+ <string>X:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="binXIN">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Horizontal binning</string>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>10</number>
+ </property>
+ <property name="value">
+ <number>1</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="textLabel1_11">
+ <property name="toolTip">
+ <string/>
+ </property>
+ <property name="whatsThis">
+ <string/>
+ </property>
+ <property name="text">
+ <string>Y:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="binYIN">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Vertical binning</string>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>10</number>
+ </property>
+ <property name="value">
+ <number>1</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="textLabel1_12">
+ <property name="toolTip">
+ <string/>
+ </property>
+ <property name="whatsThis">
+ <string/>
+ </property>
+ <property name="text">
+ <string>Frame:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_9">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="textLabel1_13">
+ <property name="toolTip">
+ <string/>
+ </property>
+ <property name="whatsThis">
+ <string/>
+ </property>
+ <property name="text">
+ <string>X:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="frameXIN">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="textLabel1_14">
+ <property name="toolTip">
+ <string/>
+ </property>
+ <property name="whatsThis">
+ <string/>
+ </property>
+ <property name="text">
+ <string>Y:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="frameYIN">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="2">
+ <layout class="QHBoxLayout" name="horizontalLayout_10">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="textLabel1_15">
+ <property name="toolTip">
+ <string/>
+ </property>
+ <property name="whatsThis">
+ <string/>
+ </property>
+ <property name="text">
+ <string>W:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="frameWIN">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximum">
+ <number>99</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="textLabel1_16">
+ <property name="toolTip">
+ <string/>
+ </property>
+ <property name="whatsThis">
+ <string/>
+ </property>
+ <property name="text">
+ <string>H:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="frameHIN">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="resetFrameB">
+ <property name="minimumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string>Reset CCD frame values to default values</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="textLabel1_17">
+ <property name="toolTip">
+ <string/>
+ </property>
+ <property name="whatsThis">
+ <string/>
+ </property>
+ <property name="text">
+ <string>Type:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QComboBox" name="frameTypeCombo">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="2">
+ <layout class="QHBoxLayout" name="horizontalLayout_12">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="temperatureCheck">
+ <property name="toolTip">
+ <string><html><head/><body><p>Enforce \
temperature value before capturing an \
image</p></body></html></string> + </property>
+ <property name="text">
+ <string>T º</string>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="temperatureOUT">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDoubleSpinBox" name="temperatureIN">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="setTemperatureB">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="toolTip">
+ <string>Set CCD temperature</string>
+ </property>
+ <property name="text">
+ <string>Set</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="calibrationB">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string>Calibration Options</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="textLabel1_7">
+ <property name="toolTip">
+ <string>Prefix to append to the beginning of file names</string>
+ </property>
+ <property name="whatsThis">
+ <string/>
+ </property>
+ <property name="text">
+ <string>Prefix:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QLineEdit" name="prefixIN"/>
+ </item>
+ <item row="4" column="2">
+ <layout class="QHBoxLayout" name="horizontalLayout_16">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="filterCheck">
+ <property name="toolTip">
+ <string>Append the active filter slot to the prefix</string>
+ </property>
+ <property name="text">
+ <string>Filter</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="expDurationCheck">
+ <property name="toolTip">
+ <string>Append the expose duration to the prefix</string>
+ </property>
+ <property name="text">
+ <string>Duration</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="ISOCheck">
+ <property name="toolTip">
+ <string>Append time stamp to the prefix</string>
+ </property>
+ <property name="text">
+ <string>TS</string>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item row="5" column="0">
+ <widget class="QLabel" name="textLabel1_4">
+ <property name="toolTip">
+ <string>Number of images to capture</string>
+ </property>
+ <property name="whatsThis">
+ <string/>
+ </property>
+ <property name="text">
+ <string>Count:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <widget class="QSpinBox" name="countIN">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>999</number>
+ </property>
+ <property name="value">
+ <number>1</number>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="2">
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="textLabel1_8">
+ <property name="toolTip">
+ <string>Delay in seconds between consecutive images</string>
+ </property>
+ <property name="whatsThis">
+ <string/>
+ </property>
+ <property name="text">
+ <string>Delay:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="delayIN">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximum">
+ <number>3600</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="6" column="0">
+ <widget class="QLabel" name="label_8">
+ <property name="toolTip">
+ <string>Apply filter to image after capture to enhance it</string>
+ </property>
+ <property name="text">
+ <string>Filters:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="1">
+ <widget class="QComboBox" name="filterCombo">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <item>
+ <property name="text">
+ <string>--</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="6" column="2">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="ISOLabel">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>ISO:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="ISOCombo">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_8">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="textLabel1_18">
+ <property name="toolTip">
+ <string>Directory to save sequence images</string>
+ </property>
+ <property name="whatsThis">
+ <string/>
+ </property>
+ <property name="text">
+ <string>Directory:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="fitsDir"/>
+ </item>
+ <item>
+ <widget class="QPushButton" name="selectFITSDirB">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>28</width>
+ <height>28</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>28</width>
+ <height>28</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="SequenceQueue">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Sequence Queue</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_8">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_14">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="addToQueueB">
+ <property name="minimumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <attribute name="buttonGroup">
+ <string notr="true">queueEditButtonGroup</string>
+ </attribute>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="removeFromQueueB">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <attribute name="buttonGroup">
+ <string notr="true">queueEditButtonGroup</string>
+ </attribute>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="queueUpB">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <attribute name="buttonGroup">
+ <string notr="true">queueEditButtonGroup</string>
+ </attribute>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="queueDownB">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <attribute name="buttonGroup">
+ <string notr="true">queueEditButtonGroup</string>
+ </attribute>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="resetB">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string>Reset status of all jobs</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <attribute name="buttonGroup">
+ <string notr="true">queueEditButtonGroup</string>
+ </attribute>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="queueLoadB">
+ <property name="minimumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <attribute name="buttonGroup">
+ <string notr="true">queueEditButtonGroup</string>
+ </attribute>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="queueSaveB">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <attribute name="buttonGroup">
+ <string notr="true">queueEditButtonGroup</string>
+ </attribute>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="queueSaveAsB">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <attribute name="buttonGroup">
+ <string notr="true">queueEditButtonGroup</string>
+ </attribute>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QTableWidget" name="queueTable">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAsNeeded</enum>
+ </property>
+ <attribute name="horizontalHeaderDefaultSectionSize">
+ <number>85</number>
+ </attribute>
+ <attribute name="horizontalHeaderMinimumSectionSize">
+ <number>30</number>
+ </attribute>
+ <column>
+ <property name="text">
+ <string>Status</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Filter</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Type</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Bin</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Exp</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>ISO</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Count</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_7">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="previewB">
+ <property name="toolTip">
+ <string/>
+ </property>
+ <property name="text">
+ <string>Preview</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="buttonSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>18</width>
+ <height>21</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="startB">
+ <property name="minimumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string>Start Sequence</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="autoDefault">
+ <bool>true</bool>
+ </property>
+ <property name="default">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pauseB">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string>Pause Sequence</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="autoDefault">
+ <bool>true</bool>
+ </property>
+ <property name="default">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_13">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="optionsGroup">
+ <property name="title">
+ <string>Options</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="darkSubCheck">
+ <property name="toolTip">
+ <string>Perform automatic dark subtraction in preview mode</string>
+ </property>
+ <property name="text">
+ <string>Auto dark subtract</string>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="guideDeviationCheck">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="toolTip">
+ <string>Abort sequence if guiding deviation exceed this value</string>
+ </property>
+ <property name="text">
+ <string>Guiding Deviation <</string>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QDoubleSpinBox" name="guideDeviation">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="decimals">
+ <number>2</number>
+ </property>
+ <property name="maximum">
+ <double>30.000000000000000</double>
+ </property>
+ <property name="singleStep">
+ <double>0.500000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="textLabel1_2_4">
+ <property name="toolTip">
+ <string>Number of images to capture</string>
+ </property>
+ <property name="whatsThis">
+ <string/>
+ </property>
+ <property name="text">
+ <string>"</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="6">
+ <widget class="QLabel" name="textLabel1_2_5">
+ <property name="toolTip">
+ <string>Number of images to capture</string>
+ </property>
+ <property name="whatsThis">
+ <string/>
+ </property>
+ <property name="text">
+ <string>pixels</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="meridianCheck">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="toolTip">
+ <string><html><head/><body><p>Command a \
meridian flip if the hour angle exceeds the specified value. Capture and Guiding will \
be suspended and resumed after the flip is \
complete.</p></body></html></string> + </property>
+ <property name="text">
+ <string>Meridian Flip if HA ></string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="4" colspan="2">
+ <widget class="QCheckBox" name="parkCheck">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="toolTip">
+ <string>Automatically park telescope once all sequence jobs are \
completed</string> + </property>
+ <property name="text">
+ <string>Park When Complete</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="5">
+ <widget class="QDoubleSpinBox" name="HFRPixels">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="decimals">
+ <number>3</number>
+ </property>
+ <property name="minimum">
+ <double>0.000000000000000</double>
+ </property>
+ <property name="maximum">
+ <double>10.000000000000000</double>
+ </property>
+ <property name="singleStep">
+ <double>0.100000000000000</double>
+ </property>
+ <property name="value">
+ <double>0.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QLabel" name="textLabel1_2_6">
+ <property name="toolTip">
+ <string>Number of images to capture</string>
+ </property>
+ <property name="whatsThis">
+ <string/>
+ </property>
+ <property name="text">
+ <string>hours</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QDoubleSpinBox" name="meridianHours">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="decimals">
+ <number>2</number>
+ </property>
+ <property name="minimum">
+ <double>0.000000000000000</double>
+ </property>
+ <property name="maximum">
+ <double>3.000000000000000</double>
+ </property>
+ <property name="singleStep">
+ <double>0.100000000000000</double>
+ </property>
+ <property name="value">
+ <double>0.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="4">
+ <widget class="QCheckBox" name="autofocusCheck">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="toolTip">
+ <string>Perform autofocusing once Half-Flux-Radius (HFR) value exceeds \
this limit</string> + </property>
+ <property name="text">
+ <string>Autofocus if HFR ></string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3">
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="Line" name="line_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="progressGroup">
+ <property name="title">
+ <string>Progress</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_7">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <layout class="QGridLayout" name="progressLayout">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="progressLabel_3">
+ <property name="text">
+ <string>Expose:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="exposeOUT">
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::Box</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2" colspan="2">
+ <widget class="QLabel" name="secondsLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>85</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>85</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string notr="true">second left</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="progressLabel_2">
+ <property name="text">
+ <string>Progress:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="currentImgCountOUT">
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::Box</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QLabel" name="textLabel1_4_2_3">
+ <property name="text">
+ <string>of</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QLabel" name="fullImgCountOUT">
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::Box</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="4">
+ <widget class="QLabel" name="completedLabel_2">
+ <property name="text">
+ <string>completed</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="progressSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>51</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QProgressBar" name="imgProgress">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>1</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>2</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ <zorder>verticalSpacer</zorder>
+ </widget>
+ <resources/>
+ <connections/>
+ <buttongroups>
+ <buttongroup name="queueEditButtonGroup">
+ <property name="exclusive">
+ <bool>false</bool>
+ </property>
+ </buttongroup>
+ </buttongroups>
+</ui>
diff --git a/kstars/ekos/capture/sequencejob.cpp \
b/kstars/ekos/capture/sequencejob.cpp new file mode 100644
index 0000000..b5b3215
--- /dev/null
+++ b/kstars/ekos/capture/sequencejob.cpp
@@ -0,0 +1,422 @@
+/* Ekos
+ Copyright (C) 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
+
+ This application is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+ */
+
+#include <KMessageBox>
+#include <KDirWatch>
+#include <KLocalizedString>
+#include <KNotifications/KNotification>
+
+
+#include "sequencejob.h"
+#include "Options.h"
+
+#include "kstars.h"
+#include "kstarsdata.h"
+
+#include "ui_calibrationoptions.h"
+
+#define INVALID_TEMPERATURE 10000
+#define INVALID_HA 10000
+#define MF_TIMER_TIMEOUT 90000
+#define MF_RA_DIFF_LIMIT 4
+#define MAX_CAPTURE_RETRIES 3
+
+namespace Ekos
+{
+
+SequenceJob::SequenceJob()
+{
+ statusStrings = QStringList() << i18n("Idle") << i18n("In Progress") << \
i18n("Error") << i18n("Aborted") << i18n("Complete"); + status = JOB_IDLE;
+ exposure=count=delay=frameType=targetFilter=isoIndex=-1;
+ currentTemperature=targetTemperature=INVALID_TEMPERATURE;
+ captureFilter=FITS_NONE;
+ preview=false;
+ filterReady=temperatureReady=filterPostFocusReady=prepareReady=true;
+ enforceTemperature=false;
+ activeChip=NULL;
+ activeCCD=NULL;
+ activeFilter= NULL;
+ statusCell = NULL;
+ completed=0;
+ captureRetires=0;
+
+ calibrationSettings.flatFieldSource = SOURCE_MANUAL;
+ calibrationSettings.flatFieldDuration = DURATION_MANUAL;
+ calibrationSettings.targetADU = 0;
+ calibrationSettings.preMountPark = false;
+ calibrationSettings.preDomePark = false;
+
+ typePrefixEnabled = false;
+ filterPrefixEnabled = false;
+ expPrefixEnabled = false;
+ timeStampPrefixEnabled= false;
+}
+
+void SequenceJob::reset()
+{
+ // Reset to default values
+ activeChip->setBatchMode(false);
+}
+
+void SequenceJob::resetStatus()
+{
+ status = JOB_IDLE;
+ completed=0;
+ exposeLeft=0;
+ captureRetires=0;
+ if (preview == false && statusCell)
+ statusCell->setText(statusStrings[status]);
+}
+
+void SequenceJob::abort()
+{
+ status = JOB_ABORTED;
+ if (preview == false && statusCell)
+ statusCell->setText(statusStrings[status]);
+ if (activeChip->canAbort())
+ activeChip->abortExposure();
+ activeChip->setBatchMode(false);
+}
+
+void SequenceJob::done()
+{
+ status = JOB_DONE;
+
+ if (statusCell)
+ statusCell->setText(statusStrings[status]);
+
+}
+
+void SequenceJob::prepareCapture()
+{
+ prepareReady=false;
+
+ activeChip->setBatchMode(!preview);
+
+ activeCCD->setFITSDir(fitsDir);
+
+ activeCCD->setISOMode(timeStampPrefixEnabled);
+
+ activeCCD->setSeqPrefix(fullPrefix);
+
+ if (activeChip->isBatchMode())
+ activeCCD->updateUploadSettings();
+
+ if (isoIndex != -1)
+ {
+ if (isoIndex != activeChip->getISOIndex())
+ activeChip->setISOIndex(isoIndex);
+ }
+
+ if (frameType == FRAME_DARK || frameType == FRAME_BIAS)
+ filterReady = true;
+ else if (targetFilter != -1 && activeFilter != NULL)
+ {
+ if (targetFilter == currentFilter)
+ filterReady = true;
+ else
+ {
+ filterReady = false;
+
+ // Post Focus on Filter change. If frame is NOT light, then we do not \
perform autofocusing on filter change + filterPostFocusReady = \
(!Options::autoFocusOnFilterChange() || frameType != FRAME_LIGHT); +
+ activeFilter->runCommand(INDI_SET_FILTER, &targetFilter);
+ }
+ }
+
+ if (enforceTemperature && fabs(targetTemperature - currentTemperature) > \
Options::maxTemperatureDiff()) + {
+ temperatureReady = false;
+ activeCCD->setTemperature(targetTemperature);
+ }
+
+ if (prepareReady == false && temperatureReady && filterReady)
+ {
+ prepareReady = true;
+ emit prepareComplete();
+ }
+
+}
+
+//SequenceJob::CAPTUREResult SequenceJob::capture(bool isDark)
+SequenceJob::CAPTUREResult SequenceJob::capture(bool noCaptureFilter)
+{
+ // If focusing is busy, return error
+ //if (activeChip->getCaptureMode() == FITS_FOCUS)
+ // return CAPTURE_FOCUS_ERROR;
+
+ activeChip->setBatchMode(!preview);
+
+ if (targetFilter != -1 && activeFilter != NULL)
+ {
+ if (targetFilter != currentFilter)
+ {
+ activeFilter->runCommand(INDI_SET_FILTER, &targetFilter);
+ return CAPTURE_FILTER_BUSY;
+ }
+ }
+
+ if (isoIndex != -1)
+ {
+ if (isoIndex != activeChip->getISOIndex())
+ activeChip->setISOIndex(isoIndex);
+ }
+
+ if ((w > 0 && h > 0) && activeChip->canSubframe() && activeChip->setFrame(x, y, \
w, h) == false) + {
+ status = JOB_ERROR;
+
+ if (preview == false && statusCell)
+ statusCell->setText(statusStrings[status]);
+
+ return CAPTURE_FRAME_ERROR;
+
+ }
+
+ if (activeChip->canBin() && activeChip->setBinning(binX, binY) == false)
+ {
+ status = JOB_ERROR;
+
+ if (preview == false && statusCell)
+ statusCell->setText(statusStrings[status]);
+
+ return CAPTURE_BIN_ERROR;
+ }
+
+ activeChip->setFrameType(frameTypeName);
+ activeChip->setCaptureMode(FITS_NORMAL);
+
+ if (noCaptureFilter)
+ activeChip->setCaptureFilter(FITS_NONE);
+ else
+ activeChip->setCaptureFilter(captureFilter);
+
+ // If filter is different that CCD, send the filter info
+ if (activeFilter && activeFilter != activeCCD)
+ activeCCD->setFilter(filter);
+
+ status = JOB_BUSY;
+
+ if (preview == false && statusCell)
+ statusCell->setText(statusStrings[status]);
+
+ exposeLeft = exposure;
+
+ activeChip->capture(exposure);
+
+ return CAPTURE_OK;
+
+}
+
+void SequenceJob::setTargetFilter(int pos, const QString & name)
+{
+ targetFilter = pos;
+ filter = name;
+}
+
+void SequenceJob::setFrameType(int type, const QString & name)
+{
+ frameType = type;
+ frameTypeName = name;
+}
+
+double SequenceJob::getExposeLeft() const
+{
+ return exposeLeft;
+}
+
+void SequenceJob::setExposeLeft(double value)
+{
+ exposeLeft = value;
+}
+
+void SequenceJob::setPrefixSettings(const QString &prefix, bool filterEnabled, bool \
exposureEnabled, bool tsEnabled) +{
+ rawPrefix = prefix;
+ filterPrefixEnabled = filterEnabled;
+ expPrefixEnabled = exposureEnabled;
+ timeStampPrefixEnabled = tsEnabled;
+}
+
+void SequenceJob::getPrefixSettings(QString &prefix, bool &filterEnabled, bool \
&exposureEnabled, bool &tsEnabled) +{
+ prefix = rawPrefix;
+
+ filterEnabled = filterPrefixEnabled;
+ exposureEnabled = expPrefixEnabled;
+ tsEnabled = timeStampPrefixEnabled;
+}
+double SequenceJob::getCurrentTemperature() const
+{
+ return currentTemperature;
+}
+
+void SequenceJob::setCurrentTemperature(double value)
+{
+ currentTemperature = value;
+
+ if (enforceTemperature == false || fabs(targetTemperature - currentTemperature) \
<= Options::maxTemperatureDiff()) + temperatureReady = true;
+
+ if (prepareReady == false && filterReady && temperatureReady && \
filterPostFocusReady && (status == JOB_IDLE || status == JOB_ABORTED)) + {
+ prepareReady = true;
+ emit prepareComplete();
+ }
+}
+
+double SequenceJob::getTargetTemperature() const
+{
+ return targetTemperature;
+}
+
+void SequenceJob::setTargetTemperature(double value)
+{
+ targetTemperature = value;
+}
+
+double SequenceJob::getTargetADU() const
+{
+ return calibrationSettings.targetADU;
+}
+
+void SequenceJob::setTargetADU(double value)
+{
+ calibrationSettings.targetADU = value;
+}
+int SequenceJob::getCaptureRetires() const
+{
+ return captureRetires;
+}
+
+void SequenceJob::setCaptureRetires(int value)
+{
+ captureRetires = value;
+}
+
+FlatFieldSource SequenceJob::getFlatFieldSource() const
+{
+ return calibrationSettings.flatFieldSource;
+}
+
+void SequenceJob::setFlatFieldSource(const FlatFieldSource &value)
+{
+ calibrationSettings.flatFieldSource = value;
+}
+
+FlatFieldDuration SequenceJob::getFlatFieldDuration() const
+{
+ return calibrationSettings.flatFieldDuration;
+}
+
+void SequenceJob::setFlatFieldDuration(const FlatFieldDuration &value)
+{
+ calibrationSettings.flatFieldDuration = value;
+}
+
+SkyPoint SequenceJob::getWallCoord() const
+{
+ return calibrationSettings.wallCoord;
+}
+
+void SequenceJob::setWallCoord(const SkyPoint &value)
+{
+ calibrationSettings.wallCoord = value;
+}
+
+bool SequenceJob::isPreMountPark() const
+{
+ return calibrationSettings.preMountPark;
+}
+
+void SequenceJob::setPreMountPark(bool value)
+{
+ calibrationSettings.preMountPark = value;
+}
+
+bool SequenceJob::isPreDomePark() const
+{
+ return calibrationSettings.preDomePark;
+}
+
+void SequenceJob::setPreDomePark(bool value)
+{
+ calibrationSettings.preDomePark = value;
+}
+
+bool SequenceJob::getEnforceTemperature() const
+{
+ return enforceTemperature;
+}
+
+void SequenceJob::setEnforceTemperature(bool value)
+{
+ enforceTemperature = value;
+}
+
+QString SequenceJob::getRootFITSDir() const
+{
+ return rootFITSDir;
+}
+
+void SequenceJob::setRootFITSDir(const QString &value)
+{
+ rootFITSDir = value;
+}
+
+bool SequenceJob::getFilterPostFocusReady() const
+{
+ return filterPostFocusReady;
+}
+
+void SequenceJob::setFilterPostFocusReady(bool value)
+{
+ filterPostFocusReady = value;
+
+ if (prepareReady == false && filterPostFocusReady && filterReady && \
temperatureReady && (status == JOB_IDLE || status == JOB_ABORTED)) + {
+ prepareReady = true;
+ emit prepareComplete();
+ }
+}
+
+int SequenceJob::getISOIndex() const
+{
+ return isoIndex;
+}
+
+void SequenceJob::setISOIndex(int value)
+{
+ isoIndex = value;
+}
+
+int SequenceJob::getCurrentFilter() const
+{
+ return currentFilter;
+}
+
+void SequenceJob::setCurrentFilter(int value)
+{
+ currentFilter = value;
+
+ if (currentFilter == targetFilter)
+ filterReady = true;
+
+ if (prepareReady == false && filterReady && temperatureReady && \
filterPostFocusReady && (status == JOB_IDLE || status == JOB_ABORTED)) + {
+ prepareReady = true;
+ emit prepareComplete();
+ }
+ else if (filterReady && filterPostFocusReady == false)
+ emit checkFocus();
+}
+
+}
diff --git a/kstars/ekos/capture/sequencejob.h b/kstars/ekos/capture/sequencejob.h
new file mode 100644
index 0000000..d30d5fd
--- /dev/null
+++ b/kstars/ekos/capture/sequencejob.h
@@ -0,0 +1,206 @@
+/* Ekos Capture tool
+ Copyright (C) 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
+
+ This application is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+ */
+
+#ifndef SEQUENCEJOB_H
+#define SEQUENCEJOB_H
+
+#include <QTableWidgetItem>
+
+#include "indi/indistd.h"
+#include "indi/indiccd.h"
+
+/**
+ *@class SequenceJob
+ *@short Sequence Job is a container for the details required to capture a series of \
images. + *@author Jasem Mutlaq
+ *@version 1.0
+ */
+namespace Ekos
+{
+
+class SequenceJob : public QObject
+{
+ Q_OBJECT
+
+ public:
+
+ typedef enum { JOB_IDLE, JOB_BUSY, JOB_ERROR, JOB_ABORTED, JOB_DONE } JOBStatus;
+ typedef enum { CAPTURE_OK, CAPTURE_FRAME_ERROR, CAPTURE_BIN_ERROR, \
CAPTURE_FILTER_BUSY, CAPTURE_FOCUS_ERROR} CAPTUREResult; +
+ SequenceJob();
+ ~SequenceJob() {}
+
+ CAPTUREResult capture(bool noCaptureFilter);
+ void reset();
+ void abort();
+ void done();
+ void prepareCapture();
+
+ JOBStatus getStatus() { return status; }
+ const QString & getStatusString() { return statusStrings[status]; }
+ bool isPreview() { return preview;}
+ int getDelay() { return delay;}
+ int getCount() { return count;}
+ unsigned int getCompleted() { return completed; }
+ const QString & getPrefix() { return fullPrefix;}
+ const QString & getRawPrefix() { return rawPrefix;}
+ double getExposure() const { return exposure;}
+
+ void setActiveCCD(ISD::CCD *ccd) { activeCCD = ccd; }
+ ISD::CCD *getActiveCCD() { return activeCCD;}
+
+ void setActiveFilter(ISD::GDInterface *filter) { activeFilter = filter; }
+ ISD::GDInterface *getActiveFilter() { return activeFilter;}
+
+ void setActiveChip(ISD::CCDChip *chip) { activeChip = chip; }
+ ISD::CCDChip *getActiveChip() { return activeChip;}
+
+ void setFITSDir(const QString &dir) { fitsDir = dir;}
+ const QString & getFITSDir() { return fitsDir; }
+
+ void setTargetFilter(int pos, const QString & name);
+ int getTargetFilter() { return targetFilter;}
+ int getCurrentFilter() const;
+ void setCurrentFilter(int value);
+
+ const QString &getFilterName() { return filter; }
+
+ void setFrameType(int type, const QString & name);
+ int getFrameType() { return frameType;}
+
+ void setCaptureFilter(FITSScale capFilter) { captureFilter = capFilter; }
+ FITSScale getCaptureFilter() { return captureFilter;}
+
+ void setPreview(bool enable) { preview = enable; }
+ void setFullPrefix(const QString &cprefix) { fullPrefix = cprefix;}
+ void setFrame(int in_x, int in_y, int in_w, int in_h) { x=in_x; y=in_y; w=in_w; \
h=in_h; } +
+ int getSubX() { return x;}
+ int getSubY() { return y;}
+ int getSubW() { return w;}
+ int getSubH() { return h;}
+
+ void setBin(int xbin, int ybin) { binX = xbin; binY=ybin;}
+ int getXBin() { return binX; }
+ int getYBin() { return binY; }
+
+ void setDelay(int in_delay) { delay = in_delay; }
+ void setCount(int in_count) { count = in_count;}
+ void setExposure(double duration) { exposure = duration;}
+ void setStatusCell(QTableWidgetItem *cell) { statusCell = cell; }
+ void setCompleted(unsigned int in_completed) { completed = in_completed;}
+ int getISOIndex() const;
+ void setISOIndex(int value);
+
+ double getExposeLeft() const;
+ void setExposeLeft(double value);
+ void resetStatus();
+ void setPrefixSettings(const QString &fullPrefix, bool filterEnabled, bool \
exposureEnabled, bool tsEnabled); + void getPrefixSettings(QString &fullPrefix, \
bool &filterEnabled, bool &exposureEnabled, bool &tsEnabled); +
+ bool isFilterPrefixEnabled() { return filterPrefixEnabled; }
+ bool isExposurePrefixEnabled() { return expPrefixEnabled; }
+ bool isTimestampPrefixEnabled() { return timeStampPrefixEnabled;}
+
+ double getCurrentTemperature() const;
+ void setCurrentTemperature(double value);
+
+ double getTargetTemperature() const;
+ void setTargetTemperature(double value);
+
+ double getTargetADU() const;
+ void setTargetADU(double value);
+
+ int getCaptureRetires() const;
+ void setCaptureRetires(int value);
+
+ FlatFieldSource getFlatFieldSource() const;
+ void setFlatFieldSource(const FlatFieldSource &value);
+
+ FlatFieldDuration getFlatFieldDuration() const;
+ void setFlatFieldDuration(const FlatFieldDuration &value);
+
+ SkyPoint getWallCoord() const;
+ void setWallCoord(const SkyPoint &value);
+
+ bool isPreMountPark() const;
+ void setPreMountPark(bool value);
+
+ bool isPreDomePark() const;
+ void setPreDomePark(bool value);
+
+ bool getEnforceTemperature() const;
+ void setEnforceTemperature(bool value);
+
+ QString getRootFITSDir() const;
+ void setRootFITSDir(const QString &value);
+
+ bool getFilterPostFocusReady() const;
+ void setFilterPostFocusReady(bool value);
+
+signals:
+ void prepareComplete();
+ void checkFocus();
+
+private:
+
+ QStringList statusStrings;
+ ISD::CCDChip *activeChip;
+ ISD::CCD *activeCCD;
+ ISD::GDInterface *activeFilter;
+
+ double exposure;
+ int frameType;
+ QString frameTypeName;
+ int targetFilter;
+ int currentFilter;
+
+ QString filter;
+ int binX, binY;
+ int x,y,w,h;
+ QString fullPrefix;
+ int count;
+ int delay;
+ bool preview;
+ bool filterReady, temperatureReady, filterPostFocusReady, prepareReady;
+ bool enforceTemperature;
+ int isoIndex;
+ int captureRetires;
+ unsigned int completed;
+ double exposeLeft;
+ double currentTemperature, targetTemperature;
+ FITSScale captureFilter;
+ QTableWidgetItem *statusCell;
+ QString fitsDir;
+ QString rootFITSDir;
+
+ bool typePrefixEnabled, filterPrefixEnabled, expPrefixEnabled, \
timeStampPrefixEnabled; + QString rawPrefix;
+
+ JOBStatus status;
+
+ // Flat field variables
+ struct
+ {
+ double targetADU;
+ FlatFieldSource flatFieldSource;
+ FlatFieldDuration flatFieldDuration;
+ SkyPoint wallCoord;
+ bool preMountPark;
+ bool preDomePark;
+
+ } calibrationSettings;
+
+
+
+};
+
+}
+
+#endif // SEQUENCEJOB_H
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic