Kstars

placeholderpath.cpp
1 /*
2  SPDX-FileCopyrightText: 2021 Kwon-Young Choi <[email protected]>
3 
4  SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "placeholderpath.h"
8 
9 #include "ekos/scheduler/schedulerjob.h"
10 #include "sequencejob.h"
11 #include "Options.h"
12 #include "kspaths.h"
13 
14 #include <QString>
15 #include <QStringList>
16 
17 #include <cmath>
18 
19 namespace Ekos
20 {
21 
22 PlaceholderPath::PlaceholderPath(QString seqFilename):
23  m_frameTypes(
24 {
25  {FRAME_LIGHT, "Light"},
26  {FRAME_DARK, "Dark"},
27  {FRAME_BIAS, "Bias"},
28  {FRAME_FLAT, "Flat"},
29  {FRAME_NONE, ""},
30 }),
31 m_seqFilename(seqFilename)
32 {
33 }
34 
35 PlaceholderPath::PlaceholderPath():
36  PlaceholderPath("")
37 {
38 }
39 
40 PlaceholderPath::~PlaceholderPath()
41 {
42 }
43 
44 void PlaceholderPath::processJobInfo(SequenceJob *job, QString targetName)
45 {
46  job->setCoreProperty(SequenceJob::SJ_TargetName, targetName);
47 
48  auto frameType = getFrameType(job->getFrameType());
49  auto filterType = job->getCoreProperty(SequenceJob::SJ_Filter).toString();
50  auto exposure = job->getCoreProperty(SequenceJob::SJ_Exposure).toDouble();
51  auto rawPrefix = job->getCoreProperty(SequenceJob::SJ_RawPrefix).toString();
52  auto filterEnabled = job->getCoreProperty(SequenceJob::SJ_FilterPrefixEnabled).toBool();
53  auto expEnabled = job->getCoreProperty(SequenceJob::SJ_ExpPrefixEnabled).toBool();
54  //auto tsEnabled = job->getCoreProperty(SequenceJob::SJ_TimeStampPrefixEnabled).toBool();
55  const auto isDarkFlat = job->getCoreProperty(SequenceJob::SJ_DarkFlat).toBool();
56 
57  if (isDarkFlat)
58  frameType = "DarkFlat";
59 
60  // Sanitize name
61  //QString targetName = schedJob->getName();
62  targetName = targetName.replace( QRegularExpression("\\s|/|\\(|\\)|:|\\*|~|\"" ), "_" )
63  // Remove any two or more __
64  .replace( QRegularExpression("_{2,}"), "_")
65  // Remove any _ at the end
66  .replace( QRegularExpression("_$"), "");
67 
68  // Because scheduler sets the target name in capture module
69  // it would be the same as the raw prefix
70  if (targetName.isEmpty() == false && rawPrefix.isEmpty())
71  rawPrefix = targetName;
72 
73  // Make full prefix
74  QString imagePrefix = rawPrefix;
75 
76  if (imagePrefix.isEmpty() == false)
77  imagePrefix += '_';
78 
79  imagePrefix += frameType;
80 
81  if (filterEnabled && filterType.isEmpty() == false &&
82  (job->getFrameType() == FRAME_LIGHT || job->getFrameType() == FRAME_FLAT || job->getFrameType() == FRAME_NONE || isDarkFlat))
83  {
84  imagePrefix += '_';
85 
86  imagePrefix += filterType;
87  }
88 
89  // JM 2021.08.21 For flat frames with specific ADU, the exposure duration is only advisory
90  // and the final exposure time would depend on how many seconds are needed to arrive at the
91  // target ADU. Therefore we should add duration to the signature.
92  //if (expEnabled && !(job->getFrameType() == FRAME_FLAT && job->getFlatFieldDuration() == DURATION_ADU))
93  if (expEnabled)
94  {
95  imagePrefix += '_';
96 
97  double fractpart, intpart;
98  fractpart = std::modf(exposure, &intpart);
99  if (fractpart == 0)
100  {
101  imagePrefix += QString::number(exposure, 'd', 0) + QString("_secs");
102  }
103  else if (exposure >= 1e-3)
104  {
105  imagePrefix += QString::number(exposure, 'f', 3) + QString("_secs");
106  }
107  else
108  {
109  imagePrefix += QString::number(exposure, 'f', 6) + QString("_secs");
110  }
111  }
112 
113  job->setCoreProperty(SequenceJob::SJ_FullPrefix, imagePrefix);
114 
115  // Directory postfix
116  QString directoryPostfix;
117 
118  /* FIXME: Refactor directoryPostfix assignment, whose code is duplicated in capture.cpp */
119  if (targetName.isEmpty())
120  directoryPostfix = QDir::separator() + frameType;
121  else
122  directoryPostfix = QDir::separator() + targetName + QDir::separator() + frameType;
123  if ((job->getFrameType() == FRAME_LIGHT || job->getFrameType() == FRAME_FLAT || job->getFrameType() == FRAME_NONE || isDarkFlat)
124  && filterType.isEmpty() == false)
125  directoryPostfix += QDir::separator() + filterType;
126 
127  job->setCoreProperty(SequenceJob::SJ_DirectoryPostfix, directoryPostfix);
128 
129 }
130 
131 void PlaceholderPath::addJob(SequenceJob *job, QString targetName)
132 {
133  job->setCoreProperty(SequenceJob::SJ_TargetName, targetName);
134 
135  auto frameType = job->getFrameType();
136  auto frameTypeString = getFrameType(job->getFrameType());
137  const auto rawPrefix = job->getCoreProperty(SequenceJob::SJ_RawPrefix).toString();
138  QString imagePrefix = rawPrefix;
139 
140  // Override
141  const auto isDarkFlat = job->getCoreProperty(SequenceJob::SJ_DarkFlat).toBool();
142  if (isDarkFlat)
143  frameTypeString = "DarkFlat";
144 
145  // JM 2019-11-26: In case there is no raw prefix set
146  // BUT target name is set, we update the prefix to include
147  // the target name, which is usually set by the scheduler.
148  if (imagePrefix.isEmpty() && !targetName.isEmpty())
149  {
150  imagePrefix = targetName;
151  }
152 
153  constructPrefix(job, imagePrefix);
154 
155  job->setCoreProperty(SequenceJob::SJ_FullPrefix, imagePrefix);
156 
157  QString directoryPostfix;
158 
159  const auto filterName = job->getCoreProperty(SequenceJob::SJ_Filter).toString();
160 
161  /* FIXME: Refactor directoryPostfix assignment, whose code is duplicated in scheduler.cpp */
162  if (targetName.isEmpty())
163  directoryPostfix = QDir::separator() + frameTypeString;
164  else
165  directoryPostfix = QDir::separator() + targetName + QDir::separator() + frameTypeString;
166 
167 
168  if ((frameType == FRAME_LIGHT || frameType == FRAME_FLAT || frameType == FRAME_NONE || isDarkFlat)
169  && filterName.isEmpty() == false)
170  directoryPostfix += QDir::separator() + filterName;
171 
172  job->setCoreProperty(SequenceJob::SJ_DirectoryPostfix, directoryPostfix);
173 }
174 
175 void PlaceholderPath::constructPrefix(SequenceJob *job, QString &imagePrefix)
176 {
177  CCDFrameType frameType = job->getFrameType();
178  auto filter = job->getCoreProperty(SequenceJob::SJ_Filter).toString();
179  auto rawPrefix = job->getCoreProperty(SequenceJob::SJ_RawPrefix).toString();
180  auto filterEnabled = job->getCoreProperty(SequenceJob::SJ_FilterPrefixEnabled).toBool();
181  auto expEnabled = job->getCoreProperty(SequenceJob::SJ_ExpPrefixEnabled).toBool();
182  auto tsEnabled = job->getCoreProperty(SequenceJob::SJ_TimeStampPrefixEnabled).toBool();
183 
184  double exposure = job->getCoreProperty(SequenceJob::SJ_Exposure).toDouble();
185 
186  if (imagePrefix.isEmpty() == false)
187  imagePrefix += '_';
188 
189  const auto isDarkFlat = job->getCoreProperty(SequenceJob::SJ_DarkFlat).toBool();
190 
191  imagePrefix += isDarkFlat ? "DarkFlat" : CCDFrameTypeNames[frameType];
192 
193  if (filterEnabled && filter.isEmpty() == false &&
194  (frameType == FRAME_LIGHT ||
195  frameType == FRAME_FLAT ||
196  frameType == FRAME_NONE ||
197  isDarkFlat))
198  {
199  imagePrefix += '_';
200  imagePrefix += filter;
201  }
202  if (expEnabled)
203  {
204  imagePrefix += '_';
205 
206  double exposureValue = job->getCoreProperty(SequenceJob::SJ_Exposure).toDouble();
207 
208  // Don't use the locale for exposure value in the capture file name, so that we get a "." as decimal separator
209  if (exposureValue == static_cast<int>(exposureValue))
210  // Whole number
211  imagePrefix += QString::number(exposure, 'd', 0) + QString("_secs");
212  else
213  {
214  // Decimal
215  if (exposure >= 0.001)
216  imagePrefix += QString::number(exposure, 'f', 3) + QString("_secs");
217  else
218  imagePrefix += QString::number(exposure, 'f', 6) + QString("_secs");
219  }
220  }
221  if (tsEnabled)
222  {
223  imagePrefix += SequenceJob::ISOMarker;
224  }
225 }
226 
227 void PlaceholderPath::generateFilenameOld(const QString &format, bool batch_mode, QString *filename,
228  QString fitsDir, QString seqPrefix, int nextSequenceID
229  )
230 {
231  QString currentDir;
232  if (batch_mode)
233  currentDir = fitsDir.isEmpty() ? Options::fitsDir() : fitsDir;
234  else
235  currentDir = KSPaths::writableLocation(QStandardPaths::TempLocation) + "/kstars";
236 
237  /*
238  if (QDir(currentDir).exists() == false)
239  QDir().mkpath(currentDir);
240  */
241 
242  if (currentDir.endsWith('/') == false)
243  currentDir.append('/');
244 
245  // IS8601 contains colons but they are illegal under Windows OS, so replacing them with '-'
246  // The timestamp is no longer ISO8601 but it should solve interoperality issues
247  // between different OS hosts
248  QString ts = QDateTime::currentDateTime().toString("yyyy-MM-ddThh-mm-ss");
249 
250  if (seqPrefix.contains("_ISO8601"))
251  {
252  QString finalPrefix = seqPrefix;
253  finalPrefix.replace("ISO8601", ts);
254  *filename = currentDir + finalPrefix +
255  QString("_%1%2").arg(QString().asprintf("%03d", nextSequenceID), format);
256  }
257  else
258  *filename = currentDir + seqPrefix + (seqPrefix.isEmpty() ? "" : "_") +
259  QString("%1%2").arg(QString().asprintf("%03d", nextSequenceID), format);
260 }
261 
262 void PlaceholderPath::generateFilename(
263  QString format, SequenceJob &job, QString targetName, bool batch_mode, int nextSequenceID, const QString &extension,
264  QString *filename) const
265 {
266  auto filter = job.getCoreProperty(SequenceJob::SJ_Filter).toString();
267  auto rawPrefix = job.getCoreProperty(SequenceJob::SJ_RawPrefix).toString();
268  auto filterEnabled = job.getCoreProperty(SequenceJob::SJ_FilterPrefixEnabled).toBool();
269  auto expEnabled = job.getCoreProperty(SequenceJob::SJ_ExpPrefixEnabled).toBool();
270  auto tsEnabled = job.getCoreProperty(SequenceJob::SJ_TimeStampPrefixEnabled).toBool();
271  auto darkFlat = job.getCoreProperty(SequenceJob::SJ_DarkFlat).toBool();
272 
273 
274  generateFilename(format, rawPrefix, filterEnabled, expEnabled,
275  tsEnabled, darkFlat, filter, job.getFrameType(), job.getCoreProperty(SequenceJob::SJ_Exposure).toDouble(),
276  targetName, batch_mode, nextSequenceID, extension, filename);
277 }
278 
279 void PlaceholderPath::generateFilename(QString format, bool tsEnabled, bool batch_mode,
280  int nextSequenceID, const QString &extension, QString *filename) const
281 {
282  generateFilename(format, m_RawPrefix, m_filterPrefixEnabled, m_expPrefixEnabled,
283  tsEnabled, m_DarkFlat, m_filter, m_frameType, m_exposure, m_targetName, batch_mode,
284  nextSequenceID, extension, filename);
285 }
286 
287 void PlaceholderPath::generateFilename(
288  QString format, QString rawFilePrefix, bool filterEnabled, bool exposureEnabled,
289  bool tsEnabled, bool isDarkFlat, QString filter, CCDFrameType frameType, double exposure, QString targetName,
290  bool batch_mode, int nextSequenceID, const QString &extension, QString *filename) const
291 {
292  targetName = targetName.replace( QRegularExpression("\\s|/|\\(|\\)|:|\\*|~|\"" ), "_" )
293  // Remove any two or more __
294  .replace( QRegularExpression("_{2,}"), "_")
295  // Remove any _ at the end
296  .replace( QRegularExpression("_$"), "");
297  int i = 0;
298 
299  QString currentDir;
300  if (batch_mode)
301  {
302  currentDir = m_seqFilename.path().isEmpty() ? Options::fitsDir() : currentDir;
303  }
304  else
305  {
306  currentDir = KSPaths::writableLocation(QStandardPaths::TempLocation) + "/kstars";
307  }
308 
309  if (currentDir.endsWith('/') == true)
310  currentDir.chop(1);
311 
312  if (!currentDir.isEmpty())
313  format = currentDir + "/" + format.section("/", -1);
314 
316  QRegularExpression re("(?<replace>\\%(?<name>[f,D,T,e,F,t,d,p,s])(?<level>\\d+)?)(?<sep>[_/])?");
317  while ((i = format.indexOf(re, i, &match)) != -1)
318  {
319  QString replacement = "";
320  if (match.captured("name") == "f")
321  {
322  replacement = m_seqFilename.baseName();
323  }
324  else if (match.captured("name") == "D")
325  {
326  if (tsEnabled)
327  {
328  replacement = QDateTime::currentDateTime().toString("yyyy-MM-ddThh-mm-ss");
329  }
330  }
331  else if (match.captured("name") == "T")
332  {
333  if (isDarkFlat)
334  replacement = "DarkFlat";
335  else
336  replacement = getFrameType(frameType);
337  }
338  else if (match.captured("name") == "e")
339  {
340  if (exposureEnabled)
341  {
342  double fractpart, intpart;
343  fractpart = std::modf(exposure, &intpart);
344  if (fractpart == 0)
345  {
346  replacement = QString::number(exposure, 'd', 0) + QString("_secs");
347  }
348  else if (exposure >= 1e-3)
349  {
350  replacement = QString::number(exposure, 'f', 3) + QString("_secs");
351  }
352  else
353  {
354  replacement = QString::number(exposure, 'f', 6) + QString("_secs");
355  }
356  }
357  }
358  else if (match.captured("name") == "F")
359  {
360  if (format.indexOf("/", match.capturedStart()) == -1)
361  {
362  // in the basename part of the path
363  if (filterEnabled && filter.isEmpty() == false
364  && (frameType == FRAME_LIGHT
365  || frameType == FRAME_FLAT
366  || frameType == FRAME_NONE
367  || m_DarkFlat))
368  {
369  replacement = filter;
370  }
371  }
372  else
373  {
374  // in the directory part of the path
375  if (filter.isEmpty() == false
376  && (frameType == FRAME_LIGHT
377  || frameType == FRAME_FLAT
378  || frameType == FRAME_NONE
379  || m_DarkFlat))
380  {
381  replacement = filter;
382  }
383  }
384  }
385  else if (match.captured("name") == "t")
386  {
387  if (format.indexOf("/", match.capturedStart()) != -1)
388  {
389  // in the directory part of the path
390  replacement = targetName;
391  }
392  else
393  {
394  // in the basename part of the path
395  replacement = rawFilePrefix;
396  if (replacement.isEmpty() && !targetName.isEmpty())
397  {
398  replacement = targetName;
399  }
400  }
401  }
402  else if (match.captured("name") == "d" || match.captured("name") == "p")
403  {
404  int level = 0;
405  if (!match.captured("level").isEmpty())
406  {
407  level = match.captured("level").toInt() - 1;
408  }
409  QFileInfo dir = m_seqFilename;
410  for (int j = 0; j < level; ++j)
411  {
412  dir = QFileInfo(dir.dir().path());
413  }
414  if (match.captured("name") == "d")
415  {
416  replacement = dir.dir().dirName();
417  }
418  else if (match.captured("name") == "p")
419  {
420  replacement = dir.path();
421  }
422  }
423  else if (match.captured("name") == "s")
424  {
425  int level = 1;
426  if (!match.captured("level").isEmpty())
427  {
428  level = match.captured("level").toInt();
429  }
430  replacement = QString("%1").arg(nextSequenceID, level, 10, QChar('0'));
431  }
432  else
433  {
434  qWarning() << "Unknown replacement string: " << match.captured("replace");
435  }
436  if (replacement.isEmpty())
437  {
438  format = format.replace(match.capturedStart(), match.capturedLength(), replacement);
439  }
440  else
441  {
442  format = format.replace(match.capturedStart("replace"), match.capturedLength("replace"), replacement);
443  }
444  i += replacement.length();
445  }
446  *filename = format + extension;
447 }
448 
449 void PlaceholderPath::setGenerateFilenameSettings(const SequenceJob &job)
450 {
451  m_frameType = job.getFrameType();
452  m_RawPrefix = job.getCoreProperty(SequenceJob::SJ_RawPrefix).toString();
453  m_filterPrefixEnabled = job.getCoreProperty(SequenceJob::SJ_FilterPrefixEnabled).toBool();
454  m_expPrefixEnabled = job.getCoreProperty(SequenceJob::SJ_ExpPrefixEnabled).toBool();
455  m_filter = job.getCoreProperty(SequenceJob::SJ_Filter).toString();
456  m_exposure = job.getCoreProperty(SequenceJob::SJ_Exposure).toDouble();
457  m_targetName = job.getCoreProperty(SequenceJob::SJ_TargetName).toString();
458  m_DarkFlat = job.getCoreProperty(SequenceJob::SJ_DarkFlat).toBool();
459 }
460 
461 QStringList PlaceholderPath::remainingPlaceholders(QString filename)
462 {
463  QList<QString> placeholders = {};
465  QRegularExpression re("(?<replace>\\%(?<name>[a-z])(?<level>\\d+)?)(?<sep>[_/])?");
466  int i = 0;
467  while ((i = filename.indexOf(re, i, &match)) != -1)
468  {
469  if (match.hasMatch())
470  {
471  placeholders.push_back(match.captured("replace"));
472  }
473  i += match.capturedLength("replace");
474  }
475  return placeholders;
476 }
477 
478 }
479 
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
QString section(QChar sep, int start, int end, QString::SectionFlags flags) const const
QString number(int n, int base)
Ekos is an advanced Astrophotography tool for Linux. It is based on a modular extensible framework to...
Definition: align.cpp:70
QDateTime currentDateTime()
QChar separator()
void chop(int n)
QStringView level(QStringView ifopt)
void push_back(const T &value)
Sequence Job is a container for the details required to capture a series of images.
Definition: sequencejob.h:18
bool isEmpty() const const
int length() const const
QFuture< void > filter(Sequence &sequence, KeepFunctor filterFunction)
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
QString & replace(int position, int n, QChar after)
KIOFILEWIDGETS_EXPORT QString dir(const QString &fileClass)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
QString asprintf(const char *cformat,...)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString toString(Qt::DateFormat format) const const
QString & append(QChar ch)
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Fri Aug 12 2022 04:00:56 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.