9 #include "framingassistantui.h"
10 #include "ui_framingassistant.h"
11 #include "mosaiccomponent.h"
12 #include "mosaictiles.h"
15 #include "scheduler.h"
17 #include "ekos/manager.h"
18 #include "projections/projector.h"
19 #include "skymapcomposite.h"
21 #include "ekos_scheduler_debug.h"
26 FramingAssistantUI::FramingAssistantUI():
QDialog(
KStars::Instance()), ui(new Ui::FramingAssistant())
30 auto tiles = KStarsData::Instance()->
skyComposite()->mosaicComponent()->tiles();
32 ui->raBox->setUnits(dmsBox::HOURS);
35 ui->focalLenSpin->setValue(Options::telescopeFocalLength());
36 ui->pixelWSizeSpin->setValue(Options::cameraPixelWidth());
37 ui->pixelHSizeSpin->setValue(Options::cameraPixelHeight());
38 ui->cameraWSpin->setValue(Options::cameraWidth());
39 ui->cameraHSpin->setValue(Options::cameraHeight());
41 ui->positionAngleSpin->setValue(tiles->positionAngle());
42 ui->sequenceEdit->setText(tiles->sequenceFile());
43 ui->directoryEdit->setText(tiles->outputDirectory());
44 ui->targetEdit->setText(tiles->targetName());
45 ui->focusEvery->setValue(tiles->focusEveryN());
46 ui->alignEvery->setValue(tiles->alignEveryN());
47 ui->trackStepCheck->setChecked(tiles->isTrackChecked());
48 ui->focusStepCheck->setChecked(tiles->isFocusChecked());
49 ui->alignStepCheck->setChecked(tiles->isAlignChecked());
50 ui->guideStepCheck->setChecked(tiles->isGuideChecked());
51 ui->mosaicWSpin->setValue(tiles->gridSize().width());
52 ui->mosaicHSpin->setValue(tiles->gridSize().height());
53 ui->overlapSpin->setValue(tiles->overlap());
55 if (tiles->operationMode() == MosaicTiles::MODE_OPERATION)
57 m_CenterPoint = *tiles.data();
63 m_CenterPoint = *SkyMap::Instance()->
focus();
65 m_CenterPoint.
setRA0(J2000Coords.ra0());
66 m_CenterPoint.setDec0(J2000Coords.dec0());
69 m_CenterPoint.updateCoordsNow(KStarsData::Instance()->updateNum());
70 ui->raBox->show(m_CenterPoint.ra0());
71 ui->decBox->show(m_CenterPoint.dec0());
76 ui->stackedWidget->setCurrentIndex(PAGE_EQUIPMENT);
80 if (Ekos::Manager::Instance()->ekosStatus() == Ekos::Success)
82 ui->goSolveB->setEnabled(
true);
83 connect(Ekos::Manager::Instance()->mountModule(), &
Ekos::Mount::newStatus,
this, &Ekos::FramingAssistantUI::setMountState,
85 connect(Ekos::Manager::Instance()->alignModule(), &Ekos::Align::newStatus,
this, &Ekos::FramingAssistantUI::setAlignState,
88 connect(Ekos::Manager::Instance(), &Ekos::Manager::ekosStatusChanged,
this, [
this](Ekos::CommunicationStatus status)
90 ui->goSolveB->setEnabled(status == Ekos::Success);
93 if (status == Ekos::Success)
95 connect(Ekos::Manager::Instance()->mountModule(), &
Ekos::Mount::newStatus,
this, &Ekos::FramingAssistantUI::setMountState,
97 connect(Ekos::Manager::Instance()->alignModule(), &Ekos::Align::newStatus,
this, &Ekos::FramingAssistantUI::setAlignState,
106 ui->stackedWidget->setCurrentIndex(PAGE_ADJUST_GRID);
110 ui->stackedWidget->setCurrentIndex(PAGE_ADJUST_GRID);
114 ui->stackedWidget->setCurrentIndex(PAGE_SELECT_GRID);
118 ui->stackedWidget->setCurrentIndex(PAGE_SELECT_GRID);
122 ui->stackedWidget->setCurrentIndex(PAGE_CREATE_JOBS);
123 ui->createJobsB->setEnabled(!ui->targetEdit->text().isEmpty() && !ui->sequenceEdit->text().isEmpty() &&
124 !ui->directoryEdit->text().isEmpty());
130 m_CenterPoint.setRA0(range24(m_CenterPoint.ra0().Hours() + dRA.
Hours()));
131 m_CenterPoint.setDec0(rangeDec(m_CenterPoint.dec0().Degrees() + dDE.
Degrees()));
132 m_CenterPoint.updateCoordsNow(KStarsData::Instance()->updateNum());
133 ui->raBox->show(m_CenterPoint.ra0());
134 ui->decBox->show(m_CenterPoint.dec0());
136 m_DebounceTimer->start();
142 QString sanitized = ui->targetEdit->text();
143 if (sanitized != i18n(
"unnamed"))
146 sanitized = sanitize(sanitized);
147 ui->targetEdit->blockSignals(true);
148 ui->targetEdit->setText(sanitized);
149 ui->targetEdit->blockSignals(false);
151 if (m_JobsDirectory.isEmpty())
152 ui->directoryEdit->setText(QDir::cleanPath(QDir::homePath() + QDir::separator() + sanitized));
154 ui->directoryEdit->setText(m_JobsDirectory + QDir::separator() + sanitized);
156 ui->createJobsB->setEnabled(!ui->targetEdit->text().isEmpty() && !ui->sequenceEdit->text().isEmpty() &&
157 !ui->directoryEdit->text().isEmpty());
167 m_CenterPoint = *SkyMap::Instance()->focus();
168 auto J2000Coords = m_CenterPoint.catalogueCoord(KStars::Instance()->data()->ut().djd());
169 m_CenterPoint.setRA0(J2000Coords.ra0());
170 m_CenterPoint.setDec0(J2000Coords.dec0());
172 m_CenterPoint.updateCoordsNow(KStarsData::Instance()->updateNum());
173 ui->raBox->show(m_CenterPoint.ra0());
174 ui->decBox->show(m_CenterPoint.dec0());
175 m_DebounceTimer->start();
179 if (tiles->operationMode() == MosaicTiles::MODE_PLANNING && SkyMap::IsFocused())
181 auto sanitized = sanitize(SkyMap::Instance()->focusObject()->
name());
182 if (sanitized !=
i18n(
"unnamed"))
184 ui->targetEdit->setText(sanitized);
186 if (m_JobsDirectory.isEmpty())
189 ui->directoryEdit->setText(m_JobsDirectory +
QDir::separator() + sanitized);
197 if (sanitized !=
i18n(
"unnamed"))
200 sanitized = sanitize(sanitized);
201 ui->targetEdit->setText(sanitized);
203 if (m_JobsDirectory.isEmpty())
206 ui->directoryEdit->setText(m_JobsDirectory +
QDir::separator() + sanitized);
213 m_CenterPoint.setRA0(ui->raBox->createDms());
214 m_CenterPoint.updateCoordsNow(KStarsData::Instance()->updateNum());
215 m_DebounceTimer->start();
221 m_CenterPoint.setDec0(ui->decBox->createDms());
222 m_CenterPoint.updateCoordsNow(KStarsData::Instance()->updateNum());
223 m_DebounceTimer->start();
227 connect(ui->selectJobsDirB, &
QPushButton::clicked,
this, &Ekos::FramingAssistantUI::selectDirectory);
229 ui->transparencySlider->setValue(Options::mosaicTransparencyLevel());
230 ui->transparencySlider->setEnabled(!Options::mosaicTransparencyAuto());
231 tiles->setPainterAlpha(Options::mosaicTransparencyLevel());
234 ui->transparencySlider->setToolTip(QString(
"%1%").arg(v));
235 Options::setMosaicTransparencyLevel(v);
236 auto tiles = KStarsData::Instance()->skyComposite()->mosaicComponent()->tiles();
237 tiles->setPainterAlpha(v);
238 m_DebounceTimer->start();
240 ui->transparencyAuto->setChecked(Options::mosaicTransparencyAuto());
241 tiles->setPainterAlphaAuto(Options::mosaicTransparencyAuto());
244 ui->transparencySlider->setEnabled(!v);
245 Options::setMosaicTransparencyAuto(v);
246 auto tiles = KStarsData::Instance()->skyComposite()->mosaicComponent()->tiles();
247 tiles->setPainterAlphaAuto(v);
249 m_DebounceTimer->start();
253 m_DebounceTimer =
new QTimer(
this);
254 m_DebounceTimer->setSingleShot(
true);
255 m_DebounceTimer->setInterval(500);
256 connect(m_DebounceTimer, &
QTimer::timeout,
this, &Ekos::FramingAssistantUI::constructMosaic);
264 &Ekos::FramingAssistantUI::calculateFOV);
266 &Ekos::FramingAssistantUI::calculateFOV);
268 &Ekos::FramingAssistantUI::calculateFOV);
275 &Ekos::FramingAssistantUI::updateGridFromTargetFOV);
277 &Ekos::FramingAssistantUI::updateGridFromTargetFOV);
279 &Ekos::FramingAssistantUI::updateGridFromTargetFOV);
281 &Ekos::FramingAssistantUI::updateTargetFOVFromGrid);
283 &Ekos::FramingAssistantUI::updateTargetFOVFromGrid);
289 m_DebounceTimer->start();
293 connect(ui->resetB, &
QPushButton::clicked,
this, &Ekos::FramingAssistantUI::updateTargetFOVFromGrid);
298 connect(ui->alignEvery, QOverload<int>::of(&
QSpinBox::valueChanged),
this, &Ekos::FramingAssistantUI::rewordStepEvery);
299 connect(ui->focusEvery, QOverload<int>::of(&
QSpinBox::valueChanged),
this, &Ekos::FramingAssistantUI::rewordStepEvery);
302 if (tiles->operationMode() == MosaicTiles::MODE_PLANNING)
303 fetchINDIInformation();
305 if (isEquipmentValid())
306 ui->stackedWidget->setCurrentIndex(PAGE_SELECT_GRID);
308 tiles->setOperationMode(MosaicTiles::MODE_PLANNING);
311 FramingAssistantUI::~FramingAssistantUI()
313 delete m_DebounceTimer;
316 bool FramingAssistantUI::isEquipmentValid()
const
318 return (ui->focalLenSpin->value() > 0 && ui->cameraWSpin->value() > 0 && ui->cameraHSpin->value() > 0 &&
319 ui->pixelWSizeSpin->value() > 0 && ui->pixelHSizeSpin->value() > 0);
322 double FramingAssistantUI::getTargetWFOV()
const
324 double const xFOV = ui->cameraWFOVSpin->value() * (1 - ui->overlapSpin->value() / 100.0);
325 return ui->cameraWFOVSpin->value() + xFOV * (ui->mosaicWSpin->value() - 1);
328 double FramingAssistantUI::getTargetHFOV()
const
330 double const yFOV = ui->cameraHFOVSpin->value() * (1 - ui->overlapSpin->value() / 100.0);
331 return ui->cameraHFOVSpin->value() + yFOV * (ui->mosaicHSpin->value() - 1);
334 double FramingAssistantUI::getTargetMosaicW()
const
337 if (!isEquipmentValid() || !ui->targetWFOVSpin->value() || ui->targetWFOVSpin->value() <= ui->cameraWFOVSpin->value())
341 double const xFOV = ui->cameraWFOVSpin->value() * (1 - ui->overlapSpin->value() / 100.0);
342 int const tiles = 1 + ceil((ui->targetWFOVSpin->value() - ui->cameraWFOVSpin->value()) / xFOV);
347 double FramingAssistantUI::getTargetMosaicH()
const
350 if (!isEquipmentValid() || !ui->targetHFOVSpin->value() || ui->targetHFOVSpin->value() <= ui->cameraHFOVSpin->value())
354 double const yFOV = ui->cameraHFOVSpin->value() * (1 - ui->overlapSpin->value() / 100.0);
355 int const tiles = 1 + ceil((ui->targetHFOVSpin->value() - ui->cameraHFOVSpin->value()) / yFOV);
360 void FramingAssistantUI::calculateFOV()
362 if (!isEquipmentValid())
365 ui->nextToSelectGridB->setEnabled(
true);
367 ui->targetWFOVSpin->setMinimum(ui->cameraWFOVSpin->value());
368 ui->targetHFOVSpin->setMinimum(ui->cameraHFOVSpin->value());
370 Options::setTelescopeFocalLength(ui->focalLenSpin->value());
371 Options::setCameraPixelWidth(ui->pixelWSizeSpin->value());
372 Options::setCameraPixelHeight(ui->pixelHSizeSpin->value());
373 Options::setCameraWidth(ui->cameraWSpin->value());
374 Options::setCameraHeight(ui->cameraHSpin->value());
375 Options::setCameraRotation(ui->positionAngleSpin->value());
378 const auto fov_x = 206264.8062470963552 * ui->cameraWSpin->value() * ui->pixelWSizeSpin->value() / 60000.0 /
379 ui->focalLenSpin->value();
380 const auto fov_y = 206264.8062470963552 * ui->cameraHSpin->value() * ui->pixelHSizeSpin->value() / 60000.0 /
381 ui->focalLenSpin->value();
383 ui->cameraWFOVSpin->setValue(fov_x);
384 ui->cameraHFOVSpin->setValue(fov_y);
386 double const target_fov_w = getTargetWFOV();
387 double const target_fov_h = getTargetHFOV();
389 if (ui->targetWFOVSpin->value() < target_fov_w)
391 bool const sig = ui->targetWFOVSpin->blockSignals(
true);
392 ui->targetWFOVSpin->setValue(target_fov_w);
393 ui->targetWFOVSpin->blockSignals(sig);
396 if (ui->targetHFOVSpin->value() < target_fov_h)
398 bool const sig = ui->targetHFOVSpin->blockSignals(
true);
399 ui->targetHFOVSpin->setValue(target_fov_h);
400 ui->targetHFOVSpin->blockSignals(sig);
403 m_DebounceTimer->start();
406 void FramingAssistantUI::resetFOV()
408 if (!isEquipmentValid())
411 ui->targetWFOVSpin->setValue(getTargetWFOV());
412 ui->targetHFOVSpin->setValue(getTargetHFOV());
415 void FramingAssistantUI::updateTargetFOVFromGrid()
417 if (!isEquipmentValid())
420 double const targetWFOV = getTargetWFOV();
421 double const targetHFOV = getTargetHFOV();
423 if (ui->targetWFOVSpin->value() != targetWFOV)
425 bool const sig = ui->targetWFOVSpin->blockSignals(
true);
426 ui->targetWFOVSpin->setValue(targetWFOV);
427 ui->targetWFOVSpin->blockSignals(sig);
428 m_DebounceTimer->start();
431 if (ui->targetHFOVSpin->value() != targetHFOV)
433 bool const sig = ui->targetHFOVSpin->blockSignals(
true);
434 ui->targetHFOVSpin->setValue(targetHFOV);
435 ui->targetHFOVSpin->blockSignals(sig);
436 m_DebounceTimer->start();
440 void FramingAssistantUI::updateGridFromTargetFOV()
442 if (!isEquipmentValid())
445 double const expectedW = getTargetMosaicW();
446 double const expectedH = getTargetMosaicH();
448 if (expectedW != ui->mosaicWSpin->value())
450 bool const sig = ui->mosaicWSpin->blockSignals(
true);
451 ui->mosaicWSpin->setValue(expectedW);
452 ui->mosaicWSpin->blockSignals(sig);
455 if (expectedH != ui->mosaicHSpin->value())
457 bool const sig = ui->mosaicHSpin->blockSignals(
true);
458 ui->mosaicHSpin->setValue(expectedH);
459 ui->mosaicHSpin->blockSignals(sig);
463 m_DebounceTimer->start();
466 void FramingAssistantUI::constructMosaic()
468 m_DebounceTimer->stop();
470 if (!isEquipmentValid())
473 auto tiles = KStarsData::Instance()->
skyComposite()->mosaicComponent()->tiles();
477 tiles->setRA0(m_CenterPoint.ra0());
478 tiles->setDec0(m_CenterPoint.dec0());
479 tiles->updateCoordsNow(KStarsData::Instance()->updateNum());
482 tiles->setGridSize(
QSize(ui->mosaicWSpin->value(), ui->mosaicHSpin->value()));
484 tiles->setPositionAngle(ui->positionAngleSpin->value());
486 tiles->setCameraFOV(
QSizeF(ui->cameraWFOVSpin->value(), ui->cameraHFOVSpin->value()));
488 tiles->setMosaicFOV(
QSizeF(ui->targetWFOVSpin->value(), ui->targetHFOVSpin->value()));
490 tiles->setOverlap(ui->overlapSpin->value());
492 tiles->createTiles(ui->reverseOddRows->checkState() == Qt::CheckState::Checked);
495 void FramingAssistantUI::fetchINDIInformation()
498 for (
auto oneWidget : ui->equipment->children())
500 for (
auto oneWidget : ui->createGrid->children())
501 oneWidget->blockSignals(
true);
504 "/KStars/Ekos/Align",
505 "org.kde.kstars.Ekos.Align",
513 m_CameraSize =
QSize(values[0], values[1]);
514 ui->cameraWSpin->setValue(m_CameraSize.width());
515 ui->cameraHSpin->setValue(m_CameraSize.height());
516 m_PixelSize =
QSizeF(values[2], values[3]);
517 ui->pixelWSizeSpin->setValue(m_PixelSize.width());
518 ui->pixelHSizeSpin->setValue(m_PixelSize.height());
525 m_FocalLength =
values[0];
526 ui->focalLenSpin->setValue(m_FocalLength);
533 if (values[0] > INVALID_VALUE)
535 m_PA = SolverUtils::rotationToPositionAngle(values[0]);
536 ui->positionAngleSpin->setValue(m_PA);
543 for (
auto oneWidget : ui->equipment->children())
544 oneWidget->blockSignals(
false);
545 for (
auto oneWidget : ui->createGrid->children())
546 oneWidget->blockSignals(
false);
549 void FramingAssistantUI::rewordStepEvery(
int v)
561 if (sanitized !=
i18n(
"unnamed"))
573 void FramingAssistantUI::goAndSolve()
577 if (m_GOTOSolvePending && m_MountState == ISD::Mount::MOUNT_TRACKING)
579 m_GOTOSolvePending =
false;
580 ui->goSolveB->setStyleSheet(
"border: 1px outset yellow");
581 Ekos::Manager::Instance()->alignModule()->captureAndSolve();
586 Ekos::Manager::Instance()->alignModule()->setSolverAction(Ekos::Align::GOTO_SLEW);
587 Ekos::Manager::Instance()->mountModule()->gotoTarget(m_CenterPoint);
588 ui->goSolveB->setStyleSheet(
"border: 1px outset magenta");
589 m_GOTOSolvePending =
true;
593 void FramingAssistantUI::createJobs()
595 auto scheduler = Ekos::Manager::Instance()->schedulerModule();
596 auto tiles = KStarsData::Instance()->
skyComposite()->mosaicComponent()->tiles();
597 auto sequence = ui->sequenceEdit->text();
598 auto outputDirectory = ui->directoryEdit->text();
599 auto target = ui->targetEdit->text();
601 tiles->setTargetName(target);
602 tiles->setOutputDirectory(outputDirectory);
603 tiles->setSequenceFile(sequence);
604 tiles->setFocusEveryN(ui->focusEvery->value());
605 tiles->setAlignEveryN(ui->alignEvery->value());
606 tiles->setStepChecks(ui->trackStepCheck->isChecked(), ui->focusStepCheck->isChecked(),
607 ui->alignStepCheck->isChecked(), ui->guideStepCheck->isChecked());
608 tiles->setPositionAngle(ui->positionAngleSpin->value());
610 scheduler->removeAllJobs();
613 for (
auto oneTile : tiles->tiles())
616 XMLEle *root = scheduler->getSequenceJobRoot(sequence);
620 const auto oneTarget =
QString(
"%1-Part%2").
arg(target).
arg(batchCount);
621 if (scheduler->createJobSequence(root, oneTarget, outputDirectory) ==
false)
628 auto oneSequence =
QString(
"%1/%2.esq").
arg(outputDirectory, oneTarget);
631 bool shouldFocus = ui->focusStepCheck->isChecked() && (batchCount == 1 || (batchCount % ui->focusEvery->value()) == 0);
632 bool shouldAlign = ui->alignStepCheck->isChecked() && (batchCount == 1 || (batchCount % ui->alignEvery->value()) == 0);
635 {
"target", oneTarget},
636 {
"ra", oneTile->skyCenter.ra0().toHMSString()},
637 {
"dec", oneTile->skyCenter.dec0().toDMSString()},
638 {
"pa", tiles->positionAngle()},
639 {
"sequence", oneSequence},
640 {
"track", ui->trackStepCheck->isChecked()},
641 {
"focus", shouldFocus},
642 {
"align", shouldAlign},
643 {
"guide", ui->guideStepCheck->isChecked()}
646 scheduler->setPrimarySettings(settings);
648 scheduler->saveJob();
651 auto schedulerListFile =
QString(
"%1/%2.esl").
arg(outputDirectory, target);
654 Ekos::Manager::Instance()->activateModule(
i18n(
"Scheduler"),
true);
659 void FramingAssistantUI::setMountState(ISD::Mount::Status value)
661 m_MountState = value;
662 if (m_GOTOSolvePending && m_MountState == ISD::Mount::MOUNT_TRACKING)
664 m_GOTOSolvePending =
false;
665 ui->goSolveB->setStyleSheet(
"border: 1px outset yellow");
666 Ekos::Manager::Instance()->alignModule()->captureAndSolve();
670 void FramingAssistantUI::setAlignState(AlignState value)
672 m_AlignState = value;
674 if (m_AlignState == Ekos::ALIGN_COMPLETE)
675 ui->goSolveB->setStyleSheet(
"border: 1px outset green");
676 else if (m_AlignState == Ekos::ALIGN_ABORTED || m_AlignState == Ekos::ALIGN_FAILED)
677 ui->goSolveB->setStyleSheet(
"border: 1px outset red");
680 void FramingAssistantUI::selectSequence()
684 i18n(
"Ekos Sequence Queue (*.esq)"));
688 ui->sequenceEdit->setText(file);
689 ui->createJobsB->setEnabled(!ui->targetEdit->text().isEmpty() && !ui->sequenceEdit->text().isEmpty() &&
690 !ui->directoryEdit->text().isEmpty());
694 void FramingAssistantUI::selectDirectory()
699 if (!m_JobsDirectory.isEmpty())
702 QString sanitized = ui->targetEdit->text();
703 if (sanitized.
isEmpty() ==
false && sanitized !=
i18n(
"unnamed"))
706 sanitized = sanitize(sanitized);
707 ui->directoryEdit->setText(m_JobsDirectory +
QDir::separator() + sanitized);
711 ui->directoryEdit->setText(m_JobsDirectory);
714 ui->createJobsB->setEnabled(!ui->targetEdit->text().isEmpty() && !ui->sequenceEdit->text().isEmpty() &&
715 !ui->directoryEdit->text().isEmpty());