Kstars

capturecountswidget.cpp
1/*
2 SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
3 SPDX-FileCopyrightText: 2021 Wolfgang Reissenberger <sterne-jaeger@openfuture.de>
4
5 SPDX-License-Identifier: GPL-2.0-or-later
6*/
7
8#include "capturecountswidget.h"
9#include "Options.h"
10#include "ekos/manager.h"
11#include "ekos/scheduler/schedulerjob.h"
12#include "ekos/scheduler/schedulermodulestate.h"
13#include "ekos/capture/capture.h"
14#include "ekos/capture/sequencejob.h"
15
16#include <ekos_capture_debug.h>
17
18using Ekos::SequenceJob;
19
20CaptureCountsWidget::CaptureCountsWidget(QWidget *parent) : QWidget(parent)
21{
22 setupUi(this);
23 // switch between stacked views
24 connect(switchToGraphicsButton, &QPushButton::clicked, this, [this]()
25 {
26 textView->setVisible(false);
27 graphicalView->setVisible(true);
28 Options::setUseGraphicalCountsDisplay(true);
29 });
30 connect(switchToTextButton, &QPushButton::clicked, this, [this]()
31 {
32 textView->setVisible(true);
33 graphicalView->setVisible(false);
34 Options::setUseGraphicalCountsDisplay(false);
35 });
36
37 // start with the last used view
38 graphicalView->setVisible(Options::useGraphicalCountsDisplay());
39 textView->setVisible(!Options::useGraphicalCountsDisplay());
40
41 // setup graphical view
42 gr_sequenceProgressBar->setDecimals(0);
43 gr_overallProgressBar->setDecimals(0);
44
45 reset();
46}
47
48void CaptureCountsWidget::setCurrentTrainName(const QString &name)
49{
50 m_currentTrainName = name;
51 showCurrentCameraInfo();
52 refreshCaptureCounters(name);
53}
54
55void CaptureCountsWidget::refreshImageCounts(const QString &trainname)
56{
57 if (imageCounts[trainname].changed)
58 {
59 imageProgress->setRange(0, int(std::ceil(imageCounts[trainname].totalTime)));
60 imageProgress->setValue(int(std::ceil(imageCounts[trainname].totalTime - imageCounts[trainname].remainingTime)));
61 gr_imageProgress->setRange(0, int(std::ceil(imageCounts[trainname].totalTime)));
62 gr_imageProgress->setValue(imageProgress->value());
63
64 frameRemainingTime->setText(imageCounts[trainname].countDown.toString("hh:mm:ss"));
65 gr_frameRemainingTime->setText(frameRemainingTime->text());
66 // clear the changed flag
67 imageCounts[trainname].changed = false;
68 }
69 else if(isCaptureActive(trainname) == false)
70 {
71 imageProgress->setValue(0);
72 gr_imageProgress->setValue(0);
73 }
74}
75
76void CaptureCountsWidget::updateExposureProgress(const QSharedPointer<Ekos::SequenceJob> &job, const QString &trainname)
77{
78 imageCounts[trainname].countDown.setHMS(0, 0, 0);
79 imageCounts[trainname].countDown = imageCounts[trainname].countDown.addSecs(int(std::round(job->getExposeLeft())));
80 if (imageCounts[trainname].countDown.hour() == 23)
81 imageCounts[trainname].countDown.setHMS(0, 0, 0);
82
83 const double total = job->getCoreProperty(SequenceJob::SJ_Exposure).toDouble();
84 const double remaining = job->getExposeLeft();
85
86 // changes recognized only if the difference is at least one second (since we update once a second)
87 imageCounts[trainname].changed = fabs(total - imageCounts[trainname].totalTime) >= 1.0 ||
88 fabs(remaining - imageCounts[trainname].remainingTime) >= 1.0;
89 imageCounts[trainname].totalTime = total;
90 imageCounts[trainname].remainingTime = remaining;
91
92 refreshImageCounts(trainname);
93}
94
95void CaptureCountsWidget::updateDownloadProgress(double timeLeft, const QString &trainname)
96{
97 imageCounts[trainname].countDown.setHMS(0, 0, 0);
98 imageCounts[trainname].countDown = imageCounts[trainname].countDown.addSecs(int(std::ceil(timeLeft)));
99 frameRemainingTime->setText(imageCounts[trainname].countDown.toString("hh:mm:ss"));
100}
101
102void CaptureCountsWidget::shareSchedulerState(QSharedPointer<Ekos::SchedulerModuleState> state)
103{
104 m_schedulerModuleState = state;
105}
106
107void CaptureCountsWidget::updateCaptureCountDown(int delta)
108{
109 // update counters of all devices
110 for (const QString &trainname : totalCounts.keys())
111 {
112 totalCounts[trainname].countDown = totalCounts[trainname].countDown.addSecs(delta);
113 jobCounts[trainname].countDown = jobCounts[trainname].countDown.addSecs(delta);
114 sequenceCounts[trainname].countDown = sequenceCounts[trainname].countDown.addSecs(delta);
115
116 // ensure that count downs do not overshoot
117 if (totalCounts[trainname].countDown.hour() == 23)
118 totalCounts[trainname].countDown.setHMS(0, 0, 0);
119 if (jobCounts[trainname].countDown.hour() == 23)
120 jobCounts[trainname].countDown.setHMS(0, 0, 0);
121 if (sequenceCounts[trainname].countDown.hour() == 23)
122 sequenceCounts[trainname].countDown.setHMS(0, 0, 0);
123 }
124
125 // do not change overall remaining time if scheduler is in endless loop
126 if (m_schedulerModuleState == nullptr || m_schedulerModuleState->activeJob() == nullptr ||
127 m_schedulerModuleState->activeJob()->getCompletionCondition() != Ekos::FINISH_LOOP)
128 {
129 overallRemainingTime->setText(totalCounts[m_currentTrainName].countDown.toString("hh:mm:ss"));
130 gr_overallRemainingTime->setText(overallRemainingTime->text());
131 }
132 if (!m_captureProcess->isActiveJobPreview() && isCaptureActive(m_currentTrainName))
133 {
134 jobRemainingTime->setText(jobCounts[m_currentTrainName].countDown.toString("hh:mm:ss"));
135 sequenceRemainingTime->setText(sequenceCounts[m_currentTrainName].countDown.toString("hh:mm:ss"));
136 gr_sequenceRemainingTime->setText(sequenceRemainingTime->text());
137 }
138 else
139 {
140 jobRemainingTime->setText("--:--:--");
141 sequenceRemainingTime->setText("--:--:--");
142 gr_sequenceRemainingTime->setText("--:--:--");
143 }
144}
145
146void CaptureCountsWidget::reset()
147{
148 // reset graphical view
149 gr_imageProgress->setValue(0);
150 gr_frameLabel->setText("");
151 gr_frameRemainingTime->setText("--:--:--");
152 gr_frameDetailsLabel->setText("");
153 gr_sequenceLabel->setText(i18n("Sequence"));
154 gr_sequenceProgressBar->setValue(0);
155 gr_sequenceRemainingTime->setText("--:--:--");
156 gr_overallLabel->setText(i18n("Overall"));
157 gr_overallProgressBar->setValue(0);
158 gr_overallRemainingTime->setText("--:--:--");
159
160 // reset text view
161 imageProgress->setValue(0);
162 setFrameInfo("");
163 frameRemainingTime->setText("");
164
165 overallRemainingTime->setText("--:--:--");
166 jobRemainingTime->setText("--:--:--");
167 sequenceRemainingTime->setText("--:--:--");
168}
169
170namespace
171{
172QString frameLabel(const QString &type, const QString &filter)
173{
174 if (type == "Light")
175 {
176 if (filter.size() == 0)
177 return type;
178 else
179 return filter;
180 }
181 else if (type == "Flat")
182 {
183 if (filter.size() == 0)
184 return type;
185 else
186 return QString("%1 %2").arg(filter).arg(type);
187 }
188 else
189 return type;
190}
191}
192
193void CaptureCountsWidget::setFrameInfo(const QString frametype, const QString filter, const double exptime, const int xBin,
194 const int yBin, const double gain)
195{
196 if (frametype == "")
197 {
198 frameInfoLabel->setText("");
199 frameDetailsLabel->setText("");
200 gr_frameRemainingTime->setText("");
201 }
202 else
203 {
204 frameInfoLabel->setText(QString("%1").arg(frameLabel(frametype, filter)));
205 gr_frameLabel->setText(frameInfoLabel->text());
206 QString details = "";
207 if (exptime > 0)
208 details.append(QString("%1: %2 sec").arg(i18n("Exposure")).arg(exptime, 0, 'f', exptime < 1 ? 2 : exptime < 5 ? 1 : 0));
209 if (xBin > 0 && yBin > 0)
210 details.append(QString(", bin: %1x%2").arg(xBin).arg(yBin));
211 if (gain >= 0)
212 details.append(QString(", gain: %1").arg(gain, 0, 'f', 1));
213
214 frameDetailsLabel->setText(details);
215 gr_frameDetailsLabel->setText(details);
216 }
217}
218
219void CaptureCountsWidget::updateCaptureStatus(Ekos::CaptureState status, bool isPreview, const QString &trainname)
220{
221 // store current status
222 captureStates[trainname] = status;
223 // reset total counts
224 totalCounts[trainname].countDown.setHMS(0, 0, 0);
225 totalCounts[trainname].remainingTime = 0;
226 // update the attribute whether the current capture is a preview
227 m_isPreview = isPreview;
228
229 // find the corresponding camera
230 QSharedPointer<Ekos::Camera> selected_cam;
231 for (QSharedPointer<Ekos::Camera> camera : m_captureProcess->cameras())
232 {
233 if (camera->opticalTrain() == trainname)
234 {
235 selected_cam = camera;
236 break;
237 }
238 }
239
240 if (selected_cam.isNull())
241 {
242 qCWarning(KSTARS_EKOS_CAPTURE) << "No matching camera found" << m_currentTrainName;
243 return;
244 }
245
246 // determine total number of frames and completed ones - used either for
247 // total numbers if scheduler is not used - and for job figures in the text
248 // display if the scheduler is used
249 jobCounts[trainname].remainingTime = selected_cam->state()->overallRemainingTime();
250 jobCounts[trainname].count = 0, jobCounts[trainname].completed = 0;
251 for (int i = 0; i < selected_cam->state()->allJobs().count(); i++)
252 {
253 jobCounts[trainname].count += selected_cam->state()->jobImageCount(i);
254 jobCounts[trainname].completed += selected_cam->state()->jobImageProgress(i);
255 }
256
257 Ekos::SchedulerJob *activeJob = m_schedulerModuleState->activeJob(trainname);
258 if (m_schedulerModuleState != nullptr && activeJob != nullptr)
259 {
260 // FIXME: accessing the completed count might be one too low due to concurrency of updating the count and this loop
261 totalCounts[trainname].completed = activeJob->getCompletedCount();
262 totalCounts[trainname].count = activeJob->getSequenceCount();
263 if (activeJob->getEstimatedTime() > 0)
264 totalCounts[trainname].remainingTime = int(activeJob->getEstimatedTime());
265 }
266 else
267 {
268 totalCounts[trainname].remainingTime = jobCounts[trainname].remainingTime;
269 totalCounts[trainname].count = jobCounts[trainname].count;
270 totalCounts[trainname].completed = jobCounts[trainname].completed;
271 }
272
273 // update sequence remaining time
274 sequenceCounts[trainname].countDown.setHMS(0, 0, 0);
275 sequenceCounts[trainname].countDown = sequenceCounts[trainname].countDown.addSecs(
276 selected_cam->state()->activeJobRemainingTime());
277
278 // update job remaining time if run from the scheduler
279 if (m_schedulerModuleState != nullptr && activeJob != nullptr)
280 {
281 jobCounts[trainname].countDown.setHMS(0, 0, 0);
282 jobCounts[trainname].countDown = jobCounts[trainname].countDown.addSecs(selected_cam->state()->overallRemainingTime());
283 }
284
285 switch (status)
286 {
288 // do nothing
289 break;
291 refreshImageCounts(trainname);
292 updateCaptureCountDown(0);
293 [[fallthrough]];
294 default:
295 // update the counter display only for the currently selected train
296 if (m_currentTrainName == trainname)
297 refreshCaptureCounters(trainname);
298 }
299}
300
301void CaptureCountsWidget::updateJobProgress(CaptureProcessOverlay::FrameData data, const QString &trainname)
302{
303 m_currentFrame[trainname] = data;
304
305 // display informations if they come frome the currently selected camera device
306 if (trainname == m_currentTrainName)
307 showCurrentCameraInfo();
308}
309
310void CaptureCountsWidget::showCurrentCameraInfo()
311{
312 if (!m_currentFrame.contains(m_currentTrainName))
313 {
314 qCWarning(KSTARS_EKOS_CAPTURE) << "No frame info available for" << m_currentTrainName;
315 return;
316 }
317
318 auto data = m_currentFrame[m_currentTrainName];
319
320 if (data.jobType == SequenceJob::JOBTYPE_PREVIEW)
321 setFrameInfo(i18n("Preview"), data.filterName, data.exptime, data.binning.x(), data.binning.y(), data.gain);
322 else
323 setFrameInfo(CCDFrameTypeNames[data.frameType], data.filterName, data.exptime, data.binning.x(),
324 data.binning.y(), data.gain);
325
326 // display sequence progress in the graphical view
327 gr_sequenceProgressBar->setRange(0, data.count);
328 gr_sequenceProgressBar->setValue(data.completed);
329 if (data.jobType == SequenceJob::JOBTYPE_PREVIEW)
330 sequenceLabel->setText(QString("%1").arg(frameLabel(CCDFrameTypeNames[data.frameType], data.filterName)));
331 else
332 sequenceLabel->setText(QString("%1 (%3/%4)")
333 .arg(frameLabel(CCDFrameTypeNames[data.frameType], data.filterName)).arg(data.completed).arg(data.count));
334
335 gr_sequenceLabel->setText(sequenceLabel->text());
336}
337
338void CaptureCountsWidget::refreshCaptureCounters(const QString &trainname)
339{
340 QString total_label = "Total";
341 bool infinite_loop = false;
342 Ekos::SchedulerJob *activeJob = m_schedulerModuleState->activeJob(trainname);
343 bool isCapturing = isCaptureActive(trainname);
344 if (m_schedulerModuleState != nullptr && activeJob != nullptr)
345 {
346 infinite_loop = (activeJob->getCompletionCondition() == Ekos::FINISH_LOOP);
347 total_label = activeJob->getName();
348 }
349
350 if (infinite_loop == true || isCapturing == false)
351 {
352 overallRemainingTime->setText("--:--:--");
353 gr_overallProgressBar->setRange(0, 1);
354 gr_overallProgressBar->setValue(0);
355 gr_overallRemainingTime->setText(overallRemainingTime->text());
356 }
357 else
358 {
359 totalCounts[trainname].countDown = totalCounts[trainname].countDown.addSecs(totalCounts[trainname].remainingTime);
360 gr_overallProgressBar->setRange(0, totalCounts[trainname].count);
361 gr_overallProgressBar->setValue(totalCounts[trainname].completed);
362 }
363
364 // display overall remainings
365 if (m_isPreview)
366 overallLabel->setText(QString("%1").arg(total_label));
367 else
368 overallLabel->setText(QString("%1 (%2/%3)")
369 .arg(total_label)
370 .arg(totalCounts[trainname].completed)
371 .arg(infinite_loop ? QString("-") : QString::number(totalCounts[trainname].count)));
372 gr_overallLabel->setText(overallLabel->text());
373
374 // update job remaining time if run from the scheduler
375 bool show_job_progress = (m_schedulerModuleState != nullptr && activeJob != nullptr);
376 jobLabel->setVisible(show_job_progress);
377 jobRemainingTime->setVisible(show_job_progress);
378 if (show_job_progress)
379 {
380 jobLabel->setText(QString("Job (%1/%2)")
381 .arg(jobCounts[trainname].completed)
382 .arg(jobCounts[trainname].count));
383 }
384}
385
386Ekos::CaptureState CaptureCountsWidget::captureState(const QString &trainname)
387{
388 // initialize to a default state if unknown
389 if (! captureStates.contains(trainname))
390 captureStates[trainname] = Ekos::CAPTURE_IDLE;
391
392 return captureStates[trainname];
393}
394
395bool CaptureCountsWidget::isCaptureActive(const QString &trainname)
396{
397 Ekos::CaptureState state = captureState(trainname);
398 return (state == Ekos::CAPTURE_PROGRESS ||
399 state == Ekos::CAPTURE_CAPTURING ||
402}
403
404
405
406void CaptureCountsWidget::setEnabled(bool enabled)
407{
409 overallLabel->setEnabled(enabled);
410 gr_overallLabel->setEnabled(enabled);
411}
QString i18n(const char *text, const TYPE &arg...)
Type type(const QSqlDatabase &db)
CaptureState
Capture states.
Definition ekos.h:92
@ CAPTURE_PROGRESS
Definition ekos.h:94
@ CAPTURE_PAUSE_PLANNED
Definition ekos.h:96
@ CAPTURE_IMAGE_RECEIVED
Definition ekos.h:101
@ CAPTURE_ABORTED
Definition ekos.h:99
@ CAPTURE_CAPTURING
Definition ekos.h:95
@ CAPTURE_IDLE
Definition ekos.h:93
QString name(StandardAction id)
KGuiItem reset()
NETWORKMANAGERQT_EXPORT NetworkManager::Status status()
void clicked(bool checked)
int x() const const
int y() const const
bool isNull() const const
QString & append(QChar ch)
QString arg(Args &&... args) const const
QString number(double n, char format, int precision)
QFuture< void > filter(QThreadPool *pool, Sequence &sequence, KeepFunctor &&filterFunction)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void setEnabled(bool)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 25 2025 11:58:34 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.