File indexing completed on 2024-12-22 03:35:44
0001 /* 0002 File : FITSFilter.cpp 0003 Project : LabPlot 0004 Description : FITS I/O-filter 0005 -------------------------------------------------------------------- 0006 SPDX-FileCopyrightText: 2016 Fabian Kristof <fkristofszabolcs@gmail.com> 0007 SPDX-FileCopyrightText: 2017 Alexander Semke <alexander.semke@web.de> 0008 SPDX-FileCopyrightText: 2022 Stefan Gerlach <stefan.gerlach@uni.kn> 0009 SPDX-License-Identifier: GPL-2.0-or-later 0010 */ 0011 0012 #include "FITSFilter.h" 0013 #include "FITSFilterPrivate.h" 0014 #include "backend/core/column/Column.h" 0015 #include "backend/core/column/ColumnStringIO.h" 0016 #include "backend/core/datatypes/Double2StringFilter.h" 0017 #include "backend/datasources/AbstractDataSource.h" 0018 #include "backend/matrix/Matrix.h" 0019 #include "backend/matrix/MatrixModel.h" 0020 #include "backend/spreadsheet/Spreadsheet.h" 0021 #include "commonfrontend/matrix/MatrixView.h" 0022 0023 #include <QDebug> 0024 #include <QFile> 0025 #include <QMultiMap> 0026 0027 /*! \class FITSFilter 0028 * \brief Manages the import/export of data from/to a FITS file. 0029 * \since 2.2.0 0030 * \ingroup datasources 0031 */ 0032 FITSFilter::FITSFilter() 0033 : AbstractFileFilter(FileType::FITS) 0034 , d(new FITSFilterPrivate(this)) { 0035 } 0036 0037 FITSFilter::~FITSFilter() = default; 0038 0039 void FITSFilter::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) { 0040 d->readCHDU(fileName, dataSource, importMode); 0041 } 0042 0043 QVector<QStringList> FITSFilter::readChdu(const QString& fileName, bool* okToMatrix, int lines) { 0044 return d->readCHDU(fileName, nullptr, AbstractFileFilter::ImportMode::Replace, okToMatrix, lines); 0045 } 0046 0047 void FITSFilter::write(const QString& fileName, AbstractDataSource* dataSource) { 0048 d->writeCHDU(fileName, dataSource); 0049 } 0050 0051 void FITSFilter::addNewKeyword(const QString& filename, const QList<Keyword>& keywords) { 0052 d->addNewKeyword(filename, keywords); 0053 } 0054 0055 void FITSFilter::updateKeywords(const QString& fileName, const QList<Keyword>& originals, const QVector<Keyword>& updates) { 0056 d->updateKeywords(fileName, originals, updates); 0057 } 0058 0059 void FITSFilter::deleteKeyword(const QString& fileName, const QList<Keyword>& keywords) { 0060 d->deleteKeyword(fileName, keywords); 0061 } 0062 0063 void FITSFilter::addKeywordUnit(const QString& fileName, const QList<Keyword>& keywords) { 0064 d->addKeywordUnit(fileName, keywords); 0065 } 0066 0067 void FITSFilter::removeExtensions(const QStringList& extensions) { 0068 d->removeExtensions(extensions); 0069 } 0070 0071 void FITSFilter::parseHeader(const QString& fileName, QTableWidget* headerEditTable, bool readKeys, const QList<Keyword>& keys) { 0072 d->parseHeader(fileName, headerEditTable, readKeys, keys); 0073 } 0074 0075 void FITSFilter::parseExtensions(const QString& fileName, QTreeWidget* tw, bool checkPrimary) { 0076 d->parseExtensions(fileName, tw, checkPrimary); 0077 } 0078 0079 QList<FITSFilter::Keyword> FITSFilter::chduKeywords(const QString& fileName) { 0080 return d->chduKeywords(fileName); 0081 } 0082 0083 /*! 0084 * \brief contains the {StandardKeywords \ MandatoryKeywords} keywords 0085 * \return A list of keywords 0086 */ 0087 QStringList FITSFilter::standardKeywords() { 0088 return QStringList() << QLatin1String("(blank)") << QLatin1String("CROTA") << QLatin1String("EQUINOX") << QLatin1String("NAXIS") << QLatin1String("TBCOL") 0089 << QLatin1String("TUNIT") << QLatin1String("AUTHOR") << QLatin1String("CRPIX") << QLatin1String("EXTEND") << QLatin1String("OBJECT") 0090 << QLatin1String("TDIM") << QLatin1String("TZERO") << QLatin1String("BITPIX") << QLatin1String("CRVAL") << QLatin1String("EXTLEVEL") 0091 << QLatin1String("OBSERVER") << QLatin1String("TDISP") << QLatin1String("XTENSION") << QLatin1String("BLANK") << QLatin1String("CTYPE") 0092 << QLatin1String("EXTNAME") << QLatin1String("ORIGIN") << QLatin1String("TELESCOP") << QLatin1String("BLOCKED") 0093 << QLatin1String("DATAMAX") << QLatin1String("EXTVER") << QLatin1String("BSCALE") << QLatin1String("DATAMIN") << QLatin1String("PSCAL") 0094 << QLatin1String("TFORM") << QLatin1String("BUNIT") << QLatin1String("DATE") << QLatin1String("GROUPS") << QLatin1String("PTYPE") 0095 << QLatin1String("THEAP") << QLatin1String("BZERO") << QLatin1String("DATE-OBS") << QLatin1String("HISTORY") << QLatin1String("PZERO") 0096 << QLatin1String("TNULL") << QLatin1String("CDELT") << QLatin1String("INSTRUME") << QLatin1String("REFERENC") << QLatin1String("TSCAL") 0097 << QLatin1String("COMMENT") << QLatin1String("EPOCH") << QLatin1String("NAXIS") << QLatin1String("SIMPLE") << QLatin1String("TTYPE"); 0098 } 0099 0100 /*! 0101 * \brief Returns a list of keywords, that are mandatory for an image extension of a FITS file 0102 * see: 0103 * https://archive.stsci.edu/fits/fits_standard/node64.html 0104 * \return A list of keywords 0105 */ 0106 0107 QStringList FITSFilter::mandatoryImageExtensionKeywords() { 0108 return QStringList() << QLatin1String("XTENSION") << QLatin1String("BITPIX") << QLatin1String("NAXIS") << QLatin1String("PCOUNT") << QLatin1String("GCOUNT") 0109 << QLatin1String("END"); 0110 } 0111 0112 /*! 0113 * \brief Returns a list of keywords, that are mandatory for a table extension (ascii or bintable) 0114 * of a FITS file 0115 * see: 0116 * https://archive.stsci.edu/fits/fits_standard/node58.html 0117 * https://archive.stsci.edu/fits/fits_standard/node68.html 0118 * \return A list of keywords 0119 */ 0120 QStringList FITSFilter::mandatoryTableExtensionKeywords() { 0121 return QStringList() << QLatin1String("XTENSION") << QLatin1String("BITPIX") << QLatin1String("NAXIS") << QLatin1String("NAXIS1") << QLatin1String("NAXIS2") 0122 << QLatin1String("PCOUNT") << QLatin1String("GCOUNT") << QLatin1String("TFIELDS") << QLatin1String("END"); 0123 } 0124 0125 /*! 0126 * \brief Returns a list of strings that represent units which are used for autocompletion when adding 0127 * keyword units to keywords 0128 * \return A list of strings that represent units 0129 */ 0130 QStringList FITSFilter::units() { 0131 return QStringList() << QLatin1String("m (Metre)") << QLatin1String("kg (Kilogram)") << QLatin1String("s (Second)") << QString::fromUtf8("M☉ (Solar mass)") 0132 << QLatin1String("AU (Astronomical unit") << QLatin1String("l.y (Light year)") << QLatin1String("km (Kilometres") 0133 << QLatin1String("pc (Parsec)") << QLatin1String("K (Kelvin)") << QLatin1String("mol (Mole)") << QLatin1String("cd (Candela)"); 0134 } 0135 0136 /*! 0137 * \brief Sets the startColumn to \a column 0138 * \param column the column to be set 0139 */ 0140 void FITSFilter::setStartColumn(const int column) { 0141 d->startColumn = column; 0142 } 0143 0144 /*! 0145 * \brief Returns startColumn 0146 * \return The startColumn 0147 */ 0148 int FITSFilter::startColumn() const { 0149 return d->startColumn; 0150 } 0151 0152 /*! 0153 * \brief Sets the endColumn to \a column 0154 * \param column the column to be set 0155 */ 0156 void FITSFilter::setEndColumn(const int column) { 0157 d->endColumn = column; 0158 } 0159 0160 /*! 0161 * \brief Returns endColumn 0162 * \return The endColumn 0163 */ 0164 int FITSFilter::endColumn() const { 0165 return d->endColumn; 0166 } 0167 0168 /*! 0169 * \brief Sets the startRow to \a row 0170 * \param row the row to be set 0171 */ 0172 void FITSFilter::setStartRow(const int row) { 0173 d->startRow = row; 0174 } 0175 0176 /*! 0177 * \brief Returns startRow 0178 * \return The startRow 0179 */ 0180 int FITSFilter::startRow() const { 0181 return d->startRow; 0182 } 0183 0184 /*! 0185 * \brief Sets the endRow to \a row 0186 * \param row the row to be set 0187 */ 0188 void FITSFilter::setEndRow(const int row) { 0189 d->endRow = row; 0190 } 0191 0192 /*! 0193 * \brief Returns endRow 0194 * \return The endRow 0195 */ 0196 int FITSFilter::endRow() const { 0197 return d->endRow; 0198 } 0199 0200 /*! 0201 * \brief Sets commentsAsUnits to \a commentsAsUnits 0202 * 0203 * This is used when spreadsheets are exported to FITS table extensions and comments are used as the 0204 * units of the table's columns. 0205 * \param commentsAsUnits 0206 */ 0207 void FITSFilter::setCommentsAsUnits(const bool commentsAsUnits) { 0208 d->commentsAsUnits = commentsAsUnits; 0209 } 0210 0211 /*! 0212 * \brief Sets exportTo to \a exportTo 0213 * 0214 * This is used to decide whether the container should be exported to a FITS image or a FITS table 0215 * For an image \a exportTo should be 0, for a table 1 0216 * \param exportTo 0217 */ 0218 void FITSFilter::setExportTo(const int exportTo) { 0219 d->exportTo = exportTo; 0220 } 0221 0222 QString FITSFilter::fileInfoString(const QString& fileName) { 0223 const int imagesCount = FITSFilterPrivate::extensionNames(fileName).values(QLatin1String("IMAGES")).size(); 0224 QString info(i18n("Images: %1", QString::number(imagesCount))); 0225 0226 info += QLatin1String("<br>"); 0227 0228 const int tablesCount = FITSFilterPrivate::extensionNames(fileName).values(QLatin1String("TABLES")).size(); 0229 info += i18n("Tables: %1", QString::number(tablesCount)); 0230 0231 return info; 0232 } 0233 0234 // ##################################################################### 0235 // ################### Private implementation ########################## 0236 // ##################################################################### 0237 0238 FITSFilterPrivate::FITSFilterPrivate(FITSFilter* owner) 0239 : q(owner) { 0240 } 0241 0242 /*! 0243 * \brief Read the current header data unit from file \a filename in data source \a dataSource in \a importMode import mode 0244 * \param fileName the name of the file to be read 0245 * \param dataSource the data source to be filled 0246 * \param importMode 0247 */ 0248 QVector<QStringList> 0249 FITSFilterPrivate::readCHDU(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, bool* okToMatrix, int lines) { 0250 DEBUG(Q_FUNC_INFO << ", file name = " << STDSTRING(fileName)); 0251 QVector<QStringList> dataStrings; 0252 0253 #ifdef HAVE_FITS 0254 int status = 0; 0255 0256 if (fits_open_file(&m_fitsFile, qPrintable(fileName), READONLY, &status)) { 0257 DEBUG(Q_FUNC_INFO << ", ERROR opening file " << STDSTRING(fileName)); 0258 printError(status); 0259 return dataStrings; 0260 } 0261 0262 int chduType; 0263 0264 if (fits_get_hdu_type(m_fitsFile, &chduType, &status)) { 0265 printError(status); 0266 return dataStrings; 0267 } 0268 0269 long actualRows; 0270 int actualCols; 0271 int columnOffset = 0; 0272 0273 if (chduType == IMAGE_HDU) { 0274 DEBUG("IMAGE_HDU"); 0275 int maxdim = 2; 0276 int bitpix; 0277 int naxis; 0278 long naxes[2]; 0279 0280 if (fits_get_img_param(m_fitsFile, maxdim, &bitpix, &naxis, naxes, &status)) { 0281 printError(status); 0282 return dataStrings; 0283 } 0284 0285 if (naxis == 0) 0286 return dataStrings; 0287 actualRows = naxes[1]; 0288 actualCols = naxes[0]; 0289 DEBUG("rows/cols = " << actualRows << " " << actualCols) 0290 if (lines == -1) 0291 lines = actualRows; 0292 else { 0293 if (lines > actualRows) 0294 lines = actualRows; 0295 } 0296 0297 if (endRow != -1) { 0298 if (dataSource) 0299 lines = endRow; 0300 } 0301 if (endColumn != -1) 0302 actualCols = endColumn; 0303 if (!dataSource) 0304 dataStrings.reserve(lines); 0305 0306 int i = 0; 0307 int j = 0; 0308 if (startRow != 1) 0309 i = startRow; 0310 if (startColumn != 1) 0311 j = startColumn; 0312 0313 const int jstart = j; 0314 0315 // TODO: support other modes 0316 QVector<AbstractColumn::ColumnMode> columnModes; 0317 columnModes.resize(actualCols - j); 0318 QStringList vectorNames; 0319 0320 DEBUG("lines/cols = " << lines << " " << actualCols << ", i/j = " << i << " " << j) 0321 std::vector<void*> dataContainer; 0322 if (dataSource) { 0323 dataContainer.reserve(actualCols - j); 0324 columnOffset = dataSource->prepareImport(dataContainer, importMode, lines - i, actualCols - j, vectorNames, columnModes); 0325 } 0326 0327 long pixelCount = lines * actualCols; 0328 double* data = new double[pixelCount]; 0329 0330 if (!data) { 0331 DEBUG(Q_FUNC_INFO << ", Not enough memory for data"); 0332 return dataStrings; 0333 } 0334 0335 // TODO: other types 0336 if (fits_read_img(m_fitsFile, TDOUBLE, 1, pixelCount, nullptr, data, nullptr, &status)) { 0337 printError(status); 0338 return dataStrings << (QStringList() << QLatin1String("Error")); 0339 } 0340 0341 int ii = 0; 0342 DEBUG(" Import " << lines << " lines"); 0343 for (; i < lines; ++i) { 0344 int jj = 0; 0345 QStringList line; 0346 line.reserve(actualCols - j); 0347 for (; j < actualCols; ++j) { 0348 if (dataSource) 0349 static_cast<QVector<double>*>(dataContainer[jj++])->operator[](ii) = data[i * naxes[0] + j]; 0350 else 0351 line << QString::number(data[i * naxes[0] + j]); 0352 } 0353 if (!dataSource) 0354 dataStrings << line; 0355 j = jstart; 0356 ii++; 0357 } 0358 delete[] data; 0359 0360 if (dataSource) 0361 dataSource->finalizeImport(columnOffset, 1, actualCols, QString(), importMode); 0362 0363 fits_close_file(m_fitsFile, &status); 0364 0365 return dataStrings; 0366 0367 } else if ((chduType == ASCII_TBL) || (chduType == BINARY_TBL)) { 0368 DEBUG("ASCII_TBL or BINARY_TBL"); 0369 0370 if (endColumn != -1) 0371 actualCols = endColumn; 0372 else 0373 fits_get_num_cols(m_fitsFile, &actualCols, &status); 0374 0375 if (endRow != -1) 0376 actualRows = endRow; 0377 else 0378 fits_get_num_rows(m_fitsFile, &actualRows, &status); 0379 0380 QStringList columnNames; 0381 QList<int> columnsWidth; 0382 QStringList columnUnits; 0383 columnUnits.reserve(actualCols); 0384 columnsWidth.reserve(actualCols); 0385 columnNames.reserve(actualCols); 0386 int colWidth; 0387 char keyword[FLEN_KEYWORD]; 0388 char value[FLEN_VALUE]; 0389 int col = 1; 0390 if (startColumn != 1) { 0391 if (startColumn != 0) 0392 col = startColumn; 0393 } 0394 for (; col <= actualCols; ++col) { 0395 status = 0; 0396 fits_make_keyn("TTYPE", col, keyword, &status); 0397 fits_read_key(m_fitsFile, TSTRING, keyword, value, nullptr, &status); 0398 columnNames.append(QLatin1String(value)); 0399 0400 fits_make_keyn("TUNIT", col, keyword, &status); 0401 fits_read_key(m_fitsFile, TSTRING, keyword, value, nullptr, &status); 0402 columnUnits.append(QLatin1String(value)); 0403 0404 fits_get_col_display_width(m_fitsFile, col, &colWidth, &status); 0405 columnsWidth.append(colWidth); 0406 } 0407 0408 status = 0; 0409 if (lines == -1) 0410 lines = actualRows; 0411 else if (lines > actualRows) 0412 lines = actualRows; 0413 0414 if (endRow != -1) 0415 lines = endRow; 0416 QVector<QVector<QString>*> stringDataPointers; 0417 std::vector<void*> numericDataPointers; 0418 QList<bool> columnNumericTypes; 0419 0420 int startCol = 0; 0421 if (startColumn != 1) 0422 startCol = startColumn; 0423 int startRrow = 0; 0424 if (startRow != 1) 0425 startRrow = startRow; 0426 0427 columnNumericTypes.reserve(actualCols); 0428 int datatype; 0429 int c = 1; 0430 if (startColumn != 1) { 0431 if (startColumn != 0) 0432 c = startColumn; 0433 } 0434 QList<int> matrixNumericColumnIndices; 0435 for (; c <= actualCols; ++c) { 0436 fits_get_coltype(m_fitsFile, c, &datatype, nullptr, nullptr, &status); 0437 0438 switch (datatype) { 0439 case TSTRING: 0440 columnNumericTypes.append(false); 0441 break; 0442 case TSHORT: 0443 columnNumericTypes.append(true); 0444 break; 0445 case TLONG: 0446 columnNumericTypes.append(true); 0447 break; 0448 case TFLOAT: 0449 columnNumericTypes.append(true); 0450 break; 0451 case TDOUBLE: 0452 columnNumericTypes.append(true); 0453 break; 0454 case TLOGICAL: 0455 columnNumericTypes.append(false); 0456 break; 0457 case TBIT: 0458 columnNumericTypes.append(true); 0459 break; 0460 case TBYTE: 0461 columnNumericTypes.append(true); 0462 break; 0463 case TCOMPLEX: 0464 columnNumericTypes.append(true); 0465 break; 0466 default: 0467 columnNumericTypes.append(false); 0468 break; 0469 } 0470 if ((datatype != TSTRING) && (datatype != TLOGICAL)) 0471 matrixNumericColumnIndices.append(c); 0472 } 0473 0474 if (!dataSource) 0475 *okToMatrix = matrixNumericColumnIndices.isEmpty() ? false : true; 0476 else { 0477 DEBUG("HAS DataSource"); 0478 auto* spreadsheet = dynamic_cast<Spreadsheet*>(dataSource); 0479 if (spreadsheet) { 0480 numericDataPointers.reserve(actualCols - startCol); 0481 0482 stringDataPointers.reserve(actualCols - startCol); 0483 spreadsheet->setUndoAware(false); 0484 columnOffset = spreadsheet->resize(importMode, columnNames, actualCols - startCol); 0485 0486 if (importMode == AbstractFileFilter::ImportMode::Replace) { 0487 spreadsheet->clear(); 0488 spreadsheet->setRowCount(lines - startRrow); 0489 } else { 0490 if (spreadsheet->rowCount() < (lines - startRrow)) 0491 spreadsheet->setRowCount(lines - startRrow); 0492 } 0493 DEBUG("Reading columns ..."); 0494 for (int n = 0; n < actualCols - startCol; ++n) { 0495 if (columnNumericTypes.at(n)) { 0496 spreadsheet->column(columnOffset + n)->setColumnMode(AbstractColumn::ColumnMode::Double); 0497 auto* datap = static_cast<QVector<double>*>(spreadsheet->column(columnOffset + n)->data()); 0498 numericDataPointers.push_back(datap); 0499 if (importMode == AbstractFileFilter::ImportMode::Replace) 0500 datap->clear(); 0501 } else { 0502 spreadsheet->column(columnOffset + n)->setColumnMode(AbstractColumn::ColumnMode::Text); 0503 auto* list = static_cast<QVector<QString>*>(spreadsheet->column(columnOffset + n)->data()); 0504 stringDataPointers.push_back(list); 0505 if (importMode == AbstractFileFilter::ImportMode::Replace) 0506 list->clear(); 0507 } 0508 } 0509 DEBUG(" ... DONE"); 0510 stringDataPointers.squeeze(); 0511 } else { 0512 numericDataPointers.reserve(matrixNumericColumnIndices.size()); 0513 0514 columnOffset = dataSource->prepareImport(numericDataPointers, importMode, lines - startRrow, matrixNumericColumnIndices.size()); 0515 } 0516 } 0517 0518 int row = 1; 0519 if (startRow != 1) { 0520 if (startRow != 0) 0521 row = startRow; 0522 } 0523 0524 int coll = 1; 0525 if (startColumn != 1) { 0526 if (startColumn != 0) 0527 coll = startColumn; 0528 } 0529 bool isMatrix = false; 0530 if (dynamic_cast<Matrix*>(dataSource)) { 0531 isMatrix = true; 0532 coll = matrixNumericColumnIndices.first(); 0533 actualCols = matrixNumericColumnIndices.last(); 0534 if (importMode == AbstractFileFilter::ImportMode::Replace) { 0535 for (auto* col : numericDataPointers) 0536 static_cast<QVector<double>*>(col)->clear(); 0537 } 0538 } 0539 0540 char array[FLEN_VALUE]; 0541 char* tmpArr[1] = {array}; 0542 for (; row <= lines; ++row) { 0543 int numericixd = 0; 0544 int stringidx = 0; 0545 QStringList line; 0546 line.reserve(actualCols - coll); 0547 for (int col = coll; col <= actualCols; ++col) { 0548 if (isMatrix) { 0549 if (!matrixNumericColumnIndices.contains(col)) 0550 continue; 0551 } 0552 if (fits_read_col_str(m_fitsFile, col, row, 1, 1, nullptr, tmpArr, nullptr, &status)) 0553 printError(status); 0554 if (dataSource) { 0555 QString str = QString::fromLatin1(array); 0556 if (str.isEmpty()) { 0557 if (columnNumericTypes.at(col - 1)) 0558 static_cast<QVector<double>*>(numericDataPointers[numericixd++])->push_back(0); 0559 else 0560 stringDataPointers[stringidx++]->append(QLatin1String("NULL")); 0561 } else { 0562 if (columnNumericTypes.at(col - 1)) 0563 static_cast<QVector<double>*>(numericDataPointers[numericixd++])->push_back(str.toDouble()); 0564 else { 0565 if (!stringDataPointers.isEmpty()) 0566 stringDataPointers[stringidx++]->operator<<(str.simplified()); 0567 } 0568 } 0569 } else { 0570 QString tmpColstr = QString::fromLatin1(array); 0571 tmpColstr = tmpColstr.simplified(); 0572 if (tmpColstr.isEmpty()) 0573 line << QLatin1String("NULL"); 0574 else 0575 line << tmpColstr; 0576 } 0577 } 0578 if (!dataSource) 0579 dataStrings << line; 0580 } 0581 0582 if (dataSource) 0583 dataSource->finalizeImport(columnOffset, 1, actualCols, QString(), importMode); 0584 0585 fits_close_file(m_fitsFile, &status); 0586 return dataStrings; 0587 } else 0588 DEBUG(Q_FUNC_INFO << ", Incorrect header type"); 0589 0590 fits_close_file(m_fitsFile, &status); 0591 0592 #else 0593 Q_UNUSED(fileName) 0594 Q_UNUSED(dataSource) 0595 Q_UNUSED(importMode) 0596 Q_UNUSED(okToMatrix) 0597 Q_UNUSED(lines) 0598 #endif 0599 return dataStrings; 0600 } 0601 0602 /*! 0603 * \brief Export from data source \a dataSource to file \a fileName 0604 * \param fileName the name of the file to be exported to 0605 * \param dataSource the datasource whose data is exported 0606 */ 0607 0608 void FITSFilterPrivate::writeCHDU(const QString& fileName, AbstractDataSource* dataSource) { 0609 #ifdef HAVE_FITS 0610 if (!fileName.endsWith(QLatin1String(".fits"))) 0611 return; 0612 int status = 0; 0613 bool existed = false; 0614 if (!QFile::exists(fileName)) { 0615 if (fits_create_file(&m_fitsFile, qPrintable(fileName), &status)) { 0616 printError(status); 0617 qDebug() << fileName; 0618 return; 0619 } 0620 } else { 0621 if (fits_open_file(&m_fitsFile, qPrintable(fileName), READWRITE, &status)) { 0622 printError(status); 0623 return; 0624 } else 0625 existed = true; 0626 } 0627 0628 Matrix* const matrix = dynamic_cast<Matrix*>(dataSource); 0629 if (matrix) { 0630 // FITS image 0631 if (exportTo == 0) { 0632 long naxes[2] = {matrix->columnCount(), matrix->rowCount()}; 0633 if (fits_create_img(m_fitsFile, FLOAT_IMG, 2, naxes, &status)) { 0634 printError(status); 0635 status = 0; 0636 fits_close_file(m_fitsFile, &status); 0637 return; 0638 } 0639 const long nelem = naxes[0] * naxes[1]; 0640 double* const array = new double[nelem]; 0641 const QVector<QVector<double>>* const data = static_cast<QVector<QVector<double>>*>(matrix->data()); 0642 0643 for (int col = 0; col < naxes[0]; ++col) 0644 for (int row = 0; row < naxes[1]; ++row) 0645 array[row * naxes[0] + col] = data->at(row).at(col); 0646 0647 if (fits_write_img(m_fitsFile, TDOUBLE, 1, nelem, array, &status)) { 0648 printError(status); 0649 status = 0; 0650 } 0651 0652 fits_close_file(m_fitsFile, &status); 0653 delete[] array; 0654 // FITS table 0655 } else { 0656 const int nrows = matrix->rowCount(); 0657 const int tfields = matrix->columnCount(); 0658 QVector<char*> columnNames; 0659 columnNames.resize(tfields); 0660 columnNames.squeeze(); 0661 QVector<char*> tform; 0662 tform.resize(tfields); 0663 tform.squeeze(); 0664 // TODO: mode 0665 const QVector<QVector<double>>* const matrixData = static_cast<QVector<QVector<double>>*>(matrix->data()); 0666 const MatrixModel* matrixModel = static_cast<MatrixView*>(matrix->view())->model(); 0667 const int precision = matrix->precision(); 0668 for (int i = 0; i < tfields; ++i) { 0669 const QString& columnName = matrixModel->headerData(i, Qt::Horizontal).toString(); 0670 columnNames[i] = new char[columnName.size() + 1]; 0671 strcpy(columnNames[i], columnName.toLatin1().constData()); 0672 int maxSize = -1; 0673 for (int row = 0; row < nrows; ++row) { 0674 if (matrix->text<double>(row, i).size() > maxSize) 0675 maxSize = matrix->text<double>(row, i).size(); 0676 } 0677 QString tformn; 0678 if (precision > 0) { 0679 tformn = QLatin1String("F") + QString::number(maxSize) + QLatin1String(".") + QString::number(precision); 0680 } else 0681 tformn = QLatin1String("F") + QString::number(maxSize) + QLatin1String(".0"); 0682 tform[i] = new char[tformn.size() + 1]; 0683 strcpy(tform[i], tformn.toLatin1().constData()); 0684 } 0685 // TODO extension name containing[] ? 0686 0687 int r = fits_create_tbl(m_fitsFile, 0688 ASCII_TBL, 0689 nrows, 0690 tfields, 0691 columnNames.data(), 0692 tform.data(), 0693 nullptr, 0694 matrix->name().toLatin1().constData(), 0695 &status); 0696 for (int i = 0; i < tfields; ++i) { 0697 delete[] tform[i]; 0698 delete[] columnNames[i]; 0699 } 0700 if (r) { 0701 printError(status); 0702 status = 0; 0703 fits_close_file(m_fitsFile, &status); 0704 if (!existed) { 0705 QFile file(fileName); 0706 file.remove(); 0707 } 0708 return; 0709 } 0710 0711 double* columnNumeric = new double[nrows]; 0712 for (int col = 1; col <= tfields; ++col) { 0713 const QVector<double>& column = matrixData->at(col - 1); 0714 for (int r = 0; r < column.size(); ++r) 0715 columnNumeric[r] = column.at(r); 0716 0717 fits_write_col(m_fitsFile, TDOUBLE, col, 1, 1, nrows, columnNumeric, &status); 0718 if (status) { 0719 printError(status); 0720 delete[] columnNumeric; 0721 status = 0; 0722 if (!existed) { 0723 QFile file(fileName); 0724 file.remove(); 0725 } 0726 0727 fits_close_file(m_fitsFile, &status); 0728 return; 0729 } 0730 } 0731 delete[] columnNumeric; 0732 fits_close_file(m_fitsFile, &status); 0733 } 0734 return; 0735 } 0736 0737 auto* const spreadsheet = dynamic_cast<Spreadsheet*>(dataSource); 0738 if (spreadsheet) { 0739 // FITS image 0740 if (exportTo == 0) { 0741 int maxRowIdx = -1; 0742 // don't export lots of empty lines if all of those contain nans 0743 // TODO: option? 0744 for (int c = 0; c < spreadsheet->columnCount(); ++c) { 0745 const Column* const col = spreadsheet->column(c); 0746 int currMaxRoxIdx = -1; 0747 for (int r = col->rowCount(); r >= 0; --r) { 0748 if (col->isValid(r)) { 0749 currMaxRoxIdx = r; 0750 break; 0751 } 0752 } 0753 0754 if (currMaxRoxIdx > maxRowIdx) { 0755 maxRowIdx = currMaxRoxIdx; 0756 } 0757 } 0758 long naxes[2] = {spreadsheet->columnCount(), maxRowIdx + 1}; 0759 if (fits_create_img(m_fitsFile, FLOAT_IMG, 2, naxes, &status)) { 0760 printError(status); 0761 status = 0; 0762 fits_close_file(m_fitsFile, &status); 0763 if (!existed) { 0764 QFile file(fileName); 0765 file.remove(); 0766 } 0767 return; 0768 } 0769 const long nelem = naxes[0] * naxes[1]; 0770 double* array = new double[nelem]; 0771 0772 for (int row = 0; row < naxes[1]; ++row) { 0773 for (int col = 0; col < naxes[0]; ++col) 0774 array[row * naxes[0] + col] = spreadsheet->column(col)->valueAt(row); 0775 } 0776 0777 if (fits_write_img(m_fitsFile, TDOUBLE, 1, nelem, array, &status)) { 0778 printError(status); 0779 status = 0; 0780 fits_close_file(m_fitsFile, &status); 0781 if (!existed) { 0782 QFile file(fileName); 0783 file.remove(); 0784 } 0785 return; 0786 } 0787 0788 fits_close_file(m_fitsFile, &status); 0789 delete[] array; 0790 } else { 0791 const int nrows = spreadsheet->rowCount(); 0792 const int tfields = spreadsheet->columnCount(); 0793 0794 QVector<char*> columnNames; 0795 columnNames.resize(tfields); 0796 columnNames.squeeze(); 0797 QVector<char*> tform; 0798 tform.resize(tfields); 0799 tform.squeeze(); 0800 QVector<char*> tunit; 0801 tunit.resize(tfields); 0802 tunit.squeeze(); 0803 0804 for (int i = 0; i < tfields; ++i) { 0805 const Column* const column = spreadsheet->column(i); 0806 0807 columnNames[i] = new char[column->name().size() + 1]; 0808 strcpy(columnNames[i], column->name().toLatin1().constData()); 0809 if (commentsAsUnits) { 0810 tunit[i] = new char[column->comment().size() + 1]; 0811 strcpy(tunit[i], column->comment().toLatin1().constData()); 0812 } else { 0813 tunit[i] = new char[1]; 0814 tunit[i][0] = '\0'; 0815 } 0816 switch (column->columnMode()) { 0817 case AbstractColumn::ColumnMode::Double: { 0818 int maxSize = -1; 0819 for (int row = 0; row < nrows; ++row) { 0820 if (QString::number(column->valueAt(row)).size() > maxSize) 0821 maxSize = QString::number(column->valueAt(row)).size(); 0822 } 0823 0824 const Double2StringFilter* const filter = static_cast<Double2StringFilter*>(column->outputFilter()); 0825 bool decimals = false; 0826 for (int ii = 0; ii < nrows; ++ii) { 0827 bool ok; 0828 QString cell = column->asStringColumn()->textAt(ii); 0829 double val = cell.toDouble(&ok); 0830 if (cell.size() > QString::number(val).size() + 1) { 0831 decimals = true; 0832 break; 0833 } 0834 } 0835 QString tformn; 0836 if (decimals) { 0837 int maxStringSize = -1; 0838 for (int row = 0; row < nrows; ++row) { 0839 if (column->asStringColumn()->textAt(row).size() > maxStringSize) 0840 maxStringSize = column->asStringColumn()->textAt(row).size(); 0841 } 0842 const int diff = abs(maxSize - maxStringSize); 0843 maxSize += diff; 0844 tformn = QLatin1String("F") + QString::number(maxSize) + QLatin1String(".") + QString::number(filter->numDigits()); 0845 } else 0846 tformn = QLatin1String("F") + QString::number(maxSize) + QLatin1String(".0"); 0847 tform[i] = new char[tformn.size()]; 0848 strcpy(tform[i], tformn.toLatin1().data()); 0849 break; 0850 } 0851 case AbstractColumn::ColumnMode::Text: { 0852 int maxSize = -1; 0853 for (int row = 0; row < nrows; ++row) { 0854 if (column->textAt(row).size() > maxSize) 0855 maxSize = column->textAt(row).size(); 0856 } 0857 const QString& tformn = QLatin1String("A") + QString::number(maxSize); 0858 tform[i] = new char[tformn.size()]; 0859 strcpy(tform[i], tformn.toLatin1().data()); 0860 break; 0861 } 0862 case AbstractColumn::ColumnMode::Integer: // TODO 0863 case AbstractColumn::ColumnMode::BigInt: 0864 case AbstractColumn::ColumnMode::DateTime: 0865 case AbstractColumn::ColumnMode::Day: 0866 case AbstractColumn::ColumnMode::Month: 0867 break; 0868 } 0869 } 0870 // TODO extension name containing[] ? 0871 0872 int r = fits_create_tbl(m_fitsFile, 0873 ASCII_TBL, 0874 nrows, 0875 tfields, 0876 columnNames.data(), 0877 tform.data(), 0878 tunit.data(), 0879 spreadsheet->name().toLatin1().constData(), 0880 &status); 0881 for (int i = 0; i < tfields; ++i) { 0882 delete[] tform[i]; 0883 delete[] tunit[i]; 0884 delete[] columnNames[i]; 0885 } 0886 if (r) { 0887 printError(status); 0888 status = 0; 0889 fits_close_file(m_fitsFile, &status); 0890 if (!existed) { 0891 QFile file(fileName); 0892 file.remove(); 0893 } 0894 return; 0895 } 0896 0897 QVector<char*> column; 0898 column.resize(nrows); 0899 column.squeeze(); 0900 0901 double* columnNumeric = new double[nrows]; 0902 for (int col = 1; col <= tfields; ++col) { 0903 const Column* c = spreadsheet->column(col - 1); 0904 auto columnMode = c->columnMode(); 0905 0906 if (columnMode == AbstractColumn::ColumnMode::Double) { 0907 for (int row = 0; row < nrows; ++row) 0908 columnNumeric[row] = c->valueAt(row); 0909 0910 fits_write_col(m_fitsFile, TDOUBLE, col, 1, 1, nrows, columnNumeric, &status); 0911 if (status) { 0912 printError(status); 0913 delete[] columnNumeric; 0914 status = 0; 0915 fits_close_file(m_fitsFile, &status); 0916 if (!existed) { 0917 QFile file(fileName); 0918 file.remove(); 0919 } 0920 return; 0921 } 0922 } else { 0923 for (int row = 0; row < nrows; ++row) { 0924 column[row] = new char[c->textAt(row).size() + 1]; 0925 strcpy(column[row], c->textAt(row).toLatin1().constData()); 0926 } 0927 fits_write_col(m_fitsFile, TSTRING, col, 1, 1, nrows, column.data(), &status); 0928 for (int row = 0; row < nrows; ++row) 0929 delete[] column[row]; 0930 if (status) { 0931 printError(status); 0932 status = 0; 0933 fits_close_file(m_fitsFile, &status); 0934 0935 delete[] columnNumeric; 0936 return; 0937 } 0938 } 0939 } 0940 0941 delete[] columnNumeric; 0942 0943 status = 0; 0944 fits_close_file(m_fitsFile, &status); 0945 } 0946 } 0947 #else 0948 Q_UNUSED(fileName) 0949 Q_UNUSED(dataSource) 0950 #endif 0951 } 0952 0953 /*! 0954 * \brief Return a map of the available extensions names in file \a filename 0955 * The keys of the map are the extension types, the values are the names 0956 * \param fileName the name of the FITS file to be analyzed 0957 */ 0958 QMultiMap<QString, QString> FITSFilterPrivate::extensionNames(const QString& fileName) { 0959 DEBUG(Q_FUNC_INFO << ", file name = " << STDSTRING(fileName)); 0960 #ifdef HAVE_FITS 0961 QMultiMap<QString, QString> extensions; 0962 int status = 0; 0963 fitsfile* fitsFile = nullptr; 0964 if (fits_open_file(&fitsFile, qPrintable(fileName), READONLY, &status)) 0965 return {}; 0966 int hduCount; 0967 0968 if (fits_get_num_hdus(fitsFile, &hduCount, &status)) 0969 return {}; 0970 int imageCount = 0; 0971 int asciiTableCount = 0; 0972 int binaryTableCount = 0; 0973 for (int currentHDU = 1; (currentHDU <= hduCount) && !status; ++currentHDU) { 0974 int hduType; 0975 status = 0; 0976 0977 fits_get_hdu_type(fitsFile, &hduType, &status); 0978 switch (hduType) { 0979 case IMAGE_HDU: 0980 imageCount++; 0981 break; 0982 case ASCII_TBL: 0983 asciiTableCount++; 0984 break; 0985 case BINARY_TBL: 0986 binaryTableCount++; 0987 break; 0988 } 0989 char* keyVal = new char[FLEN_VALUE]; 0990 QString extName; 0991 if (!fits_read_keyword(fitsFile, "EXTNAME", keyVal, nullptr, &status)) { 0992 extName = QLatin1String(keyVal); 0993 extName = extName.mid(1, extName.length() - 2).simplified(); 0994 } else { 0995 status = 0; 0996 if (!fits_read_keyword(fitsFile, "HDUNAME", keyVal, nullptr, &status)) { 0997 extName = QLatin1String(keyVal); 0998 extName = extName.mid(1, extName.length() - 2).simplified(); 0999 } else { 1000 status = 0; 1001 switch (hduType) { 1002 case IMAGE_HDU: 1003 if (imageCount == 1) 1004 extName = i18n("Primary header"); 1005 else 1006 extName = i18n("IMAGE #%1", imageCount); 1007 break; 1008 case ASCII_TBL: 1009 extName = i18n("ASCII_TBL #%1", asciiTableCount); 1010 break; 1011 case BINARY_TBL: 1012 extName = i18n("BINARY_TBL #%1", binaryTableCount); 1013 break; 1014 } 1015 } 1016 } 1017 delete[] keyVal; 1018 status = 0; 1019 extName = extName.trimmed(); 1020 switch (hduType) { 1021 case IMAGE_HDU: 1022 extensions.insert(QLatin1String("IMAGES"), extName); 1023 break; 1024 case ASCII_TBL: 1025 extensions.insert(QLatin1String("TABLES"), extName); 1026 break; 1027 case BINARY_TBL: 1028 extensions.insert(QLatin1String("TABLES"), extName); 1029 break; 1030 } 1031 fits_movrel_hdu(fitsFile, 1, nullptr, &status); 1032 } 1033 1034 if (status == END_OF_FILE) 1035 status = 0; 1036 1037 fits_close_file(fitsFile, &status); 1038 return extensions; 1039 #else 1040 Q_UNUSED(fileName) 1041 return {}; 1042 #endif 1043 } 1044 1045 /*! 1046 * \brief Prints the error text corresponding to the status code \a status 1047 * \param status the status code of the error 1048 */ 1049 void FITSFilterPrivate::printError(int status) const { 1050 #ifdef HAVE_FITS 1051 if (status) { 1052 char errorText[FLEN_ERRMSG]; 1053 fits_get_errstatus(status, errorText); 1054 qDebug() << QLatin1String(errorText); 1055 } 1056 #else 1057 Q_UNUSED(status) 1058 #endif 1059 } 1060 1061 /*! 1062 * \brief Add the keywords \a keywords to the current header unit 1063 * \param keywords the keywords to be added 1064 * \param fileName the name of the FITS file (extension) in which the keywords are added 1065 */ 1066 1067 void FITSFilterPrivate::addNewKeyword(const QString& fileName, const QList<FITSFilter::Keyword>& keywords) { 1068 #ifdef HAVE_FITS 1069 int status = 0; 1070 if (fits_open_file(&m_fitsFile, qPrintable(fileName), READWRITE, &status)) { 1071 printError(status); 1072 return; 1073 } 1074 for (const FITSFilter::Keyword& keyword : keywords) { 1075 status = 0; 1076 if (!keyword.key.compare(QLatin1String("COMMENT"))) { 1077 if (fits_write_comment(m_fitsFile, qPrintable(keyword.value), &status)) 1078 printError(status); 1079 } else if (!keyword.key.compare(QLatin1String("HISTORY"))) { 1080 if (fits_write_history(m_fitsFile, qPrintable(keyword.value), &status)) 1081 printError(status); 1082 } else if (!keyword.key.compare(QLatin1String("DATE"))) { 1083 if (fits_write_date(m_fitsFile, &status)) 1084 printError(status); 1085 } else { 1086 int ok = 0; 1087 if (keyword.key.length() <= FLEN_KEYWORD) { 1088 ok++; 1089 if (keyword.value.length() <= FLEN_VALUE) { 1090 ok++; 1091 if (keyword.comment.length() <= FLEN_COMMENT) 1092 ok++; 1093 } 1094 } 1095 if (ok == 3) { 1096 bool ok; 1097 double val = keyword.value.toDouble(&ok); 1098 if (ok) { 1099 if (fits_write_key(m_fitsFile, TDOUBLE, keyword.key.toLatin1().data(), &val, keyword.comment.toLatin1().data(), &status)) 1100 printError(status); 1101 } else { 1102 if (fits_write_key(m_fitsFile, 1103 TSTRING, 1104 keyword.key.toLatin1().data(), 1105 keyword.value.toLatin1().data(), 1106 keyword.comment.toLatin1().data(), 1107 &status)) 1108 printError(status); 1109 } 1110 } else if (ok == 2) { 1111 // comment too long 1112 } else if (ok == 1) { 1113 // value too long 1114 } else { 1115 // keyword too long 1116 } 1117 } 1118 } 1119 status = 0; 1120 fits_close_file(m_fitsFile, &status); 1121 #else 1122 Q_UNUSED(keywords) 1123 Q_UNUSED(fileName) 1124 1125 #endif 1126 } 1127 /*! 1128 * \brief Update keywords in the current header unit 1129 * \param fileName The name of the FITS file (extension) in which the keywords will be updated 1130 * \param originals The original keywords of the FITS file (extension) 1131 * \param updates The keywords that contain the updated values 1132 */ 1133 void FITSFilterPrivate::updateKeywords(const QString& fileName, const QList<FITSFilter::Keyword>& originals, const QVector<FITSFilter::Keyword>& updates) { 1134 #ifdef HAVE_FITS 1135 int status = 0; 1136 if (fits_open_file(&m_fitsFile, qPrintable(fileName), READWRITE, &status)) { 1137 printError(status); 1138 return; 1139 } 1140 FITSFilter::Keyword updatedKeyword; 1141 FITSFilter::Keyword originalKeyword; 1142 FITSFilter::KeywordUpdate keywordUpdate; 1143 1144 for (int i = 0; i < updates.size(); ++i) { 1145 updatedKeyword = updates.at(i); 1146 originalKeyword = originals.at(i); 1147 keywordUpdate = originals.at(i).updates; 1148 if (keywordUpdate.keyUpdated && keywordUpdate.valueUpdated && keywordUpdate.commentUpdated) { 1149 if (updatedKeyword.isEmpty()) { 1150 if (fits_delete_key(m_fitsFile, qPrintable(originalKeyword.key), &status)) { 1151 printError(status); 1152 status = 0; 1153 } 1154 continue; 1155 } 1156 } 1157 if (!updatedKeyword.key.isEmpty()) { 1158 if (fits_modify_name(m_fitsFile, qPrintable(originalKeyword.key), qPrintable(updatedKeyword.key), &status)) { 1159 printError(status); 1160 status = 0; 1161 } 1162 } 1163 1164 if (!updatedKeyword.value.isEmpty()) { 1165 bool ok; 1166 int intValue; 1167 double doubleValue; 1168 bool updated = false; 1169 1170 doubleValue = updatedKeyword.value.toDouble(&ok); 1171 if (ok) { 1172 if (fits_update_key(m_fitsFile, 1173 TDOUBLE, 1174 keywordUpdate.keyUpdated ? qPrintable(updatedKeyword.key) : qPrintable(originalKeyword.key), 1175 &doubleValue, 1176 nullptr, 1177 &status)) 1178 printError(status); 1179 else 1180 updated = true; 1181 } 1182 if (!updated) { 1183 intValue = updatedKeyword.value.toInt(&ok); 1184 if (ok) { 1185 if (fits_update_key(m_fitsFile, 1186 TINT, 1187 keywordUpdate.keyUpdated ? qPrintable(updatedKeyword.key) : qPrintable(originalKeyword.key), 1188 &intValue, 1189 nullptr, 1190 &status)) 1191 printError(status); 1192 else 1193 updated = true; 1194 } 1195 } 1196 if (!updated) { 1197 if (fits_update_key(m_fitsFile, 1198 TSTRING, 1199 keywordUpdate.keyUpdated ? qPrintable(updatedKeyword.key) : qPrintable(originalKeyword.key), 1200 updatedKeyword.value.toLatin1().data(), 1201 nullptr, 1202 &status)) 1203 printError(status); 1204 } 1205 } else { 1206 if (keywordUpdate.valueUpdated) { 1207 if (fits_update_key_null(m_fitsFile, 1208 keywordUpdate.keyUpdated ? qPrintable(updatedKeyword.key) : qPrintable(originalKeyword.key), 1209 nullptr, 1210 &status)) { 1211 printError(status); 1212 status = 0; 1213 } 1214 } 1215 } 1216 1217 if (!updatedKeyword.comment.isEmpty()) { 1218 if (fits_modify_comment(m_fitsFile, 1219 keywordUpdate.keyUpdated ? qPrintable(updatedKeyword.key) : qPrintable(originalKeyword.key), 1220 updatedKeyword.comment.toLatin1().data(), 1221 &status)) { 1222 printError(status); 1223 status = 0; 1224 } 1225 } else { 1226 if (keywordUpdate.commentUpdated) { 1227 if (fits_modify_comment(m_fitsFile, 1228 keywordUpdate.keyUpdated ? qPrintable(updatedKeyword.key) : qPrintable(originalKeyword.key), 1229 QByteArray().constData(), 1230 &status)) { 1231 printError(status); 1232 status = 0; 1233 } 1234 } 1235 } 1236 } 1237 status = 0; 1238 fits_close_file(m_fitsFile, &status); 1239 #else 1240 Q_UNUSED(fileName) 1241 Q_UNUSED(originals) 1242 Q_UNUSED(updates) 1243 #endif 1244 } 1245 1246 /*! 1247 * \brief Delete the keywords \a keywords from the current header unit 1248 * \param fileName the name of the FITS file (extension) in which the keywords will be deleted. 1249 * \param keywords the keywords to deleted 1250 */ 1251 1252 void FITSFilterPrivate::deleteKeyword(const QString& fileName, const QList<FITSFilter::Keyword>& keywords) { 1253 #ifdef HAVE_FITS 1254 int status = 0; 1255 if (fits_open_file(&m_fitsFile, qPrintable(fileName), READWRITE, &status)) { 1256 printError(status); 1257 return; 1258 } 1259 for (const auto& keyword : keywords) { 1260 if (!keyword.key.isEmpty()) { 1261 status = 0; 1262 if (fits_delete_key(m_fitsFile, qPrintable(keyword.key), &status)) 1263 printError(status); 1264 } 1265 } 1266 status = 0; 1267 fits_close_file(m_fitsFile, &status); 1268 #else 1269 Q_UNUSED(keywords) 1270 Q_UNUSED(fileName) 1271 #endif 1272 } 1273 1274 /*! 1275 * \brief FITSFilterPrivate::addKeywordUnit 1276 * \param fileName the FITS file (extension) in which the keyword units are updated/added 1277 * \param keywords the keywords whose units were modified/added 1278 */ 1279 1280 void FITSFilterPrivate::addKeywordUnit(const QString& fileName, const QList<FITSFilter::Keyword>& keywords) { 1281 #ifdef HAVE_FITS 1282 int status = 0; 1283 if (fits_open_file(&m_fitsFile, qPrintable(fileName), READWRITE, &status)) { 1284 printError(status); 1285 return; 1286 } 1287 1288 for (const FITSFilter::Keyword& keyword : keywords) { 1289 if (keyword.updates.unitUpdated) { 1290 if (fits_write_key_unit(m_fitsFile, qPrintable(keyword.key), keyword.unit.toLatin1().data(), &status)) { 1291 printError(status); 1292 status = 0; 1293 } 1294 } 1295 } 1296 status = 0; 1297 fits_close_file(m_fitsFile, &status); 1298 #else 1299 Q_UNUSED(fileName) 1300 Q_UNUSED(keywords) 1301 #endif 1302 } 1303 1304 /*! 1305 * \brief Remove extensions from a FITS file 1306 * \param extensions The extensions to be removed from the FITS file 1307 */ 1308 void FITSFilterPrivate::removeExtensions(const QStringList& extensions) { 1309 #ifdef HAVE_FITS 1310 int status = 0; 1311 for (const auto& ext : extensions) { 1312 status = 0; 1313 if (fits_open_file(&m_fitsFile, qPrintable(ext), READWRITE, &status)) { 1314 printError(status); 1315 continue; 1316 } 1317 1318 if (fits_delete_hdu(m_fitsFile, nullptr, &status)) 1319 printError(status); 1320 1321 status = 0; 1322 fits_close_file(m_fitsFile, &status); 1323 } 1324 #else 1325 Q_UNUSED(extensions) 1326 #endif 1327 } 1328 1329 /*! 1330 * \brief Returns a list of keywords in the current header of \a fileName 1331 * \param fileName the name of the FITS file (extension) to be opened 1332 * \return A list of keywords 1333 */ 1334 QList<FITSFilter::Keyword> FITSFilterPrivate::chduKeywords(const QString& fileName) { 1335 #ifdef HAVE_FITS 1336 int status = 0; 1337 1338 if (fits_open_file(&m_fitsFile, qPrintable(fileName), READONLY, &status)) { 1339 printError(status); 1340 return {}; 1341 } 1342 int numberOfKeys; 1343 if (fits_get_hdrspace(m_fitsFile, &numberOfKeys, nullptr, &status)) { 1344 printError(status); 1345 return {}; 1346 } 1347 1348 QList<FITSFilter::Keyword> keywords; 1349 keywords.reserve(numberOfKeys); 1350 char* key = new char[FLEN_KEYWORD]; 1351 char* value = new char[FLEN_VALUE]; 1352 char* comment = new char[FLEN_COMMENT]; 1353 char* unit = new char[FLEN_VALUE]; 1354 for (int i = 1; i <= numberOfKeys; ++i) { 1355 QStringList recordValues; 1356 FITSFilter::Keyword keyword; 1357 1358 if (fits_read_keyn(m_fitsFile, i, key, value, comment, &status)) { 1359 printError(status); 1360 status = 0; 1361 continue; 1362 } 1363 1364 fits_read_key_unit(m_fitsFile, key, unit, &status); 1365 1366 recordValues << QLatin1String(key) << QLatin1String(value) << QLatin1String(comment) << QLatin1String(unit); 1367 1368 keyword.key = recordValues[0].simplified(); 1369 keyword.value = recordValues[1].simplified(); 1370 keyword.comment = recordValues[2].simplified(); 1371 keyword.unit = recordValues[3].simplified(); 1372 1373 keywords.append(keyword); 1374 } 1375 delete[] key; 1376 delete[] value; 1377 delete[] comment; 1378 delete[] unit; 1379 1380 fits_close_file(m_fitsFile, &status); 1381 1382 return keywords; 1383 #else 1384 Q_UNUSED(fileName) 1385 return QList<FITSFilter::Keyword>(); 1386 #endif 1387 } 1388 1389 /*! 1390 * \brief Builds the table \a headerEditTable from the keywords \a keys 1391 * \param fileName The name of the FITS file from which the keys are read if \a readKeys is \c true 1392 * \param headerEditTable The table to be built 1393 * \param readKeys It's used to determine whether the keywords are provided or they should be read from 1394 * file \a fileName 1395 * \param keys The keywords that are provided if the keywords were read already. 1396 */ 1397 void FITSFilterPrivate::parseHeader(const QString& fileName, QTableWidget* headerEditTable, bool readKeys, const QList<FITSFilter::Keyword>& keys) { 1398 DEBUG(Q_FUNC_INFO) 1399 #ifdef HAVE_FITS 1400 QList<FITSFilter::Keyword> keywords; 1401 if (readKeys) 1402 keywords = chduKeywords(fileName); 1403 else 1404 keywords = keys; 1405 1406 headerEditTable->setRowCount(keywords.size()); 1407 QTableWidgetItem* item; 1408 for (int i = 0; i < keywords.size(); ++i) { 1409 const FITSFilter::Keyword& keyword = keywords.at(i); 1410 const bool mandatory = 1411 FITSFilter::mandatoryImageExtensionKeywords().contains(keyword.key) || FITSFilter::mandatoryTableExtensionKeywords().contains(keyword.key); 1412 item = new QTableWidgetItem(keyword.key); 1413 const QString& itemText = item->text(); 1414 const bool notEditableKey = mandatory || itemText.contains(QLatin1String("TFORM")) || itemText.contains(QLatin1String("TTYPE")) 1415 || itemText.contains(QLatin1String("TUNIT")) || itemText.contains(QLatin1String("TDISP")) || itemText.contains(QLatin1String("TBCOL")) 1416 || itemText.contains(QLatin1String("TZERO")); 1417 const bool notEditableValue = mandatory || itemText.contains(QLatin1String("TFORM")) || itemText.contains(QLatin1String("TDISP")) 1418 || itemText.contains(QLatin1String("TBCOL")) || itemText.contains(QLatin1String("TZERO")); 1419 1420 if (notEditableKey) 1421 item->setFlags(item->flags() & ~Qt::ItemIsEditable); 1422 else 1423 item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); 1424 headerEditTable->setItem(i, 0, item); 1425 1426 item = new QTableWidgetItem(keyword.value); 1427 if (notEditableValue) 1428 item->setFlags(item->flags() & ~Qt::ItemIsEditable); 1429 else 1430 item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); 1431 headerEditTable->setItem(i, 1, item); 1432 QString commentFieldText; 1433 if (!keyword.unit.isEmpty()) { 1434 if (keyword.updates.unitUpdated) { 1435 const QString& comment = keyword.comment.right(keyword.comment.size() - keyword.comment.indexOf(QLatin1Char(']')) - 1); 1436 commentFieldText = QLatin1String("[") + keyword.unit + QLatin1String("] ") + comment; 1437 } else { 1438 if (keyword.comment.at(0) == QLatin1Char('[')) 1439 commentFieldText = keyword.comment; 1440 else 1441 commentFieldText = QStringLiteral("[") + keyword.unit + QStringLiteral("] ") + keyword.comment; 1442 } 1443 } else 1444 commentFieldText = keyword.comment; 1445 item = new QTableWidgetItem(commentFieldText); 1446 1447 item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); 1448 headerEditTable->setItem(i, 2, item); 1449 } 1450 1451 headerEditTable->resizeColumnsToContents(); 1452 #else 1453 Q_UNUSED(fileName) 1454 Q_UNUSED(headerEditTable) 1455 Q_UNUSED(readKeys) 1456 Q_UNUSED(keys) 1457 #endif 1458 } 1459 1460 /*! 1461 * \brief Helper function to return the value of the key \a key 1462 * \param fileName The name of the FITS file (extension) in which the keyword with key \a key should exist 1463 * \param key The key of the keyword whose value it's returned 1464 * \return The value of the keyword as a string 1465 */ 1466 const QString FITSFilterPrivate::valueOf(const QString& fileName, const char* key) { 1467 #ifdef HAVE_FITS 1468 int status = 0; 1469 if (fits_open_file(&m_fitsFile, qPrintable(fileName), READONLY, &status)) { 1470 printError(status); 1471 return {}; 1472 } 1473 1474 char* keyVal = new char[FLEN_VALUE]; 1475 QString keyValue; 1476 if (!fits_read_keyword(m_fitsFile, key, keyVal, nullptr, &status)) { 1477 keyValue = QLatin1String(keyVal); 1478 keyValue = keyValue.simplified(); 1479 } else { 1480 printError(status); 1481 delete[] keyVal; 1482 fits_close_file(m_fitsFile, &status); 1483 return {}; 1484 } 1485 1486 delete[] keyVal; 1487 status = 0; 1488 fits_close_file(m_fitsFile, &status); 1489 return keyValue; 1490 #else 1491 Q_UNUSED(fileName) 1492 Q_UNUSED(key) 1493 return {}; 1494 #endif 1495 } 1496 1497 /*! 1498 * \brief Build the extensions tree from FITS file (extension) \a fileName 1499 * \param fileName The name of the FITS file to be opened 1500 * \param tw The QTreeWidget to be built 1501 * \param checkPrimary Used to determine whether the tree will be used for import or the header edit, 1502 * if it's \c true and if the primary array it's empty, then the item won't be added to the tree 1503 */ 1504 void FITSFilterPrivate::parseExtensions(const QString& fileName, QTreeWidget* tw, bool checkPrimary) { 1505 DEBUG(Q_FUNC_INFO); 1506 #ifdef HAVE_FITS 1507 const QMultiMap<QString, QString>& extensions = extensionNames(fileName); 1508 const QStringList& imageExtensions = extensions.values(QLatin1String("IMAGES")); 1509 const QStringList& tableExtensions = extensions.values(QLatin1String("TABLES")); 1510 1511 QTreeWidgetItem* root = tw->invisibleRootItem(); 1512 // TODO: fileName may contain any data type: check if it's a FITS file 1513 auto* treeNameItem = new QTreeWidgetItem((QTreeWidgetItem*)nullptr, QStringList() << fileName); 1514 root->addChild(treeNameItem); 1515 treeNameItem->setExpanded(true); 1516 1517 auto* imageExtensionItem = new QTreeWidgetItem((QTreeWidgetItem*)nullptr, QStringList() << i18n("Images")); 1518 imageExtensionItem->setFlags(imageExtensionItem->flags() & ~Qt::ItemIsSelectable); 1519 QString primaryHeaderNaxis = valueOf(fileName, "NAXIS"); 1520 const int naxis = primaryHeaderNaxis.toInt(); 1521 bool noImage = false; 1522 for (const QString& ext : imageExtensions) { 1523 auto* treeItem = new QTreeWidgetItem((QTreeWidgetItem*)nullptr, QStringList() << ext); 1524 if (ext == i18n("Primary header")) { 1525 if (checkPrimary && naxis == 0) { 1526 delete treeItem; 1527 continue; 1528 } 1529 } 1530 imageExtensionItem->addChild(treeItem); 1531 } 1532 if (imageExtensionItem->childCount() > 0) { 1533 treeNameItem->addChild(imageExtensionItem); 1534 imageExtensionItem->setIcon(0, QIcon::fromTheme(QStringLiteral("view-preview"))); 1535 imageExtensionItem->setExpanded(true); 1536 imageExtensionItem->child(0)->setSelected(true); 1537 1538 tw->setCurrentItem(imageExtensionItem->child(0)); 1539 } else 1540 noImage = true; 1541 1542 if (tableExtensions.size() > 0) { 1543 auto* tableExtensionItem = new QTreeWidgetItem((QTreeWidgetItem*)nullptr, QStringList() << i18n("Tables")); 1544 tableExtensionItem->setFlags(tableExtensionItem->flags() & ~Qt::ItemIsSelectable); 1545 1546 for (const QString& ext : tableExtensions) { 1547 auto* treeItem = new QTreeWidgetItem((QTreeWidgetItem*)nullptr, QStringList() << ext); 1548 tableExtensionItem->addChild(treeItem); 1549 } 1550 if (tableExtensionItem->childCount() > 0) { 1551 treeNameItem->addChild(tableExtensionItem); 1552 tableExtensionItem->setIcon(0, QIcon::fromTheme(QStringLiteral("x-office-spreadsheet"))); 1553 tableExtensionItem->setExpanded(true); 1554 if (noImage) { 1555 tableExtensionItem->child(0)->setSelected(true); 1556 tw->setCurrentItem(tableExtensionItem->child(0)); 1557 } 1558 } 1559 } 1560 #else 1561 Q_UNUSED(fileName) 1562 Q_UNUSED(tw) 1563 Q_UNUSED(checkPrimary) 1564 #endif 1565 DEBUG(Q_FUNC_INFO << " DONE"); 1566 } 1567 1568 /*! 1569 * \brief FITSFilterPrivate::~FITSFilterPrivate 1570 */ 1571 1572 FITSFilterPrivate::~FITSFilterPrivate() = default; 1573 1574 // ############################################################################## 1575 // ################## Serialization/Deserialization ########################### 1576 // ############################################################################## 1577 1578 /*! 1579 Saves as XML. 1580 */ 1581 1582 void FITSFilter::save(QXmlStreamWriter*) const { 1583 } 1584 1585 /*! 1586 Loads from XML. 1587 */ 1588 1589 bool FITSFilter::load(XmlStreamReader*) { 1590 return false; 1591 }