File indexing completed on 2024-04-21 14:45:47
0001 /* 0002 SPDX-FileCopyrightText: 2004 Jasem Mutlaq <mutlaqja@ikarustech.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 0006 Some code fragments were adapted from Peter Kirchgessner's FITS plugin 0007 SPDX-FileCopyrightText: Peter Kirchgessner <http://members.aol.com/pkirchg> 0008 */ 0009 0010 #include "fitsdata.h" 0011 #include "fitsbahtinovdetector.h" 0012 #include "fitsthresholddetector.h" 0013 #include "fitsgradientdetector.h" 0014 #include "fitscentroiddetector.h" 0015 #include "fitssepdetector.h" 0016 0017 #include "fpack.h" 0018 0019 #include "kstarsdata.h" 0020 #include "ksutils.h" 0021 #include "kspaths.h" 0022 #include "Options.h" 0023 #include "skymapcomposite.h" 0024 #include "auxiliary/ksnotification.h" 0025 #include "auxiliary/robuststatistics.h" 0026 0027 #include <KFormat> 0028 #include <QApplication> 0029 #include <QImage> 0030 #include <QtConcurrent> 0031 #include <QImageReader> 0032 0033 #if !defined(KSTARS_LITE) && defined(HAVE_WCSLIB) 0034 #include <wcshdr.h> 0035 #include <wcsfix.h> 0036 #endif 0037 0038 #if !defined(KSTARS_LITE) && defined(HAVE_LIBRAW) 0039 #include <libraw/libraw.h> 0040 #endif 0041 0042 #ifdef HAVE_XISF 0043 #include <libxisf.h> 0044 #endif 0045 0046 #include <cfloat> 0047 #include <cmath> 0048 0049 #include <fits_debug.h> 0050 0051 #define ZOOM_DEFAULT 100.0 0052 #define ZOOM_MIN 10 0053 #define ZOOM_MAX 400 0054 #define ZOOM_LOW_INCR 10 0055 #define ZOOM_HIGH_INCR 50 0056 0057 QString getTemporaryPath() 0058 { 0059 return QDir(KSPaths::writableLocation(QStandardPaths::TempLocation) + "/" + 0060 qAppName()).path(); 0061 } 0062 0063 const QStringList RAWFormats = { "cr2", "cr3", "crw", "nef", "raf", "dng", "arw", "orf" }; 0064 0065 bool FITSData::readableFilename(const QString &filename) 0066 { 0067 QFileInfo info(filename); 0068 QString extension = info.completeSuffix().toLower(); 0069 return extension.contains("fit") || extension.contains("fz") || 0070 extension.contains("xisf") || 0071 QImageReader::supportedImageFormats().contains(extension.toLatin1()) || 0072 RAWFormats.contains(extension); 0073 } 0074 0075 FITSData::FITSData(FITSMode fitsMode): m_Mode(fitsMode) 0076 { 0077 qRegisterMetaType<FITSMode>("FITSMode"); 0078 0079 debayerParams.method = DC1394_BAYER_METHOD_NEAREST; 0080 debayerParams.filter = DC1394_COLOR_FILTER_RGGB; 0081 debayerParams.offsetX = debayerParams.offsetY = 0; 0082 0083 // Reserve 3 channels 0084 m_CumulativeFrequency.resize(3); 0085 m_HistogramBinWidth.resize(3); 0086 m_HistogramFrequency.resize(3); 0087 m_HistogramIntensity.resize(3); 0088 } 0089 0090 FITSData::FITSData(const QSharedPointer<FITSData> &other) 0091 { 0092 qRegisterMetaType<FITSMode>("FITSMode"); 0093 0094 debayerParams.method = DC1394_BAYER_METHOD_NEAREST; 0095 debayerParams.filter = DC1394_COLOR_FILTER_RGGB; 0096 debayerParams.offsetX = debayerParams.offsetY = 0; 0097 0098 m_TemporaryDataFile.setFileTemplate("fits_memory_XXXXXX"); 0099 0100 this->m_Mode = other->m_Mode; 0101 this->m_Statistics.channels = other->m_Statistics.channels; 0102 memcpy(&m_Statistics, &(other->m_Statistics), sizeof(m_Statistics)); 0103 m_ImageBuffer = new uint8_t[m_Statistics.samples_per_channel * m_Statistics.channels * m_Statistics.bytesPerPixel]; 0104 memcpy(m_ImageBuffer, other->m_ImageBuffer, 0105 m_Statistics.samples_per_channel * m_Statistics.channels * m_Statistics.bytesPerPixel); 0106 } 0107 0108 FITSData::~FITSData() 0109 { 0110 int status = 0; 0111 0112 if (m_StarFindFuture.isRunning()) 0113 m_StarFindFuture.waitForFinished(); 0114 0115 clearImageBuffers(); 0116 0117 #ifdef HAVE_WCSLIB 0118 if (m_WCSHandle != nullptr) 0119 { 0120 wcsvfree(&m_nwcs, &m_WCSHandle); 0121 m_WCSHandle = nullptr; 0122 m_nwcs = 0; 0123 } 0124 #endif 0125 0126 if (starCenters.count() > 0) 0127 qDeleteAll(starCenters); 0128 starCenters.clear(); 0129 0130 if (m_SkyObjects.count() > 0) 0131 qDeleteAll(m_SkyObjects); 0132 m_SkyObjects.clear(); 0133 0134 if (fptr != nullptr) 0135 { 0136 fits_flush_file(fptr, &status); 0137 fits_close_file(fptr, &status); 0138 free(m_PackBuffer); 0139 m_PackBuffer = nullptr; 0140 fptr = nullptr; 0141 } 0142 } 0143 0144 void FITSData::loadCommon(const QString &inFilename) 0145 { 0146 int status = 0; 0147 qDeleteAll(starCenters); 0148 starCenters.clear(); 0149 0150 if (fptr != nullptr) 0151 { 0152 fits_flush_file(fptr, &status); 0153 fits_close_file(fptr, &status); 0154 free(m_PackBuffer); 0155 m_PackBuffer = nullptr; 0156 fptr = nullptr; 0157 } 0158 0159 m_Filename = inFilename; 0160 } 0161 0162 bool FITSData::loadFromBuffer(const QByteArray &buffer, const QString &extension, const QString &inFilename) 0163 { 0164 loadCommon(inFilename); 0165 m_Extension = extension; 0166 qCDebug(KSTARS_FITS) << "Reading file buffer (" << KFormat().formatByteSize(buffer.size()) << ")"; 0167 return privateLoad(buffer); 0168 } 0169 0170 QFuture<bool> FITSData::loadFromFile(const QString &inFilename) 0171 { 0172 loadCommon(inFilename); 0173 QFileInfo info(m_Filename); 0174 m_Extension = info.completeSuffix().toLower(); 0175 qCDebug(KSTARS_FITS) << "Loading file " << m_Filename; 0176 return QtConcurrent::run(this, &FITSData::privateLoad, QByteArray()); 0177 } 0178 0179 namespace 0180 { 0181 // Common code for reporting fits read errors. Always returns false. 0182 QString fitsErrorToString(int status) 0183 { 0184 char error_status[512] = {0}; 0185 fits_report_error(stderr, status); 0186 fits_get_errstatus(status, error_status); 0187 QString message = error_status; 0188 return message; 0189 } 0190 } 0191 0192 bool FITSData::privateLoad(const QByteArray &buffer) 0193 { 0194 m_isTemporary = m_Filename.startsWith(KSPaths::writableLocation(QStandardPaths::TempLocation)); 0195 cacheHFR = -1; 0196 cacheEccentricity = -1; 0197 0198 if (m_Extension.contains("fit") || m_Extension.contains("fz")) 0199 return loadFITSImage(buffer); 0200 if (m_Extension.contains("xisf")) 0201 return loadXISFImage(buffer); 0202 if (QImageReader::supportedImageFormats().contains(m_Extension.toLatin1())) 0203 return loadCanonicalImage(buffer); 0204 else if (RAWFormats.contains(m_Extension)) 0205 return loadRAWImage(buffer); 0206 0207 return false; 0208 } 0209 0210 bool FITSData::loadFITSImage(const QByteArray &buffer, const bool isCompressed) 0211 { 0212 int status = 0, anynull = 0; 0213 long naxes[3]; 0214 0215 m_HistogramConstructed = false; 0216 0217 if (m_Extension.contains(".fz") || isCompressed) 0218 { 0219 fpstate fpvar; 0220 fp_init (&fpvar); 0221 bool rc = false; 0222 0223 if (buffer.isEmpty()) 0224 { 0225 // Store so we don't lose. 0226 m_compressedFilename = m_Filename; 0227 0228 QString uncompressedFile = QDir::tempPath() + QString("/%1").arg(QUuid::createUuid().toString().remove( 0229 QRegularExpression("[-{}]"))); 0230 0231 rc = fp_unpack_file_to_fits(m_Filename.toLocal8Bit().data(), &fptr, fpvar) == 0; 0232 if (rc) 0233 { 0234 m_Filename = uncompressedFile; 0235 } 0236 } 0237 else 0238 { 0239 size_t m_PackBufferSize = 100000; 0240 free(m_PackBuffer); 0241 m_PackBuffer = (uint8_t *)malloc(m_PackBufferSize); 0242 rc = fp_unpack_data_to_data(buffer.data(), buffer.size(), &m_PackBuffer, &m_PackBufferSize, fpvar) == 0; 0243 0244 if (rc) 0245 { 0246 void *data = reinterpret_cast<void *>(m_PackBuffer); 0247 if (fits_open_memfile(&fptr, m_Filename.toLocal8Bit().data(), READONLY, &data, &m_PackBufferSize, 0, 0248 nullptr, &status)) 0249 { 0250 m_LastError = i18n("Error reading fits buffer: %1.", fitsErrorToString(status)); 0251 return false; 0252 } 0253 0254 m_Statistics.size = m_PackBufferSize; 0255 } 0256 //rc = fp_unpack_data_to_fits(buffer.data(), buffer.size(), &fptr, fpvar) == 0; 0257 } 0258 0259 if (rc == false) 0260 { 0261 free(m_PackBuffer); 0262 m_PackBuffer = nullptr; 0263 m_LastError = i18n("Failed to unpack compressed fits"); 0264 qCCritical(KSTARS_FITS) << m_LastError; 0265 return false; 0266 } 0267 0268 m_isTemporary = true; 0269 m_isCompressed = true; 0270 m_Statistics.size = fptr->Fptr->logfilesize; 0271 0272 } 0273 else if (buffer.isEmpty()) 0274 { 0275 // Use open diskfile as it does not use extended file names which has problems opening 0276 // files with [ ] or ( ) in their names. 0277 if (fits_open_diskfile(&fptr, m_Filename.toLocal8Bit(), READONLY, &status)) 0278 { 0279 m_LastError = i18n("Error opening fits file %1 : %2", m_Filename, fitsErrorToString(status)); 0280 } 0281 0282 m_Statistics.size = QFile(m_Filename).size(); 0283 } 0284 else 0285 { 0286 // Read the FITS file from a memory buffer. 0287 void *temp_buffer = const_cast<void *>(reinterpret_cast<const void *>(buffer.data())); 0288 size_t temp_size = buffer.size(); 0289 if (fits_open_memfile(&fptr, m_Filename.toLocal8Bit().data(), READONLY, 0290 &temp_buffer, &temp_size, 0, nullptr, &status)) 0291 { 0292 m_LastError = i18n("Error reading fits buffer: %1", fitsErrorToString(status)); 0293 return false; 0294 } 0295 0296 m_Statistics.size = temp_size; 0297 } 0298 0299 if (fits_movabs_hdu(fptr, 1, IMAGE_HDU, &status)) 0300 { 0301 0302 free(m_PackBuffer); 0303 m_PackBuffer = nullptr; 0304 m_LastError = i18n("Could not locate image HDU: %1", fitsErrorToString(status)); 0305 } 0306 0307 if (fits_get_img_param(fptr, 3, &m_FITSBITPIX, &(m_Statistics.ndim), naxes, &status)) 0308 { 0309 free(m_PackBuffer); 0310 m_PackBuffer = nullptr; 0311 m_LastError = i18n("FITS file open error (fits_get_img_param): %1", fitsErrorToString(status)); 0312 return false; 0313 } 0314 0315 // Reload if it is transparently compressed. 0316 if ((fits_is_compressed_image(fptr, &status) || m_Statistics.ndim <= 0) && !isCompressed) 0317 { 0318 loadCommon(m_Filename); 0319 qCDebug(KSTARS_FITS) << "Image is compressed. Reloading..."; 0320 return loadFITSImage(buffer, true); 0321 } 0322 0323 if (m_Statistics.ndim < 2) 0324 { 0325 m_LastError = i18n("1D FITS images are not supported in KStars."); 0326 qCCritical(KSTARS_FITS) << m_LastError; 0327 free(m_PackBuffer); 0328 m_PackBuffer = nullptr; 0329 return false; 0330 } 0331 0332 switch (m_FITSBITPIX) 0333 { 0334 case BYTE_IMG: 0335 m_Statistics.dataType = TBYTE; 0336 m_Statistics.bytesPerPixel = sizeof(uint8_t); 0337 break; 0338 case SHORT_IMG: 0339 // Read SHORT image as USHORT 0340 m_FITSBITPIX = USHORT_IMG; 0341 m_Statistics.dataType = TUSHORT; 0342 m_Statistics.bytesPerPixel = sizeof(int16_t); 0343 break; 0344 case USHORT_IMG: 0345 m_Statistics.dataType = TUSHORT; 0346 m_Statistics.bytesPerPixel = sizeof(uint16_t); 0347 break; 0348 case LONG_IMG: 0349 // Read LONG image as ULONG 0350 m_FITSBITPIX = ULONG_IMG; 0351 m_Statistics.dataType = TULONG; 0352 m_Statistics.bytesPerPixel = sizeof(int32_t); 0353 break; 0354 case ULONG_IMG: 0355 m_Statistics.dataType = TULONG; 0356 m_Statistics.bytesPerPixel = sizeof(uint32_t); 0357 break; 0358 case FLOAT_IMG: 0359 m_Statistics.dataType = TFLOAT; 0360 m_Statistics.bytesPerPixel = sizeof(float); 0361 break; 0362 case LONGLONG_IMG: 0363 m_Statistics.dataType = TLONGLONG; 0364 m_Statistics.bytesPerPixel = sizeof(int64_t); 0365 break; 0366 case DOUBLE_IMG: 0367 m_Statistics.dataType = TDOUBLE; 0368 m_Statistics.bytesPerPixel = sizeof(double); 0369 break; 0370 default: 0371 m_LastError = i18n("Bit depth %1 is not supported.", m_FITSBITPIX); 0372 qCCritical(KSTARS_FITS) << m_LastError; 0373 return false; 0374 } 0375 0376 if (m_Statistics.ndim < 3) 0377 naxes[2] = 1; 0378 0379 if (naxes[0] == 0 || naxes[1] == 0) 0380 { 0381 m_LastError = i18n("Image has invalid dimensions %1x%2", naxes[0], naxes[1]); 0382 qCCritical(KSTARS_FITS) << m_LastError; 0383 free(m_PackBuffer); 0384 m_PackBuffer = nullptr; 0385 return false; 0386 } 0387 0388 m_Statistics.width = naxes[0]; 0389 m_Statistics.height = naxes[1]; 0390 m_Statistics.samples_per_channel = m_Statistics.width * m_Statistics.height; 0391 roiCenter.setX(m_Statistics.width / 2); 0392 roiCenter.setY(m_Statistics.height / 2); 0393 if(m_Statistics.width % 2) 0394 roiCenter.setX(roiCenter.x() + 1); 0395 if(m_Statistics.height % 2) 0396 roiCenter.setY(roiCenter.y() + 1); 0397 0398 clearImageBuffers(); 0399 0400 m_Statistics.channels = naxes[2]; 0401 0402 // Channels always set to #1 if we are not required to process 3D Cubes 0403 // Or if mode is not FITS_NORMAL (guide, focus..etc) 0404 if ( (m_Mode != FITS_NORMAL && m_Mode != FITS_CALIBRATE) || !Options::auto3DCube()) 0405 m_Statistics.channels = 1; 0406 0407 m_ImageBufferSize = m_Statistics.samples_per_channel * m_Statistics.channels * m_Statistics.bytesPerPixel; 0408 m_ImageBuffer = new uint8_t[m_ImageBufferSize]; 0409 if (m_ImageBuffer == nullptr) 0410 { 0411 qCWarning(KSTARS_FITS) << "FITSData: Not enough memory for image_buffer channel. Requested: " 0412 << m_ImageBufferSize << " bytes."; 0413 clearImageBuffers(); 0414 free(m_PackBuffer); 0415 m_PackBuffer = nullptr; 0416 return false; 0417 } 0418 0419 rotCounter = 0; 0420 flipHCounter = 0; 0421 flipVCounter = 0; 0422 long nelements = m_Statistics.samples_per_channel * m_Statistics.channels; 0423 0424 if (fits_read_img(fptr, m_Statistics.dataType, 1, nelements, nullptr, m_ImageBuffer, &anynull, &status)) 0425 { 0426 m_LastError = i18n("Error reading image: %1", fitsErrorToString(status)); 0427 return false; 0428 } 0429 0430 parseHeader(); 0431 0432 // Get UTC date time 0433 QVariant value; 0434 if (getRecordValue("DATE-OBS", value) && value.isValid()) 0435 { 0436 QDateTime ts = value.toDateTime(); 0437 m_DateTime = KStarsDateTime(ts.date(), ts.time()); 0438 } 0439 0440 // Only check for debayed IF the original naxes[2] is 1 0441 // which is for single channels. 0442 if (naxes[2] == 1 && m_Statistics.channels == 1 && Options::autoDebayer() && checkDebayer()) 0443 { 0444 // Save bayer image on disk in case we need to save it later since debayer destorys this data 0445 if (m_isTemporary && m_TemporaryDataFile.open()) 0446 { 0447 m_TemporaryDataFile.write(buffer); 0448 m_TemporaryDataFile.close(); 0449 m_Filename = m_TemporaryDataFile.fileName(); 0450 } 0451 0452 if (debayer()) 0453 calculateStats(false, false); 0454 } 0455 else 0456 calculateStats(false, false); 0457 0458 if (m_Mode == FITS_NORMAL || m_Mode == FITS_ALIGN) 0459 loadWCS(); 0460 0461 starsSearched = false; 0462 0463 return true; 0464 } 0465 0466 bool FITSData::loadXISFImage(const QByteArray &buffer) 0467 { 0468 m_HistogramConstructed = false; 0469 clearImageBuffers(); 0470 0471 #ifdef HAVE_XISF 0472 try 0473 { 0474 LibXISF::XISFReader xisfReader; 0475 if (buffer.isEmpty()) 0476 { 0477 xisfReader.open(m_Filename.toLocal8Bit().data()); 0478 } 0479 else 0480 { 0481 LibXISF::ByteArray byteArray(buffer.constData(), buffer.size()); 0482 xisfReader.open(byteArray); 0483 } 0484 0485 if (xisfReader.imagesCount() == 0) 0486 { 0487 m_LastError = i18n("File contain no images"); 0488 return false; 0489 } 0490 0491 const LibXISF::Image &image = xisfReader.getImage(0); 0492 0493 switch (image.sampleFormat()) 0494 { 0495 case LibXISF::Image::UInt8: 0496 m_Statistics.dataType = TBYTE; 0497 m_Statistics.bytesPerPixel = sizeof(LibXISF::UInt8); 0498 m_FITSBITPIX = TBYTE; 0499 break; 0500 case LibXISF::Image::UInt16: 0501 m_Statistics.dataType = TUSHORT; 0502 m_Statistics.bytesPerPixel = sizeof(LibXISF::UInt16); 0503 m_FITSBITPIX = TUSHORT; 0504 break; 0505 case LibXISF::Image::UInt32: 0506 m_Statistics.dataType = TULONG; 0507 m_Statistics.bytesPerPixel = sizeof(LibXISF::UInt32); 0508 m_FITSBITPIX = TULONG; 0509 break; 0510 case LibXISF::Image::Float32: 0511 m_Statistics.dataType = TFLOAT; 0512 m_Statistics.bytesPerPixel = sizeof(LibXISF::Float32); 0513 m_FITSBITPIX = TFLOAT; 0514 break; 0515 default: 0516 m_LastError = i18n("Sample format %1 is not supported.", LibXISF::Image::sampleFormatString(image.sampleFormat()).c_str()); 0517 qCCritical(KSTARS_FITS) << m_LastError; 0518 return false; 0519 } 0520 0521 m_Statistics.width = image.width(); 0522 m_Statistics.height = image.height(); 0523 m_Statistics.samples_per_channel = m_Statistics.width * m_Statistics.height; 0524 m_Statistics.channels = image.channelCount(); 0525 m_Statistics.size = buffer.size(); 0526 roiCenter.setX(m_Statistics.width / 2); 0527 roiCenter.setY(m_Statistics.height / 2); 0528 if(m_Statistics.width % 2) 0529 roiCenter.setX(roiCenter.x() + 1); 0530 if(m_Statistics.height % 2) 0531 roiCenter.setY(roiCenter.y() + 1); 0532 0533 m_HeaderRecords.clear(); 0534 auto &fitsKeywords = image.fitsKeywords(); 0535 for(auto &fitsKeyword : fitsKeywords) 0536 m_HeaderRecords.push_back({QString::fromStdString(fitsKeyword.name), QString::fromStdString(fitsKeyword.value), QString::fromStdString(fitsKeyword.comment)}); 0537 0538 QVariant value; 0539 if (getRecordValue("DATE-OBS", value) && value.isValid()) 0540 { 0541 QDateTime ts = value.toDateTime(); 0542 m_DateTime = KStarsDateTime(ts.date(), ts.time()); 0543 } 0544 0545 m_ImageBufferSize = image.imageDataSize(); 0546 m_ImageBuffer = new uint8_t[m_ImageBufferSize]; 0547 std::memcpy(m_ImageBuffer, image.imageData(), m_ImageBufferSize); 0548 0549 calculateStats(false, false); 0550 loadWCS(); 0551 } 0552 catch (LibXISF::Error &error) 0553 { 0554 m_LastError = i18n("XISF file open error: ") + error.what(); 0555 return false; 0556 } 0557 return true; 0558 #else 0559 return false; 0560 #endif 0561 0562 } 0563 0564 bool FITSData::saveXISFImage(const QString &newFilename) 0565 { 0566 #ifdef HAVE_XISF 0567 try 0568 { 0569 LibXISF::XISFWriter xisfWriter; 0570 LibXISF::Image image; 0571 image.setGeometry(m_Statistics.width, m_Statistics.height, m_Statistics.channels); 0572 if (m_Statistics.channels > 1) 0573 image.setColorSpace(LibXISF::Image::RGB); 0574 0575 switch (m_FITSBITPIX) 0576 { 0577 case BYTE_IMG: 0578 image.setSampleFormat(LibXISF::Image::UInt8); 0579 break; 0580 case USHORT_IMG: 0581 image.setSampleFormat(LibXISF::Image::UInt16); 0582 break; 0583 case ULONG_IMG: 0584 image.setSampleFormat(LibXISF::Image::UInt32); 0585 break; 0586 case FLOAT_IMG: 0587 image.setSampleFormat(LibXISF::Image::Float32); 0588 break; 0589 default: 0590 m_LastError = i18n("Bit depth %1 is not supported.", m_FITSBITPIX); 0591 qCCritical(KSTARS_FITS) << m_LastError; 0592 return false; 0593 } 0594 0595 std::memcpy(image.imageData(), m_ImageBuffer, m_ImageBufferSize); 0596 for (auto &fitsKeyword : m_HeaderRecords) 0597 image.addFITSKeyword({fitsKeyword.key.toUtf8().data(), fitsKeyword.value.toString().toUtf8().data(), fitsKeyword.comment.toUtf8().data()}); 0598 0599 xisfWriter.writeImage(image); 0600 xisfWriter.save(newFilename.toLocal8Bit().data()); 0601 m_Filename = newFilename; 0602 } 0603 catch (LibXISF::Error &err) 0604 { 0605 m_LastError = i18n("Error saving XISF image") + err.what(); 0606 return false; 0607 } 0608 return true; 0609 #else 0610 return false; 0611 #endif 0612 } 0613 0614 bool FITSData::loadCanonicalImage(const QByteArray &buffer) 0615 { 0616 QImage imageFromFile; 0617 if (!buffer.isEmpty()) 0618 { 0619 if(!imageFromFile.loadFromData(reinterpret_cast<const uint8_t*>(buffer.data()), buffer.size())) 0620 { 0621 qCCritical(KSTARS_FITS) << "Failed to open image."; 0622 return false; 0623 } 0624 0625 m_Statistics.size = buffer.size(); 0626 } 0627 else 0628 { 0629 if(!imageFromFile.load(m_Filename.toLocal8Bit())) 0630 { 0631 qCCritical(KSTARS_FITS) << "Failed to open image."; 0632 return false; 0633 } 0634 0635 m_Statistics.size = QFileInfo(m_Filename).size(); 0636 } 0637 0638 //imageFromFile = imageFromFile.convertToFormat(QImage::Format_RGB32); 0639 0640 // Note: This will need to be changed. I think QT only loads 8 bpp images. 0641 // Also the depth method gives the total bits per pixel in the image not just the bits per 0642 // pixel in each channel. 0643 m_FITSBITPIX = BYTE_IMG; 0644 switch (m_FITSBITPIX) 0645 { 0646 case BYTE_IMG: 0647 m_Statistics.dataType = TBYTE; 0648 m_Statistics.bytesPerPixel = sizeof(uint8_t); 0649 break; 0650 case SHORT_IMG: 0651 // Read SHORT image as USHORT 0652 m_Statistics.dataType = TUSHORT; 0653 m_Statistics.bytesPerPixel = sizeof(int16_t); 0654 break; 0655 case USHORT_IMG: 0656 m_Statistics.dataType = TUSHORT; 0657 m_Statistics.bytesPerPixel = sizeof(uint16_t); 0658 break; 0659 case LONG_IMG: 0660 // Read LONG image as ULONG 0661 m_Statistics.dataType = TULONG; 0662 m_Statistics.bytesPerPixel = sizeof(int32_t); 0663 break; 0664 case ULONG_IMG: 0665 m_Statistics.dataType = TULONG; 0666 m_Statistics.bytesPerPixel = sizeof(uint32_t); 0667 break; 0668 case FLOAT_IMG: 0669 m_Statistics.dataType = TFLOAT; 0670 m_Statistics.bytesPerPixel = sizeof(float); 0671 break; 0672 case LONGLONG_IMG: 0673 m_Statistics.dataType = TLONGLONG; 0674 m_Statistics.bytesPerPixel = sizeof(int64_t); 0675 break; 0676 case DOUBLE_IMG: 0677 m_Statistics.dataType = TDOUBLE; 0678 m_Statistics.bytesPerPixel = sizeof(double); 0679 break; 0680 default: 0681 m_LastError = QString("Bit depth %1 is not supported.").arg(m_FITSBITPIX); 0682 qCCritical(KSTARS_FITS) << m_LastError; 0683 return false; 0684 } 0685 0686 m_Statistics.width = static_cast<uint16_t>(imageFromFile.width()); 0687 m_Statistics.height = static_cast<uint16_t>(imageFromFile.height()); 0688 m_Statistics.channels = imageFromFile.format() == QImage::Format_Grayscale8 ? 1 : 3; 0689 m_Statistics.samples_per_channel = m_Statistics.width * m_Statistics.height; 0690 clearImageBuffers(); 0691 m_ImageBufferSize = m_Statistics.samples_per_channel * m_Statistics.channels * static_cast<uint16_t> 0692 (m_Statistics.bytesPerPixel); 0693 m_ImageBuffer = new uint8_t[m_ImageBufferSize]; 0694 if (m_ImageBuffer == nullptr) 0695 { 0696 m_LastError = i18n("FITSData: Not enough memory for image_buffer channel. Requested: %1 bytes ", m_ImageBufferSize); 0697 qCCritical(KSTARS_FITS) << m_LastError; 0698 clearImageBuffers(); 0699 return false; 0700 } 0701 0702 if (m_Statistics.channels == 1) 0703 { 0704 memcpy(m_ImageBuffer, imageFromFile.bits(), m_ImageBufferSize); 0705 } 0706 else 0707 { 0708 0709 auto debayered_buffer = reinterpret_cast<uint8_t *>(m_ImageBuffer); 0710 auto * original_bayered_buffer = reinterpret_cast<uint8_t *>(imageFromFile.bits()); 0711 0712 // Data in RGBA, with bytes in the order of B,G,R we need to copy them into 3 layers for FITS 0713 uint8_t * rBuff = debayered_buffer; 0714 uint8_t * gBuff = debayered_buffer + (m_Statistics.width * m_Statistics.height); 0715 uint8_t * bBuff = debayered_buffer + (m_Statistics.width * m_Statistics.height * 2); 0716 0717 int imax = m_Statistics.samples_per_channel * 4 - 4; 0718 for (int i = 0; i <= imax; i += 4) 0719 { 0720 *rBuff++ = original_bayered_buffer[i + 2]; 0721 *gBuff++ = original_bayered_buffer[i + 1]; 0722 *bBuff++ = original_bayered_buffer[i + 0]; 0723 } 0724 } 0725 0726 calculateStats(false, false); 0727 return true; 0728 } 0729 0730 bool FITSData::loadRAWImage(const QByteArray &buffer) 0731 { 0732 #if !defined(KSTARS_LITE) && !defined(HAVE_LIBRAW) 0733 m_LastError = i18n("Unable to find dcraw and cjpeg. Please install the required tools to convert CR2/NEF to JPEG."); 0734 return false; 0735 #else 0736 0737 int ret = 0; 0738 // Creation of image processing object 0739 LibRaw RawProcessor; 0740 0741 // Let us open the file/buffer 0742 if (buffer.isEmpty()) 0743 { 0744 // Open file 0745 if ((ret = RawProcessor.open_file(m_Filename.toLocal8Bit().constData())) != LIBRAW_SUCCESS) 0746 { 0747 m_LastError = i18n("Cannot open file %1: %2", m_Filename, libraw_strerror(ret)); 0748 RawProcessor.recycle(); 0749 return false; 0750 } 0751 0752 m_Statistics.size = QFileInfo(m_Filename).size(); 0753 } 0754 // Open Buffer 0755 else 0756 { 0757 if ((ret = RawProcessor.open_buffer(const_cast<void *>(reinterpret_cast<const void *>(buffer.data())), 0758 buffer.size())) != LIBRAW_SUCCESS) 0759 { 0760 m_LastError = i18n("Cannot open buffer: %1", libraw_strerror(ret)); 0761 RawProcessor.recycle(); 0762 return false; 0763 } 0764 0765 m_Statistics.size = buffer.size(); 0766 } 0767 0768 // Let us unpack the thumbnail 0769 if ((ret = RawProcessor.unpack()) != LIBRAW_SUCCESS) 0770 { 0771 m_LastError = i18n("Cannot unpack_thumb: %1", libraw_strerror(ret)); 0772 RawProcessor.recycle(); 0773 return false; 0774 } 0775 0776 if ((ret = RawProcessor.dcraw_process()) != LIBRAW_SUCCESS) 0777 { 0778 m_LastError = i18n("Cannot dcraw_process: %1", libraw_strerror(ret)); 0779 RawProcessor.recycle(); 0780 return false; 0781 } 0782 0783 libraw_processed_image_t *image = RawProcessor.dcraw_make_mem_image(&ret); 0784 if (ret != LIBRAW_SUCCESS) 0785 { 0786 m_LastError = i18n("Cannot load to memory: %1", libraw_strerror(ret)); 0787 RawProcessor.recycle(); 0788 return false; 0789 } 0790 0791 RawProcessor.recycle(); 0792 0793 m_Statistics.bytesPerPixel = image->bits / 8; 0794 // We only support two types now 0795 if (m_Statistics.bytesPerPixel == 1) 0796 m_Statistics.dataType = TBYTE; 0797 else 0798 m_Statistics.dataType = TUSHORT; 0799 m_Statistics.width = image->width; 0800 m_Statistics.height = image->height; 0801 m_Statistics.channels = image->colors; 0802 m_Statistics.samples_per_channel = m_Statistics.width * m_Statistics.height; 0803 clearImageBuffers(); 0804 m_ImageBufferSize = m_Statistics.samples_per_channel * m_Statistics.channels * m_Statistics.bytesPerPixel; 0805 m_ImageBuffer = new uint8_t[m_ImageBufferSize]; 0806 if (m_ImageBuffer == nullptr) 0807 { 0808 m_LastError = i18n("FITSData: Not enough memory for image_buffer channel. Requested: %1 bytes ", m_ImageBufferSize); 0809 qCCritical(KSTARS_FITS) << m_LastError; 0810 libraw_dcraw_clear_mem(image); 0811 clearImageBuffers(); 0812 return false; 0813 } 0814 0815 auto destination_buffer = reinterpret_cast<uint8_t *>(m_ImageBuffer); 0816 auto source_buffer = reinterpret_cast<uint8_t *>(image->data); 0817 0818 // For mono, we memcpy directly 0819 if (image->colors == 1) 0820 { 0821 memcpy(destination_buffer, source_buffer, m_ImageBufferSize); 0822 } 0823 else 0824 { 0825 // Data in RGB24, with bytes in the order of R,G,B. We copy them copy them into 3 layers for FITS 0826 uint8_t * rBuff = destination_buffer; 0827 uint8_t * gBuff = destination_buffer + (m_Statistics.width * m_Statistics.height); 0828 uint8_t * bBuff = destination_buffer + (m_Statistics.width * m_Statistics.height * 2); 0829 0830 int imax = m_Statistics.samples_per_channel * 3 - 3; 0831 for (int i = 0; i <= imax; i += 3) 0832 { 0833 *rBuff++ = source_buffer[i + 0]; 0834 *gBuff++ = source_buffer[i + 1]; 0835 *bBuff++ = source_buffer[i + 2]; 0836 } 0837 } 0838 libraw_dcraw_clear_mem(image); 0839 0840 calculateStats(false, false); 0841 return true; 0842 #endif 0843 } 0844 0845 bool FITSData::saveImage(const QString &newFilename) 0846 { 0847 if (newFilename == m_Filename) 0848 return true; 0849 0850 const QString ext = QFileInfo(newFilename).suffix(); 0851 0852 if (ext == "jpg" || ext == "png") 0853 { 0854 double min, max; 0855 QImage fitsImage = FITSToImage(newFilename); 0856 getMinMax(&min, &max); 0857 0858 if (min == max) 0859 { 0860 fitsImage.fill(Qt::white); 0861 } 0862 else if (channels() == 1) 0863 { 0864 fitsImage = QImage(width(), height(), QImage::Format_Indexed8); 0865 0866 fitsImage.setColorCount(256); 0867 for (int i = 0; i < 256; i++) 0868 fitsImage.setColor(i, qRgb(i, i, i)); 0869 } 0870 else 0871 { 0872 fitsImage = QImage(width(), height(), QImage::Format_RGB32); 0873 } 0874 0875 double dataMin = m_Statistics.mean[0] - m_Statistics.stddev[0]; 0876 double dataMax = m_Statistics.mean[0] + m_Statistics.stddev[0] * 3; 0877 0878 double bscale = 255. / (dataMax - dataMin); 0879 double bzero = (-dataMin) * (255. / (dataMax - dataMin)); 0880 0881 // Long way to do this since we do not want to use templated functions here 0882 switch (m_Statistics.dataType) 0883 { 0884 case TBYTE: 0885 convertToQImage<uint8_t>(dataMin, dataMax, bscale, bzero, fitsImage); 0886 break; 0887 0888 case TSHORT: 0889 convertToQImage<int16_t>(dataMin, dataMax, bscale, bzero, fitsImage); 0890 break; 0891 0892 case TUSHORT: 0893 convertToQImage<uint16_t>(dataMin, dataMax, bscale, bzero, fitsImage); 0894 break; 0895 0896 case TLONG: 0897 convertToQImage<int32_t>(dataMin, dataMax, bscale, bzero, fitsImage); 0898 break; 0899 0900 case TULONG: 0901 convertToQImage<uint32_t>(dataMin, dataMax, bscale, bzero, fitsImage); 0902 break; 0903 0904 case TFLOAT: 0905 convertToQImage<float>(dataMin, dataMax, bscale, bzero, fitsImage); 0906 break; 0907 0908 case TLONGLONG: 0909 convertToQImage<int64_t>(dataMin, dataMax, bscale, bzero, fitsImage); 0910 break; 0911 0912 case TDOUBLE: 0913 convertToQImage<double>(dataMin, dataMax, bscale, bzero, fitsImage); 0914 break; 0915 0916 default: 0917 break; 0918 } 0919 0920 fitsImage.save(newFilename, ext.toLatin1().constData()); 0921 0922 m_Filename = newFilename; 0923 qCInfo(KSTARS_FITS) << "Saved image file:" << m_Filename; 0924 return true; 0925 } 0926 0927 if (m_isCompressed) 0928 { 0929 m_LastError = i18n("Saving compressed files is not supported."); 0930 return false; 0931 } 0932 0933 int status = 0; 0934 long nelements; 0935 fitsfile * new_fptr; 0936 0937 if (ext == "xisf") 0938 { 0939 if(fptr) 0940 { 0941 fits_close_file(fptr, &status); 0942 fptr = nullptr; 0943 } 0944 rotCounter = flipHCounter = flipVCounter = 0; 0945 return saveXISFImage(newFilename); 0946 } 0947 0948 // if (HasDebayer && m_Filename.isEmpty() == false) 0949 // { 0950 // fits_flush_file(fptr, &status); 0951 // /* close current file */ 0952 // if (fits_close_file(fptr, &status)) 0953 // { 0954 // recordLastError(status); 0955 // return status; 0956 // } 0957 0958 // // Skip "!" in the beginning of the new file name 0959 // QString finalFileName(newFilename); 0960 0961 // finalFileName.remove('!'); 0962 0963 // // Remove first otherwise copy will fail below if file exists 0964 // QFile::remove(finalFileName); 0965 0966 // if (!QFile::copy(m_Filename, finalFileName)) 0967 // { 0968 // qCCritical(KSTARS_FITS()) << "FITS: Failed to copy " << m_Filename << " to " << finalFileName; 0969 // fptr = nullptr; 0970 // return false; 0971 // } 0972 0973 // m_Filename = finalFileName; 0974 0975 // // Use open diskfile as it does not use extended file names which has problems opening 0976 // // files with [ ] or ( ) in their names. 0977 // fits_open_diskfile(&fptr, m_Filename.toLocal8Bit(), READONLY, &status); 0978 // fits_movabs_hdu(fptr, 1, IMAGE_HDU, &status); 0979 0980 // return true; 0981 // } 0982 0983 // Read the image back into buffer in case we debyayed 0984 nelements = m_Statistics.samples_per_channel * m_Statistics.channels; 0985 0986 /* close current file */ 0987 if (fptr && fits_close_file(fptr, &status)) 0988 { 0989 m_LastError = i18n("Failed to close file: %1", fitsErrorToString(status)); 0990 return false; 0991 } 0992 0993 /* Create a new File, overwriting existing*/ 0994 if (fits_create_file(&new_fptr, QString("!%1").arg(newFilename).toLocal8Bit(), &status)) 0995 { 0996 m_LastError = i18n("Failed to create file: %1", fitsErrorToString(status)); 0997 return status; 0998 } 0999 1000 status = 0; 1001 1002 fptr = new_fptr; 1003 1004 // Create image 1005 long naxis = m_Statistics.channels == 1 ? 2 : 3; 1006 long naxes[3] = {m_Statistics.width, m_Statistics.height, naxis}; 1007 1008 // JM 2020-12-28: Here we to use bitpix values 1009 if (fits_create_img(fptr, m_FITSBITPIX, naxis, naxes, &status)) 1010 { 1011 m_LastError = i18n("Failed to create image: %1", fitsErrorToString(status)); 1012 return false; 1013 } 1014 1015 /* Write keywords */ 1016 1017 // Minimum 1018 if (fits_update_key(fptr, TDOUBLE, "DATAMIN", &(m_Statistics.min), "Minimum value", &status)) 1019 { 1020 m_LastError = i18n("Failed to update key: %1", fitsErrorToString(status)); 1021 return false; 1022 } 1023 1024 // Maximum 1025 if (fits_update_key(fptr, TDOUBLE, "DATAMAX", &(m_Statistics.max), "Maximum value", &status)) 1026 { 1027 m_LastError = i18n("Failed to update key: %1", fitsErrorToString(status)); 1028 return false; 1029 } 1030 1031 // KStars Min, for 3 channels 1032 fits_write_key(fptr, TDOUBLE, "MIN1", &m_Statistics.min[0], "Min Channel 1", &status); 1033 if (channels() > 1) 1034 { 1035 fits_write_key(fptr, TDOUBLE, "MIN2", &m_Statistics.min[1], "Min Channel 2", &status); 1036 fits_write_key(fptr, TDOUBLE, "MIN3", &m_Statistics.min[2], "Min Channel 3", &status); 1037 } 1038 1039 // KStars max, for 3 channels 1040 fits_write_key(fptr, TDOUBLE, "MAX1", &m_Statistics.max[0], "Max Channel 1", &status); 1041 if (channels() > 1) 1042 { 1043 fits_write_key(fptr, TDOUBLE, "MAX2", &m_Statistics.max[1], "Max Channel 2", &status); 1044 fits_write_key(fptr, TDOUBLE, "MAX3", &m_Statistics.max[2], "Max Channel 3", &status); 1045 } 1046 1047 // Mean 1048 if (m_Statistics.mean[0] > 0) 1049 { 1050 fits_write_key(fptr, TDOUBLE, "MEAN1", &m_Statistics.mean[0], "Mean Channel 1", &status); 1051 if (channels() > 1) 1052 { 1053 fits_write_key(fptr, TDOUBLE, "MEAN2", &m_Statistics.mean[1], "Mean Channel 2", &status); 1054 fits_write_key(fptr, TDOUBLE, "MEAN3", &m_Statistics.mean[2], "Mean Channel 3", &status); 1055 } 1056 } 1057 1058 // Median 1059 if (m_Statistics.median[0] > 0) 1060 { 1061 fits_write_key(fptr, TDOUBLE, "MEDIAN1", &m_Statistics.median[0], "Median Channel 1", &status); 1062 if (channels() > 1) 1063 { 1064 fits_write_key(fptr, TDOUBLE, "MEDIAN2", &m_Statistics.median[1], "Median Channel 2", &status); 1065 fits_write_key(fptr, TDOUBLE, "MEDIAN3", &m_Statistics.median[2], "Median Channel 3", &status); 1066 } 1067 } 1068 1069 // Standard Deviation 1070 if (m_Statistics.stddev[0] > 0) 1071 { 1072 fits_write_key(fptr, TDOUBLE, "STDDEV1", &m_Statistics.stddev[0], "Standard Deviation Channel 1", &status); 1073 if (channels() > 1) 1074 { 1075 fits_write_key(fptr, TDOUBLE, "STDDEV2", &m_Statistics.stddev[1], "Standard Deviation Channel 2", &status); 1076 fits_write_key(fptr, TDOUBLE, "STDDEV3", &m_Statistics.stddev[2], "Standard Deviation Channel 3", &status); 1077 } 1078 } 1079 1080 // Skip first 10 standard records and copy the rest. 1081 for (int i = 10; i < m_HeaderRecords.count(); i++) 1082 { 1083 QString key = m_HeaderRecords[i].key; 1084 const char *comment = m_HeaderRecords[i].comment.toLatin1().constBegin(); 1085 QVariant value = m_HeaderRecords[i].value; 1086 1087 switch (value.type()) 1088 { 1089 case QVariant::Int: 1090 { 1091 int number = value.toInt(); 1092 fits_write_key(fptr, TINT, key.toLatin1().constData(), &number, comment, &status); 1093 } 1094 break; 1095 1096 case QVariant::Double: 1097 { 1098 double number = value.toDouble(); 1099 fits_write_key(fptr, TDOUBLE, key.toLatin1().constData(), &number, comment, &status); 1100 } 1101 break; 1102 1103 case QVariant::String: 1104 default: 1105 { 1106 char valueBuffer[256] = {0}; 1107 strncpy(valueBuffer, value.toString().toLatin1().constData(), 256 - 1); 1108 fits_write_key(fptr, TSTRING, key.toLatin1().constData(), valueBuffer, comment, &status); 1109 } 1110 } 1111 } 1112 1113 // ISO Date 1114 if (fits_write_date(fptr, &status)) 1115 { 1116 m_LastError = i18n("Failed to update date: %1", fitsErrorToString(status)); 1117 return false; 1118 } 1119 1120 QString history = 1121 QString("Modified by KStars on %1").arg(QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss")); 1122 // History 1123 if (fits_write_history(fptr, history.toLatin1(), &status)) 1124 { 1125 m_LastError = i18n("Failed to update history: %1", fitsErrorToString(status)); 1126 return false; 1127 } 1128 1129 int rot = 0, mirror = 0; 1130 if (rotCounter > 0) 1131 { 1132 rot = (90 * rotCounter) % 360; 1133 if (rot < 0) 1134 rot += 360; 1135 } 1136 if (flipHCounter % 2 != 0 || flipVCounter % 2 != 0) 1137 mirror = 1; 1138 1139 if ((rot != 0) || (mirror != 0)) 1140 rotWCSFITS(rot, mirror); 1141 1142 rotCounter = flipHCounter = flipVCounter = 0; 1143 1144 // Here we need to use the actual data type 1145 if (fits_write_img(fptr, m_Statistics.dataType, 1, nelements, m_ImageBuffer, &status)) 1146 { 1147 m_LastError = i18n("Failed to write image: %1", fitsErrorToString(status)); 1148 return false; 1149 } 1150 1151 m_Filename = newFilename; 1152 1153 fits_flush_file(fptr, &status); 1154 1155 qCInfo(KSTARS_FITS) << "Saved FITS file:" << m_Filename; 1156 1157 return true; 1158 } 1159 1160 void FITSData::clearImageBuffers() 1161 { 1162 delete[] m_ImageBuffer; 1163 m_ImageBuffer = nullptr; 1164 if(m_ImageRoiBuffer != nullptr ) 1165 { 1166 delete[] m_ImageRoiBuffer; 1167 m_ImageRoiBuffer = nullptr; 1168 1169 } 1170 //m_BayerBuffer = nullptr; 1171 } 1172 1173 void FITSData::makeRoiBuffer(QRect roi) 1174 { 1175 if (!roi.isValid()) 1176 return; 1177 1178 uint32_t channelSize = roi.height() * roi.width(); 1179 if(channelSize > m_Statistics.samples_per_channel || channelSize == 1) 1180 { 1181 return; 1182 } 1183 if(m_ImageRoiBuffer != nullptr ) 1184 { 1185 delete[] m_ImageRoiBuffer; 1186 m_ImageRoiBuffer = nullptr; 1187 } 1188 int xoffset = roi.topLeft().x() - 1; 1189 int yoffset = roi.topLeft().y() - 1; 1190 uint32_t bpp = m_Statistics.bytesPerPixel; 1191 m_ImageRoiBuffer = new uint8_t[roi.height()*roi.width()*m_Statistics.channels * m_Statistics.bytesPerPixel](); 1192 for(int n = 0 ; n < m_Statistics.channels ; n++) 1193 { 1194 for(int i = 0; i < roi.height(); i++) 1195 { 1196 size_t i1 = n * channelSize * bpp + i * roi.width() * bpp; 1197 size_t i2 = n * m_Statistics.samples_per_channel * bpp + (yoffset + i) * width() * bpp + xoffset * bpp; 1198 memcpy(&m_ImageRoiBuffer[i1], 1199 &m_ImageBuffer[i2], 1200 roi.width() * bpp); 1201 } 1202 1203 } 1204 memcpy(&m_ROIStatistics, &m_Statistics, sizeof(FITSImage::Statistic)); 1205 m_ROIStatistics.samples_per_channel = roi.height() * roi.width(); 1206 m_ROIStatistics.width = roi.width(); 1207 m_ROIStatistics.height = roi.height(); 1208 calculateStats(false, true); 1209 } 1210 void FITSData::calculateStats(bool refresh, bool roi) 1211 { 1212 // Calculate min max 1213 if(roi == false) 1214 { 1215 calculateMinMax(refresh); 1216 calculateMedian(refresh); 1217 1218 // Try to read mean/median/stddev if in file 1219 if (refresh == false && fptr) 1220 { 1221 int status = 0; 1222 int nfound = 0; 1223 if (fits_read_key_dbl(fptr, "MEAN1", &m_Statistics.mean[0], nullptr, &status) == 0) 1224 nfound++; 1225 // NB. These could fail if missing, which is OK. 1226 fits_read_key_dbl(fptr, "MEAN2", & m_Statistics.mean[1], nullptr, &status); 1227 fits_read_key_dbl(fptr, "MEAN3", &m_Statistics.mean[2], nullptr, &status); 1228 1229 status = 0; 1230 if (fits_read_key_dbl(fptr, "STDDEV1", &m_Statistics.stddev[0], nullptr, &status) == 0) 1231 nfound++; 1232 // NB. These could fail if missing, which is OK. 1233 fits_read_key_dbl(fptr, "STDDEV2", &m_Statistics.stddev[1], nullptr, &status); 1234 fits_read_key_dbl(fptr, "STDDEV3", &m_Statistics.stddev[2], nullptr, &status); 1235 1236 // If all is OK, we're done 1237 if (nfound == 2) 1238 return; 1239 } 1240 1241 // Get standard deviation and mean in one run 1242 switch (m_Statistics.dataType) 1243 { 1244 case TBYTE: 1245 calculateStdDev<uint8_t>(); 1246 break; 1247 1248 case TSHORT: 1249 calculateStdDev<int16_t>(); 1250 break; 1251 1252 case TUSHORT: 1253 calculateStdDev<uint16_t>(); 1254 break; 1255 1256 case TLONG: 1257 calculateStdDev<int32_t>(); 1258 break; 1259 1260 case TULONG: 1261 calculateStdDev<uint32_t>(); 1262 break; 1263 1264 case TFLOAT: 1265 calculateStdDev<float>(); 1266 break; 1267 1268 case TLONGLONG: 1269 calculateStdDev<int64_t>(); 1270 break; 1271 1272 case TDOUBLE: 1273 calculateStdDev<double>(); 1274 break; 1275 1276 default: 1277 return; 1278 } 1279 1280 // FIXME That's not really SNR, must implement a proper solution for this value 1281 m_Statistics.SNR = m_Statistics.mean[0] / m_Statistics.stddev[0]; 1282 } 1283 else 1284 { 1285 calculateMinMax(refresh, roi); 1286 calculateMedian(refresh, roi); 1287 1288 switch (m_ROIStatistics.dataType) 1289 { 1290 case TBYTE: 1291 calculateStdDev<uint8_t>(roi); 1292 break; 1293 1294 case TSHORT: 1295 calculateStdDev<int16_t>(roi); 1296 break; 1297 1298 case TUSHORT: 1299 calculateStdDev<uint16_t>(roi); 1300 break; 1301 1302 case TLONG: 1303 calculateStdDev<int32_t>(roi); 1304 break; 1305 1306 case TULONG: 1307 calculateStdDev<uint32_t>(roi); 1308 break; 1309 1310 case TFLOAT: 1311 calculateStdDev<float>(roi); 1312 break; 1313 1314 case TLONGLONG: 1315 calculateStdDev<int64_t>(roi); 1316 break; 1317 1318 case TDOUBLE: 1319 calculateStdDev<double>(roi); 1320 break; 1321 1322 default: 1323 return; 1324 } 1325 } 1326 } 1327 1328 void FITSData::calculateMinMax(bool refresh, bool roi) 1329 { 1330 if(roi == false) 1331 { 1332 int status, nfound = 0; 1333 1334 status = 0; 1335 1336 // Only fetch from header if we have a single channel 1337 // Otherwise, calculate manually. 1338 if (fptr != nullptr && !refresh) 1339 { 1340 1341 status = 0; 1342 1343 if (fits_read_key_dbl(fptr, "DATAMIN", &(m_Statistics.min[0]), nullptr, &status) == 0) 1344 nfound++; 1345 else if (fits_read_key_dbl(fptr, "MIN1", &(m_Statistics.min[0]), nullptr, &status) == 0) 1346 nfound++; 1347 1348 // NB. These could fail if missing, which is OK. 1349 fits_read_key_dbl(fptr, "MIN2", &m_Statistics.min[1], nullptr, &status); 1350 fits_read_key_dbl(fptr, "MIN3", &m_Statistics.min[2], nullptr, &status); 1351 1352 status = 0; 1353 1354 if (fits_read_key_dbl(fptr, "DATAMAX", &(m_Statistics.max[0]), nullptr, &status) == 0) 1355 nfound++; 1356 else if (fits_read_key_dbl(fptr, "MAX1", &(m_Statistics.max[0]), nullptr, &status) == 0) 1357 nfound++; 1358 1359 // NB. These could fail if missing, which is OK. 1360 fits_read_key_dbl(fptr, "MAX2", &m_Statistics.max[1], nullptr, &status); 1361 fits_read_key_dbl(fptr, "MAX3", &m_Statistics.max[2], nullptr, &status); 1362 1363 // If we found both keywords, no need to calculate them, unless they are both zeros 1364 if (nfound == 2 && !(m_Statistics.min[0] == 0 && m_Statistics.max[0] == 0)) 1365 return; 1366 } 1367 1368 m_Statistics.min[0] = 1.0E30; 1369 m_Statistics.max[0] = -1.0E30; 1370 1371 m_Statistics.min[1] = 1.0E30; 1372 m_Statistics.max[1] = -1.0E30; 1373 1374 m_Statistics.min[2] = 1.0E30; 1375 m_Statistics.max[2] = -1.0E30; 1376 1377 switch (m_Statistics.dataType) 1378 { 1379 case TBYTE: 1380 calculateMinMax<uint8_t>(); 1381 break; 1382 1383 case TSHORT: 1384 calculateMinMax<int16_t>(); 1385 break; 1386 1387 case TUSHORT: 1388 calculateMinMax<uint16_t>(); 1389 break; 1390 1391 case TLONG: 1392 calculateMinMax<int32_t>(); 1393 break; 1394 1395 case TULONG: 1396 calculateMinMax<uint32_t>(); 1397 break; 1398 1399 case TFLOAT: 1400 calculateMinMax<float>(); 1401 break; 1402 1403 case TLONGLONG: 1404 calculateMinMax<int64_t>(); 1405 break; 1406 1407 case TDOUBLE: 1408 calculateMinMax<double>(); 1409 break; 1410 1411 default: 1412 break; 1413 } 1414 } 1415 else 1416 { 1417 m_ROIStatistics.min[0] = 1.0E30; 1418 m_ROIStatistics.max[0] = -1.0E30; 1419 1420 m_ROIStatistics.min[1] = 1.0E30; 1421 m_ROIStatistics.max[1] = -1.0E30; 1422 1423 m_ROIStatistics.min[2] = 1.0E30; 1424 m_ROIStatistics.max[2] = -1.0E30; 1425 1426 switch (m_Statistics.dataType) 1427 { 1428 case TBYTE: 1429 calculateMinMax<uint8_t>(roi); 1430 break; 1431 1432 case TSHORT: 1433 calculateMinMax<int16_t>(roi); 1434 break; 1435 1436 case TUSHORT: 1437 calculateMinMax<uint16_t>(roi); 1438 break; 1439 1440 case TLONG: 1441 calculateMinMax<int32_t>(roi); 1442 break; 1443 1444 case TULONG: 1445 calculateMinMax<uint32_t>(roi); 1446 break; 1447 1448 case TFLOAT: 1449 calculateMinMax<float>(roi); 1450 break; 1451 1452 case TLONGLONG: 1453 calculateMinMax<int64_t>(roi); 1454 break; 1455 1456 case TDOUBLE: 1457 calculateMinMax<double>(roi); 1458 break; 1459 1460 default: 1461 break; 1462 } 1463 1464 } 1465 } 1466 1467 void FITSData::calculateMedian(bool refresh, bool roi) 1468 { 1469 if(!roi) 1470 { 1471 int status, nfound = 0; 1472 1473 status = 0; 1474 1475 // Only fetch from header if we have a single channel 1476 // Otherwise, calculate manually. 1477 if (fptr != nullptr && !refresh) 1478 { 1479 status = 0; 1480 if (fits_read_key_dbl(fptr, "MEDIAN1", &m_Statistics.median[0], nullptr, &status) == 0) 1481 nfound++; 1482 1483 // NB. These could fail if missing, which is OK. 1484 fits_read_key_dbl(fptr, "MEDIAN2", &m_Statistics.median[1], nullptr, &status); 1485 fits_read_key_dbl(fptr, "MEDIAN3", &m_Statistics.median[2], nullptr, &status); 1486 1487 if (nfound == 1) 1488 return; 1489 } 1490 1491 m_Statistics.median[RED_CHANNEL] = 0; 1492 m_Statistics.median[GREEN_CHANNEL] = 0; 1493 m_Statistics.median[BLUE_CHANNEL] = 0; 1494 1495 switch (m_Statistics.dataType) 1496 { 1497 case TBYTE: 1498 calculateMedian<uint8_t>(); 1499 break; 1500 1501 case TSHORT: 1502 calculateMedian<int16_t>(); 1503 break; 1504 1505 case TUSHORT: 1506 calculateMedian<uint16_t>(); 1507 break; 1508 1509 case TLONG: 1510 calculateMedian<int32_t>(); 1511 break; 1512 1513 case TULONG: 1514 calculateMedian<uint32_t>(); 1515 break; 1516 1517 case TFLOAT: 1518 calculateMedian<float>(); 1519 break; 1520 1521 case TLONGLONG: 1522 calculateMedian<int64_t>(); 1523 break; 1524 1525 case TDOUBLE: 1526 calculateMedian<double>(); 1527 break; 1528 1529 default: 1530 break; 1531 } 1532 } 1533 else 1534 { 1535 m_ROIStatistics.median[RED_CHANNEL] = 0; 1536 m_ROIStatistics.median[GREEN_CHANNEL] = 0; 1537 m_ROIStatistics.median[BLUE_CHANNEL] = 0; 1538 1539 switch (m_ROIStatistics.dataType) 1540 { 1541 case TBYTE: 1542 calculateMedian<uint8_t>(roi); 1543 break; 1544 1545 case TSHORT: 1546 calculateMedian<int16_t>(roi); 1547 break; 1548 1549 case TUSHORT: 1550 calculateMedian<uint16_t>(roi); 1551 break; 1552 1553 case TLONG: 1554 calculateMedian<int32_t>(roi); 1555 break; 1556 1557 case TULONG: 1558 calculateMedian<uint32_t>(roi); 1559 break; 1560 1561 case TFLOAT: 1562 calculateMedian<float>(roi); 1563 break; 1564 1565 case TLONGLONG: 1566 calculateMedian<int64_t>(roi); 1567 break; 1568 1569 case TDOUBLE: 1570 calculateMedian<double>(roi); 1571 break; 1572 1573 default: 1574 break; 1575 } 1576 1577 } 1578 } 1579 1580 template <typename T> 1581 void FITSData::calculateMedian(bool roi) 1582 { 1583 auto * buffer = reinterpret_cast<T *>(roi ? m_ImageRoiBuffer : m_ImageBuffer); 1584 const uint32_t maxMedianSize = 500000; 1585 uint32_t medianSize = roi ? m_ROIStatistics.samples_per_channel : m_Statistics.samples_per_channel; 1586 uint8_t downsample = 1; 1587 if (medianSize > maxMedianSize) 1588 { 1589 downsample = (static_cast<double>(medianSize) / maxMedianSize) + 0.999; 1590 medianSize /= downsample; 1591 } 1592 // Ideally samples would be declared like this... 1593 //std::vector<T> samples; 1594 // Unfortunately this doesn't compile on Mac - see the comments in robuststatistics.cpp for more details 1595 // So for now declare samples like this... 1596 std::vector<int32_t> samples; 1597 samples.reserve(medianSize); 1598 1599 for (uint8_t n = 0; n < m_Statistics.channels; n++) 1600 { 1601 auto *oneChannel = buffer + n * (roi ? m_ROIStatistics.samples_per_channel : m_Statistics.samples_per_channel); 1602 for (uint32_t upto = 0; upto < (roi ? m_ROIStatistics.samples_per_channel : m_Statistics.samples_per_channel); 1603 upto += downsample) 1604 samples.push_back(oneChannel[upto]); 1605 auto median = Mathematics::RobustStatistics::ComputeLocation(Mathematics::RobustStatistics::LOCATION_MEDIAN, samples); 1606 roi ? m_ROIStatistics.median[n] = median : m_Statistics.median[n] = median; 1607 } 1608 } 1609 1610 template <typename T> 1611 QPair<T, T> FITSData::getParitionMinMax(uint32_t start, uint32_t stride, bool roi) 1612 { 1613 auto * buffer = reinterpret_cast<T *>(roi ? m_ImageRoiBuffer : m_ImageBuffer); 1614 T min = std::numeric_limits<T>::max(); 1615 T max = std::numeric_limits<T>::min(); 1616 1617 uint32_t end = start + stride; 1618 1619 for (uint32_t i = start; i < end; i++) 1620 { 1621 min = qMin(buffer[i], min); 1622 max = qMax(buffer[i], max); 1623 // if (buffer[i] < min) 1624 // min = buffer[i]; 1625 // else if (buffer[i] > max) 1626 // max = buffer[i]; 1627 } 1628 1629 return qMakePair(min, max); 1630 } 1631 1632 template <typename T> 1633 void FITSData::calculateMinMax(bool roi) 1634 { 1635 T min = std::numeric_limits<T>::max(); 1636 T max = std::numeric_limits<T>::min(); 1637 1638 1639 // Create N threads 1640 const uint8_t nThreads = 16; 1641 1642 for (int n = 0; n < m_Statistics.channels; n++) 1643 { 1644 uint32_t cStart = n * (roi ? m_ROIStatistics.samples_per_channel : m_Statistics.samples_per_channel); 1645 1646 // Calculate how many elements we process per thread 1647 uint32_t tStride = (roi ? m_ROIStatistics.samples_per_channel : m_Statistics.samples_per_channel) / nThreads; 1648 1649 // Calculate the final stride since we can have some left over due to division above 1650 uint32_t fStride = tStride + ((roi ? m_ROIStatistics.samples_per_channel : m_Statistics.samples_per_channel) - 1651 (tStride * nThreads)); 1652 1653 // Start location for inspecting elements 1654 uint32_t tStart = cStart; 1655 1656 // List of futures 1657 QList<QFuture<QPair<T, T>>> futures; 1658 1659 for (int i = 0; i < nThreads; i++) 1660 { 1661 // Run threads 1662 futures.append(QtConcurrent::run(this, &FITSData::getParitionMinMax<T>, tStart, (i == (nThreads - 1)) ? fStride : tStride, 1663 roi)); 1664 tStart += tStride; 1665 } 1666 1667 // Now wait for results 1668 for (int i = 0; i < nThreads; i++) 1669 { 1670 QPair<T, T> result = futures[i].result(); 1671 min = qMin(result.first, min); 1672 max = qMax(result.second, max); 1673 } 1674 1675 if(!roi) 1676 { 1677 m_Statistics.min[n] = min; 1678 m_Statistics.max[n] = max; 1679 } 1680 else 1681 { 1682 m_ROIStatistics.min[n] = min; 1683 m_ROIStatistics.max[n] = max; 1684 } 1685 } 1686 1687 } 1688 1689 // This struct is used when returning results from the threaded getSumAndSquaredSum calculations 1690 // used to compute the mean and variance of the image. 1691 struct SumData 1692 { 1693 double sum; 1694 double squaredSum; 1695 double numSamples; 1696 SumData(double s, double sq, int n) : sum(s), squaredSum(sq), numSamples(n) {} 1697 SumData() : sum(0), squaredSum(0), numSamples(0) {} 1698 }; 1699 1700 template <typename T> 1701 SumData getSumAndSquaredSum(uint32_t start, uint32_t stride, uint8_t *buff) 1702 { 1703 auto * buffer = reinterpret_cast<T *>(buff); 1704 const uint32_t end = start + stride; 1705 double sum = 0; 1706 double squaredSum = 0; 1707 for (uint32_t i = start; i < end; i++) 1708 { 1709 double sample = buffer[i]; 1710 sum += sample; 1711 squaredSum += sample * sample; 1712 } 1713 const double numSamples = end - start; 1714 return SumData(sum, squaredSum, numSamples); 1715 } 1716 1717 template <typename T> 1718 void FITSData::calculateStdDev(bool roi ) 1719 { 1720 // Create N threads 1721 const uint8_t nThreads = 16; 1722 1723 for (int n = 0; n < m_Statistics.channels; n++) 1724 { 1725 uint32_t cStart = n * (roi ? m_ROIStatistics.samples_per_channel : m_Statistics.samples_per_channel); 1726 1727 // Calculate how many elements we process per thread 1728 uint32_t tStride = (roi ? m_ROIStatistics.samples_per_channel : m_Statistics.samples_per_channel) / nThreads; 1729 1730 // Calculate the final stride since we can have some left over due to division above 1731 uint32_t fStride = tStride + ((roi ? m_ROIStatistics.samples_per_channel : m_Statistics.samples_per_channel) - 1732 (tStride * nThreads)); 1733 1734 // Start location for inspecting elements 1735 uint32_t tStart = cStart; 1736 1737 // List of futures 1738 QList<QFuture<SumData>> futures; 1739 1740 for (int i = 0; i < nThreads; i++) 1741 { 1742 // Run threads 1743 uint8_t *buff = roi ? m_ImageRoiBuffer : m_ImageBuffer; 1744 futures.append(QtConcurrent::run(&getSumAndSquaredSum<T>, tStart, 1745 (i == (nThreads - 1)) ? fStride : tStride, buff)); 1746 tStart += tStride; 1747 } 1748 1749 // Now wait for results 1750 double sum = 0, squared_sum = 0; 1751 double numSamples = 0; 1752 for (int i = 0; i < nThreads; i++) 1753 { 1754 const SumData result = futures[i].result(); 1755 sum += result.sum; 1756 squared_sum += result.squaredSum; 1757 numSamples += result.numSamples; 1758 1759 } 1760 if (numSamples <= 0) continue; 1761 const double mean = sum / numSamples; 1762 const double variance = squared_sum / numSamples - mean * mean; 1763 if(!roi) 1764 { 1765 m_Statistics.mean[n] = mean; 1766 m_Statistics.stddev[n] = sqrt(variance); 1767 } 1768 else 1769 { 1770 m_ROIStatistics.mean[n] = mean; 1771 m_ROIStatistics.stddev[n] = sqrt(variance); 1772 } 1773 } 1774 } 1775 1776 QVector<double> FITSData::createGaussianKernel(int size, double sigma) 1777 { 1778 QVector<double> kernel(size * size); 1779 kernel.fill(0.0, size * size); 1780 1781 double kernelSum = 0.0; 1782 int fOff = (size - 1) / 2; 1783 double normal = 1.0 / (2.0 * M_PI * sigma * sigma); 1784 for (int y = -fOff; y <= fOff; y++) 1785 { 1786 for (int x = -fOff; x <= fOff; x++) 1787 { 1788 double distance = ((y * y) + (x * x)) / (2.0 * sigma * sigma); 1789 int index = (y + fOff) * size + (x + fOff); 1790 kernel[index] = normal * qExp(-distance); 1791 kernelSum += kernel.at(index); 1792 } 1793 } 1794 for (int y = 0; y < size; y++) 1795 { 1796 for (int x = 0; x < size; x++) 1797 { 1798 int index = y * size + x; 1799 kernel[index] = kernel.at(index) * 1.0 / kernelSum; 1800 } 1801 } 1802 1803 return kernel; 1804 } 1805 1806 template <typename T> 1807 void FITSData::convolutionFilter(const QVector<double> &kernel, int kernelSize) 1808 { 1809 T * imagePtr = reinterpret_cast<T *>(m_ImageBuffer); 1810 1811 // Create variable for pixel data for each kernel 1812 T gt = 0; 1813 1814 // This is how much your center pixel is offset from the border of your kernel 1815 int fOff = (kernelSize - 1) / 2; 1816 1817 // Start with the pixel that is offset fOff from top and fOff from the left side 1818 // this is so entire kernel is on your image 1819 for (int offsetY = 0; offsetY < m_Statistics.height; offsetY++) 1820 { 1821 for (int offsetX = 0; offsetX < m_Statistics.width; offsetX++) 1822 { 1823 // reset gray value to 0 1824 gt = 0; 1825 // position of the kernel center pixel 1826 int byteOffset = offsetY * m_Statistics.width + offsetX; 1827 1828 // kernel calculations 1829 for (int filterY = -fOff; filterY <= fOff; filterY++) 1830 { 1831 for (int filterX = -fOff; filterX <= fOff; filterX++) 1832 { 1833 if ((offsetY + filterY) >= 0 && (offsetY + filterY) < m_Statistics.height 1834 && ((offsetX + filterX) >= 0 && (offsetX + filterX) < m_Statistics.width )) 1835 { 1836 1837 int calcOffset = byteOffset + filterX + filterY * m_Statistics.width; 1838 int index = (filterY + fOff) * kernelSize + (filterX + fOff); 1839 double kernelValue = kernel.at(index); 1840 gt += (imagePtr[calcOffset]) * kernelValue; 1841 } 1842 } 1843 } 1844 1845 // set new data in the other byte array for your image data 1846 imagePtr[byteOffset] = gt; 1847 } 1848 } 1849 } 1850 1851 template <typename T> 1852 void FITSData::gaussianBlur(int kernelSize, double sigma) 1853 { 1854 // Size must be an odd number! 1855 if (kernelSize % 2 == 0) 1856 { 1857 kernelSize--; 1858 qCInfo(KSTARS_FITS) << "Warning, size must be an odd number, correcting size to " << kernelSize; 1859 } 1860 // Edge must be a positive number! 1861 if (kernelSize < 1) 1862 { 1863 kernelSize = 1; 1864 } 1865 1866 QVector<double> gaussianKernel = createGaussianKernel(kernelSize, sigma); 1867 convolutionFilter<T>(gaussianKernel, kernelSize); 1868 } 1869 1870 void FITSData::setMinMax(double newMin, double newMax, uint8_t channel) 1871 { 1872 m_Statistics.min[channel] = newMin; 1873 m_Statistics.max[channel] = newMax; 1874 } 1875 1876 bool FITSData::parseHeader() 1877 { 1878 char * header = nullptr; 1879 int status = 0, nkeys = 0; 1880 1881 if (fits_hdr2str(fptr, 0, nullptr, 0, &header, &nkeys, &status)) 1882 { 1883 fits_report_error(stderr, status); 1884 free(header); 1885 return false; 1886 } 1887 1888 m_HeaderRecords.clear(); 1889 QString recordList = QString(header); 1890 1891 for (int i = 0; i < nkeys; i++) 1892 { 1893 Record oneRecord; 1894 // Quotes cause issues for simplified below so we're removing them. 1895 QString record = recordList.mid(i * 80, 80).remove("'"); 1896 QStringList properties = record.split(QRegExp("[=/]")); 1897 // If it is only a comment 1898 if (properties.size() == 1) 1899 { 1900 oneRecord.key = properties[0].mid(0, 7); 1901 oneRecord.comment = properties[0].mid(8).simplified(); 1902 } 1903 else 1904 { 1905 oneRecord.key = properties[0].simplified(); 1906 oneRecord.value = properties[1].simplified(); 1907 if (properties.size() > 2) 1908 oneRecord.comment = properties[2].simplified(); 1909 1910 // Try to guess the value. 1911 // Test for integer & double. If neither, then leave it as "string". 1912 bool ok = false; 1913 1914 // Is it Integer? 1915 oneRecord.value.toInt(&ok); 1916 if (ok) 1917 oneRecord.value.convert(QMetaType::Int); 1918 else 1919 { 1920 // Is it double? 1921 oneRecord.value.toDouble(&ok); 1922 if (ok) 1923 oneRecord.value.convert(QMetaType::Double); 1924 } 1925 } 1926 1927 m_HeaderRecords.append(oneRecord); 1928 } 1929 1930 free(header); 1931 1932 return true; 1933 } 1934 1935 bool FITSData::getRecordValue(const QString &key, QVariant &value) const 1936 { 1937 auto result = std::find_if(m_HeaderRecords.begin(), m_HeaderRecords.end(), [&key](const Record & oneRecord) 1938 { 1939 return (oneRecord.key == key && oneRecord.value.isValid()); 1940 }); 1941 1942 if (result != m_HeaderRecords.end()) 1943 { 1944 value = (*result).value; 1945 return true; 1946 } 1947 return false; 1948 } 1949 1950 bool FITSData::parseSolution(FITSImage::Solution &solution) const 1951 { 1952 dms angleValue; 1953 bool raOK = false, deOK = false, coordOK = false, scaleOK = false; 1954 QVariant value; 1955 1956 // Reset all 1957 solution.fieldWidth = solution.fieldHeight = solution.pixscale = solution.ra = solution.dec = -1; 1958 1959 // RA 1960 if (getRecordValue("OBJCTRA", value)) 1961 { 1962 angleValue = dms::fromString(value.toString(), false); 1963 solution.ra = angleValue.Degrees(); 1964 raOK = true; 1965 } 1966 else if (getRecordValue("RA", value)) 1967 { 1968 solution.ra = value.toDouble(&raOK); 1969 } 1970 1971 // DE 1972 if (getRecordValue("OBJCTDEC", value)) 1973 { 1974 angleValue = dms::fromString(value.toString(), true); 1975 solution.dec = angleValue.Degrees(); 1976 deOK = true; 1977 } 1978 else if (getRecordValue("DEC", value)) 1979 { 1980 solution.dec = value.toDouble(&deOK); 1981 } 1982 1983 coordOK = raOK && deOK; 1984 1985 // PixScale 1986 double scale = -1; 1987 if (getRecordValue("SCALE", value)) 1988 { 1989 scale = value.toDouble(); 1990 } 1991 1992 double focal_length = -1; 1993 if (getRecordValue("FOCALLEN", value)) 1994 { 1995 focal_length = value.toDouble(); 1996 } 1997 1998 double pixsize1 = -1, pixsize2 = -1; 1999 // Pixel Size 1 2000 if (getRecordValue("PIXSIZE1", value)) 2001 { 2002 pixsize1 = value.toDouble(); 2003 } 2004 // Pixel Size 2 2005 if (getRecordValue("PIXSIZE2", value)) 2006 { 2007 pixsize2 = value.toDouble(); 2008 } 2009 2010 int binx = 1, biny = 1; 2011 // Binning X 2012 if (getRecordValue("XBINNING", value)) 2013 { 2014 binx = value.toDouble(); 2015 } 2016 // Binning Y 2017 if (getRecordValue("YBINNING", value)) 2018 { 2019 biny = value.toDouble(); 2020 } 2021 2022 if (pixsize1 > 0 && pixsize2 > 0) 2023 { 2024 // If we have scale, then that's it 2025 if (scale > 0) 2026 { 2027 // Arcsecs per pixel 2028 solution.pixscale = scale; 2029 // Arcmins 2030 solution.fieldWidth = (m_Statistics.width * scale) / 60.0; 2031 // Arcmins, and account for pixel ratio if non-squared. 2032 solution.fieldHeight = (m_Statistics.height * scale * (pixsize2 / pixsize1)) / 60.0; 2033 } 2034 else if (focal_length > 0) 2035 { 2036 // Arcmins 2037 solution.fieldWidth = ((206264.8062470963552 * m_Statistics.width * (pixsize1 / 1000.0)) / (focal_length * binx)) / 60.0; 2038 // Arsmins 2039 solution.fieldHeight = ((206264.8062470963552 * m_Statistics.height * (pixsize2 / 1000.0)) / (focal_length * biny)) / 60.0; 2040 // Arcsecs per pixel 2041 solution.pixscale = (206264.8062470963552 * (pixsize1 / 1000.0)) / (focal_length * binx); 2042 } 2043 2044 scaleOK = true; 2045 } 2046 2047 return (coordOK || scaleOK); 2048 } 2049 2050 QFuture<bool> FITSData::findStars(StarAlgorithm algorithm, const QRect &trackingBox) 2051 { 2052 if (m_StarFindFuture.isRunning()) 2053 m_StarFindFuture.waitForFinished(); 2054 2055 starAlgorithm = algorithm; 2056 qDeleteAll(starCenters); 2057 starCenters.clear(); 2058 starsSearched = true; 2059 2060 switch (algorithm) 2061 { 2062 case ALGORITHM_SEP: 2063 { 2064 m_StarDetector.reset(new FITSSEPDetector(this)); 2065 m_StarDetector->setSettings(m_SourceExtractorSettings); 2066 if (m_Mode == FITS_NORMAL && trackingBox.isNull()) 2067 { 2068 if (Options::quickHFR()) 2069 { 2070 //Just finds stars in the center 25% of the image. 2071 const int w = getStatistics().width; 2072 const int h = getStatistics().height; 2073 QRect middle(static_cast<int>(w * 0.25), static_cast<int>(h * 0.25), w / 2, h / 2); 2074 m_StarFindFuture = m_StarDetector->findSources(middle); 2075 return m_StarFindFuture; 2076 } 2077 } 2078 m_StarFindFuture = m_StarDetector->findSources(trackingBox); 2079 return m_StarFindFuture; 2080 } 2081 2082 case ALGORITHM_GRADIENT: 2083 default: 2084 { 2085 m_StarDetector.reset(new FITSGradientDetector(this)); 2086 m_StarDetector->setSettings(m_SourceExtractorSettings); 2087 m_StarFindFuture = m_StarDetector->findSources(trackingBox); 2088 return m_StarFindFuture; 2089 } 2090 2091 case ALGORITHM_CENTROID: 2092 { 2093 #ifndef KSTARS_LITE 2094 m_StarDetector.reset(new FITSCentroidDetector(this)); 2095 m_StarDetector->setSettings(m_SourceExtractorSettings); 2096 // We need JMIndex calculated from histogram 2097 if (!isHistogramConstructed()) 2098 constructHistogram(); 2099 m_StarDetector->configure("JMINDEX", m_JMIndex); 2100 m_StarFindFuture = m_StarDetector->findSources(trackingBox); 2101 return m_StarFindFuture; 2102 } 2103 #else 2104 { 2105 m_StarDetector.reset(new FITSCentroidDetector(this)); 2106 m_StarFindFuture = starDetector->findSources(trackingBox); 2107 return m_StarFindFuture; 2108 } 2109 #endif 2110 2111 case ALGORITHM_THRESHOLD: 2112 { 2113 m_StarDetector.reset(new FITSThresholdDetector(this)); 2114 m_StarDetector->setSettings(m_SourceExtractorSettings); 2115 m_StarDetector->configure("THRESHOLD_PERCENTAGE", Options::focusThreshold()); 2116 m_StarFindFuture = m_StarDetector->findSources(trackingBox); 2117 return m_StarFindFuture; 2118 } 2119 2120 case ALGORITHM_BAHTINOV: 2121 { 2122 m_StarDetector.reset(new FITSBahtinovDetector(this)); 2123 m_StarDetector->setSettings(m_SourceExtractorSettings); 2124 m_StarDetector->configure("NUMBER_OF_AVERAGE_ROWS", Options::focusMultiRowAverage()); 2125 m_StarFindFuture = m_StarDetector->findSources(trackingBox); 2126 return m_StarFindFuture; 2127 } 2128 } 2129 } 2130 2131 int FITSData::filterStars(QSharedPointer<ImageMask> mask) 2132 { 2133 if (mask.isNull() == false) 2134 { 2135 starCenters.erase(std::remove_if(starCenters.begin(), starCenters.end(), 2136 [&](Edge * edge) 2137 { 2138 return (mask->isVisible(edge->x, edge->y) == false); 2139 }), starCenters.end()); 2140 } 2141 2142 return starCenters.count(); 2143 2144 } 2145 2146 double FITSData::getHFR(HFRType type) 2147 { 2148 if (starCenters.empty()) 2149 return -1; 2150 2151 if (cacheHFR >= 0 && cacheHFRType == type) 2152 return cacheHFR; 2153 2154 m_SelectedHFRStar.invalidate(); 2155 2156 if (type == HFR_MAX) 2157 { 2158 2159 int maxVal = 0; 2160 int maxIndex = 0; 2161 for (int i = 0; i < starCenters.count(); i++) 2162 { 2163 if (starCenters[i]->HFR > maxVal) 2164 { 2165 maxIndex = i; 2166 maxVal = starCenters[i]->HFR; 2167 } 2168 } 2169 2170 m_SelectedHFRStar = *starCenters[maxIndex]; 2171 cacheHFR = starCenters[maxIndex]->HFR; 2172 cacheHFRType = type; 2173 return cacheHFR; 2174 } 2175 else if (type == HFR_HIGH) 2176 { 2177 // Reject all stars within 10% of border 2178 int minX = width() / 10; 2179 int minY = height() / 10; 2180 int maxX = width() - minX; 2181 int maxY = height() - minY; 2182 starCenters.erase(std::remove_if(starCenters.begin(), starCenters.end(), [minX, minY, maxX, maxY](Edge * oneStar) 2183 { 2184 return (oneStar->x < minX || oneStar->x > maxX || oneStar->y < minY || oneStar->y > maxY); 2185 }), starCenters.end()); 2186 // Top 5% 2187 if (starCenters.empty()) 2188 return -1; 2189 2190 m_SelectedHFRStar = *starCenters[static_cast<int>(starCenters.size() * 0.05)]; 2191 cacheHFR = m_SelectedHFRStar.HFR; 2192 cacheHFRType = type; 2193 return cacheHFR; 2194 } 2195 else if (type == HFR_MEDIAN) 2196 { 2197 std::nth_element(starCenters.begin(), starCenters.begin() + starCenters.size() / 2, starCenters.end()); 2198 m_SelectedHFRStar = *starCenters[starCenters.size() / 2]; 2199 2200 cacheHFR = m_SelectedHFRStar.HFR; 2201 cacheHFRType = type; 2202 return cacheHFR; 2203 } 2204 2205 // We may remove saturated stars from the HFR calculation, if we have enough stars. 2206 // No real way to tell the scale, so only remove saturated stars with range 0 -> 2**16 2207 // for non-byte types. Unsigned types and floating types, or really any pixels whose 2208 // range isn't 0-64 (or 0-255 for bytes) won't have their saturated stars removed. 2209 const int saturationValue = m_Statistics.dataType == TBYTE ? 250 : 50000; 2210 int numSaturated = 0; 2211 for (auto center : starCenters) 2212 if (center->val > saturationValue) 2213 numSaturated++; 2214 const bool removeSaturatedStars = starCenters.size() - numSaturated > 20; 2215 if (removeSaturatedStars && numSaturated > 0) 2216 qCDebug(KSTARS_FITS) << "Removing " << numSaturated << " stars from HFR calculation"; 2217 2218 std::vector<double> HFRs; 2219 2220 for (auto center : starCenters) 2221 { 2222 if (removeSaturatedStars && center->val > saturationValue) continue; 2223 2224 if (type == HFR_AVERAGE) 2225 HFRs.push_back(center->HFR); 2226 else 2227 { 2228 // HFR_ADJ_AVERAGE - so adjust the HFR based on the stars brightness 2229 // HFRadj = HFR.erf(sqrt(ln(peak/background)))/(1 - background/peak) 2230 // Sanity check inputs to prevent equation blowing up 2231 if (m_SkyBackground.mean <= 0.0 || center->val < m_SkyBackground.mean) 2232 { 2233 HFRs.push_back(center->HFR); 2234 qCDebug(KSTARS_FITS) << "HFR Adj, sky background " << m_SkyBackground.mean << " star peak " << center->val << 2235 " not adjusting"; 2236 } 2237 else 2238 { 2239 const double a_div_b = center->val / m_SkyBackground.mean; 2240 const double factor = erf(sqrt(log(a_div_b))) / (1 - (1 / a_div_b)); 2241 HFRs.push_back(center->HFR * factor); 2242 qCDebug(KSTARS_FITS) << "HFR Adj, brightness adjusted from " << center->HFR << " to " << center->HFR * factor; 2243 } 2244 } 2245 } 2246 2247 auto m = Mathematics::RobustStatistics::ComputeLocation(Mathematics::RobustStatistics::LOCATION_SIGMACLIPPING, 2248 HFRs, 2); 2249 2250 cacheHFR = m; 2251 cacheHFRType = HFR_AVERAGE; 2252 return cacheHFR; 2253 } 2254 2255 double FITSData::getHFR(int x, int y, double scale) 2256 { 2257 if (starCenters.empty()) 2258 return -1; 2259 2260 for (int i = 0; i < starCenters.count(); i++) 2261 { 2262 const int maxDist = std::max(2, static_cast<int>(0.5 + 2 * starCenters[i]->width / scale)); 2263 const int dx = std::fabs(starCenters[i]->x - x); 2264 const int dy = std::fabs(starCenters[i]->y - y); 2265 if (dx <= maxDist && dy <= maxDist) 2266 { 2267 m_SelectedHFRStar = *starCenters[i]; 2268 return starCenters[i]->HFR; 2269 } 2270 } 2271 2272 return -1; 2273 } 2274 2275 double FITSData::getEccentricity() 2276 { 2277 if (starCenters.empty()) 2278 return -1; 2279 if (cacheEccentricity >= 0) 2280 return cacheEccentricity; 2281 std::vector<float> eccs; 2282 for (const auto &s : starCenters) 2283 eccs.push_back(s->ellipticity); 2284 int middle = eccs.size() / 2; 2285 std::nth_element(eccs.begin(), eccs.begin() + middle, eccs.end()); 2286 float medianEllipticity = eccs[middle]; 2287 2288 // SEP gives us the ellipticity (flattening) directly. 2289 // To get the eccentricity, use this formula: 2290 // e = sqrt(ellipticity * (2 - ellipticity)) 2291 // https://en.wikipedia.org/wiki/Eccentricity_(mathematics)#Ellipses 2292 const float eccentricity = sqrt(medianEllipticity * (2 - medianEllipticity)); 2293 cacheEccentricity = eccentricity; 2294 return eccentricity; 2295 } 2296 2297 void FITSData::applyFilter(FITSScale type, uint8_t * image, QVector<double> * min, QVector<double> * max) 2298 { 2299 if (type == FITS_NONE) 2300 return; 2301 2302 QVector<double> dataMin(3); 2303 QVector<double> dataMax(3); 2304 2305 if (min) 2306 dataMin = *min; 2307 if (max) 2308 dataMax = *max; 2309 2310 switch (type) 2311 { 2312 case FITS_AUTO_STRETCH: 2313 { 2314 for (int i = 0; i < 3; i++) 2315 { 2316 dataMin[i] = m_Statistics.mean[i] - m_Statistics.stddev[i]; 2317 dataMax[i] = m_Statistics.mean[i] + m_Statistics.stddev[i] * 3; 2318 } 2319 } 2320 break; 2321 2322 case FITS_HIGH_CONTRAST: 2323 { 2324 for (int i = 0; i < 3; i++) 2325 { 2326 dataMin[i] = m_Statistics.mean[i] + m_Statistics.stddev[i]; 2327 dataMax[i] = m_Statistics.mean[i] + m_Statistics.stddev[i] * 3; 2328 } 2329 } 2330 break; 2331 2332 case FITS_HIGH_PASS: 2333 { 2334 for (int i = 0; i < 3; i++) 2335 { 2336 dataMin[i] = m_Statistics.mean[i]; 2337 } 2338 } 2339 break; 2340 2341 default: 2342 break; 2343 } 2344 2345 switch (m_Statistics.dataType) 2346 { 2347 case TBYTE: 2348 { 2349 for (int i = 0; i < 3; i++) 2350 { 2351 dataMin[i] = dataMin[i] < 0 ? 0 : dataMin[i]; 2352 dataMax[i] = dataMax[i] > UINT8_MAX ? UINT8_MAX : dataMax[i]; 2353 } 2354 applyFilter<uint8_t>(type, image, &dataMin, &dataMax); 2355 } 2356 break; 2357 2358 case TSHORT: 2359 { 2360 for (int i = 0; i < 3; i++) 2361 { 2362 dataMin[i] = dataMin[i] < INT16_MIN ? INT16_MIN : dataMin[i]; 2363 dataMax[i] = dataMax[i] > INT16_MAX ? INT16_MAX : dataMax[i]; 2364 } 2365 applyFilter<uint16_t>(type, image, &dataMin, &dataMax); 2366 } 2367 2368 break; 2369 2370 case TUSHORT: 2371 { 2372 for (int i = 0; i < 3; i++) 2373 { 2374 dataMin[i] = dataMin[i] < 0 ? 0 : dataMin[i]; 2375 dataMax[i] = dataMax[i] > UINT16_MAX ? UINT16_MAX : dataMax[i]; 2376 } 2377 applyFilter<uint16_t>(type, image, &dataMin, &dataMax); 2378 } 2379 break; 2380 2381 case TLONG: 2382 { 2383 for (int i = 0; i < 3; i++) 2384 { 2385 dataMin[i] = dataMin[i] < INT_MIN ? INT_MIN : dataMin[i]; 2386 dataMax[i] = dataMax[i] > INT_MAX ? INT_MAX : dataMax[i]; 2387 } 2388 applyFilter<uint16_t>(type, image, &dataMin, &dataMax); 2389 } 2390 break; 2391 2392 case TULONG: 2393 { 2394 for (int i = 0; i < 3; i++) 2395 { 2396 dataMin[i] = dataMin[i] < 0 ? 0 : dataMin[i]; 2397 dataMax[i] = dataMax[i] > UINT_MAX ? UINT_MAX : dataMax[i]; 2398 } 2399 applyFilter<uint16_t>(type, image, &dataMin, &dataMax); 2400 } 2401 break; 2402 2403 case TFLOAT: 2404 { 2405 for (int i = 0; i < 3; i++) 2406 { 2407 dataMin[i] = dataMin[i] < FLT_MIN ? FLT_MIN : dataMin[i]; 2408 dataMax[i] = dataMax[i] > FLT_MAX ? FLT_MAX : dataMax[i]; 2409 } 2410 applyFilter<float>(type, image, &dataMin, &dataMax); 2411 } 2412 break; 2413 2414 case TLONGLONG: 2415 { 2416 for (int i = 0; i < 3; i++) 2417 { 2418 dataMin[i] = dataMin[i] < LLONG_MIN ? LLONG_MIN : dataMin[i]; 2419 dataMax[i] = dataMax[i] > LLONG_MAX ? LLONG_MAX : dataMax[i]; 2420 } 2421 2422 applyFilter<long>(type, image, &dataMin, &dataMax); 2423 } 2424 break; 2425 2426 case TDOUBLE: 2427 { 2428 for (int i = 0; i < 3; i++) 2429 { 2430 dataMin[i] = dataMin[i] < DBL_MIN ? DBL_MIN : dataMin[i]; 2431 dataMax[i] = dataMax[i] > DBL_MAX ? DBL_MAX : dataMax[i]; 2432 } 2433 applyFilter<double>(type, image, &dataMin, &dataMax); 2434 } 2435 2436 break; 2437 2438 default: 2439 return; 2440 } 2441 2442 if (min != nullptr) 2443 *min = dataMin; 2444 if (max != nullptr) 2445 *max = dataMax; 2446 2447 emit dataChanged(); 2448 } 2449 2450 template <typename T> 2451 void FITSData::applyFilter(FITSScale type, uint8_t * targetImage, QVector<double> * targetMin, QVector<double> * targetMax) 2452 { 2453 bool calcStats = false; 2454 T * image = nullptr; 2455 2456 if (targetImage) 2457 image = reinterpret_cast<T *>(targetImage); 2458 else 2459 { 2460 image = reinterpret_cast<T *>(m_ImageBuffer); 2461 calcStats = true; 2462 } 2463 2464 T min[3], max[3]; 2465 for (int i = 0; i < 3; i++) 2466 { 2467 min[i] = (*targetMin)[i] < std::numeric_limits<T>::min() ? std::numeric_limits<T>::min() : (*targetMin)[i]; 2468 max[i] = (*targetMax)[i] > std::numeric_limits<T>::max() ? std::numeric_limits<T>::max() : (*targetMax)[i]; 2469 } 2470 2471 2472 // Create N threads 2473 const uint8_t nThreads = 16; 2474 2475 uint32_t width = m_Statistics.width; 2476 uint32_t height = m_Statistics.height; 2477 2478 //QTime timer; 2479 //timer.start(); 2480 switch (type) 2481 { 2482 case FITS_AUTO: 2483 case FITS_LINEAR: 2484 case FITS_AUTO_STRETCH: 2485 case FITS_HIGH_CONTRAST: 2486 case FITS_LOG: 2487 case FITS_SQRT: 2488 case FITS_HIGH_PASS: 2489 { 2490 // List of futures 2491 QList<QFuture<void>> futures; 2492 QVector<double> coeff(3); 2493 2494 if (type == FITS_LOG) 2495 { 2496 for (int i = 0; i < 3; i++) 2497 coeff[i] = max[i] / std::log(1 + max[i]); 2498 } 2499 else if (type == FITS_SQRT) 2500 { 2501 for (int i = 0; i < 3; i++) 2502 coeff[i] = max[i] / sqrt(max[i]); 2503 } 2504 2505 for (int n = 0; n < m_Statistics.channels; n++) 2506 { 2507 if (type == FITS_HIGH_PASS) 2508 min[n] = m_Statistics.mean[n]; 2509 2510 uint32_t cStart = n * m_Statistics.samples_per_channel; 2511 2512 // Calculate how many elements we process per thread 2513 uint32_t tStride = m_Statistics.samples_per_channel / nThreads; 2514 2515 // Calculate the final stride since we can have some left over due to division above 2516 uint32_t fStride = tStride + (m_Statistics.samples_per_channel - (tStride * nThreads)); 2517 2518 T * runningBuffer = image + cStart; 2519 2520 if (type == FITS_LOG) 2521 { 2522 for (int i = 0; i < nThreads; i++) 2523 { 2524 // Run threads 2525 futures.append(QtConcurrent::map(runningBuffer, (runningBuffer + ((i == (nThreads - 1)) ? fStride : tStride)), [min, max, 2526 coeff, n](T & a) 2527 { 2528 a = qBound(min[n], static_cast<T>(round(coeff[n] * std::log(1 + qBound(min[n], a, max[n])))), max[n]); 2529 })); 2530 2531 runningBuffer += tStride; 2532 } 2533 } 2534 else if (type == FITS_SQRT) 2535 { 2536 for (int i = 0; i < nThreads; i++) 2537 { 2538 // Run threads 2539 futures.append(QtConcurrent::map(runningBuffer, (runningBuffer + ((i == (nThreads - 1)) ? fStride : tStride)), [min, max, 2540 coeff, n](T & a) 2541 { 2542 a = qBound(min[n], static_cast<T>(round(coeff[n] * a)), max[n]); 2543 })); 2544 } 2545 2546 runningBuffer += tStride; 2547 } 2548 else 2549 { 2550 for (int i = 0; i < nThreads; i++) 2551 { 2552 // Run threads 2553 futures.append(QtConcurrent::map(runningBuffer, (runningBuffer + ((i == (nThreads - 1)) ? fStride : tStride)), [min, max, 2554 n](T & a) 2555 { 2556 a = qBound(min[n], a, max[n]); 2557 })); 2558 runningBuffer += tStride; 2559 } 2560 } 2561 } 2562 2563 for (int i = 0; i < nThreads * m_Statistics.channels; i++) 2564 futures[i].waitForFinished(); 2565 2566 if (calcStats) 2567 { 2568 for (int i = 0; i < 3; i++) 2569 { 2570 m_Statistics.min[i] = min[i]; 2571 m_Statistics.max[i] = max[i]; 2572 } 2573 calculateStdDev<T>(); 2574 } 2575 } 2576 break; 2577 2578 case FITS_EQUALIZE: 2579 { 2580 #ifndef KSTARS_LITE 2581 if (!isHistogramConstructed()) 2582 constructHistogram(); 2583 2584 T bufferVal = 0; 2585 2586 double coeff = 255.0 / (height * width); 2587 uint32_t row = 0; 2588 uint32_t index = 0; 2589 2590 for (int i = 0; i < m_Statistics.channels; i++) 2591 { 2592 uint32_t offset = i * m_Statistics.samples_per_channel; 2593 for (uint32_t j = 0; j < height; j++) 2594 { 2595 row = offset + j * width; 2596 for (uint32_t k = 0; k < width; k++) 2597 { 2598 index = k + row; 2599 bufferVal = (image[index] - min[i]) / m_HistogramBinWidth[i]; 2600 2601 if (bufferVal >= m_CumulativeFrequency[i].size()) 2602 bufferVal = m_CumulativeFrequency[i].size() - 1; 2603 2604 image[index] = qBound(min[i], static_cast<T>(round(coeff * m_CumulativeFrequency[i][bufferVal])), max[i]); 2605 } 2606 } 2607 } 2608 #endif 2609 } 2610 if (calcStats) 2611 calculateStats(true, false); 2612 break; 2613 2614 // Based on http://www.librow.com/articles/article-1 2615 case FITS_MEDIAN: 2616 { 2617 uint8_t BBP = m_Statistics.bytesPerPixel; 2618 auto * extension = new T[(width + 2) * (height + 2)]; 2619 // Check memory allocation 2620 if (!extension) 2621 return; 2622 // Create image extension 2623 for (uint32_t ch = 0; ch < m_Statistics.channels; ch++) 2624 { 2625 uint32_t offset = ch * m_Statistics.samples_per_channel; 2626 uint32_t N = width, M = height; 2627 2628 for (uint32_t i = 0; i < M; ++i) 2629 { 2630 memcpy(extension + (N + 2) * (i + 1) + 1, image + (N * i) + offset, N * BBP); 2631 extension[(N + 2) * (i + 1)] = image[N * i + offset]; 2632 extension[(N + 2) * (i + 2) - 1] = image[N * (i + 1) - 1 + offset]; 2633 } 2634 // Fill first line of image extension 2635 memcpy(extension, extension + N + 2, (N + 2) * BBP); 2636 // Fill last line of image extension 2637 memcpy(extension + (N + 2) * (M + 1), extension + (N + 2) * M, (N + 2) * BBP); 2638 // Call median filter implementation 2639 2640 N = width + 2; 2641 M = height + 2; 2642 2643 // Move window through all elements of the image 2644 for (uint32_t m = 1; m < M - 1; ++m) 2645 for (uint32_t n = 1; n < N - 1; ++n) 2646 { 2647 // Pick up window elements 2648 int k = 0; 2649 float window[9]; 2650 2651 memset(&window[0], 0, 9 * sizeof(float)); 2652 for (uint32_t j = m - 1; j < m + 2; ++j) 2653 for (uint32_t i = n - 1; i < n + 2; ++i) 2654 window[k++] = extension[j * N + i]; 2655 // Order elements (only half of them) 2656 for (uint32_t j = 0; j < 5; ++j) 2657 { 2658 // Find position of minimum element 2659 int mine = j; 2660 for (uint32_t l = j + 1; l < 9; ++l) 2661 if (window[l] < window[mine]) 2662 mine = l; 2663 // Put found minimum element in its place 2664 const float temp = window[j]; 2665 window[j] = window[mine]; 2666 window[mine] = temp; 2667 } 2668 // Get result - the middle element 2669 image[(m - 1) * (N - 2) + n - 1 + offset] = window[4]; 2670 } 2671 } 2672 2673 // Free memory 2674 delete[] extension; 2675 2676 if (calcStats) 2677 calculateStdDev<T>(); 2678 } 2679 break; 2680 2681 case FITS_GAUSSIAN: 2682 gaussianBlur<T>(Options::focusGaussianKernelSize(), Options::focusGaussianSigma()); 2683 if (calcStats) 2684 calculateStats(true, false); 2685 break; 2686 2687 case FITS_ROTATE_CW: 2688 rotFITS<T>(90, 0); 2689 rotCounter++; 2690 break; 2691 2692 case FITS_ROTATE_CCW: 2693 rotFITS<T>(270, 0); 2694 rotCounter--; 2695 break; 2696 2697 case FITS_MOUNT_FLIP_H: 2698 rotFITS<T>(0, 1); 2699 flipHCounter++; 2700 break; 2701 2702 case FITS_MOUNT_FLIP_V: 2703 rotFITS<T>(0, 2); 2704 flipVCounter++; 2705 break; 2706 2707 default: 2708 break; 2709 } 2710 } 2711 2712 QList<Edge *> FITSData::getStarCentersInSubFrame(QRect subFrame) const 2713 { 2714 QList<Edge *> starCentersInSubFrame; 2715 for (int i = 0; i < starCenters.count(); i++) 2716 { 2717 int x = static_cast<int>(starCenters[i]->x); 2718 int y = static_cast<int>(starCenters[i]->y); 2719 if(subFrame.contains(x, y)) 2720 { 2721 starCentersInSubFrame.append(starCenters[i]); 2722 } 2723 } 2724 return starCentersInSubFrame; 2725 } 2726 2727 bool FITSData::loadWCS() 2728 { 2729 #if !defined(KSTARS_LITE) && defined(HAVE_WCSLIB) 2730 2731 if (m_WCSState == Success) 2732 { 2733 qCWarning(KSTARS_FITS) << "WCS data already loaded"; 2734 return true; 2735 } 2736 2737 if (m_WCSHandle != nullptr) 2738 { 2739 wcsvfree(&m_nwcs, &m_WCSHandle); 2740 m_nwcs = 0; 2741 m_WCSHandle = nullptr; 2742 } 2743 2744 qCDebug(KSTARS_FITS) << "Started WCS Data Processing..."; 2745 2746 QByteArray header_str; 2747 int status = 0; 2748 int nkeyrec = 0, nreject = 0; 2749 if (fptr) 2750 { 2751 char *header = nullptr; 2752 if (fits_hdr2str(fptr, 1, nullptr, 0, &header, &nkeyrec, &status)) 2753 { 2754 char errmsg[512]; 2755 fits_get_errstatus(status, errmsg); 2756 m_LastError = errmsg; 2757 m_WCSState = Failure; 2758 return false; 2759 } 2760 header_str = QByteArray(header); 2761 fits_free_memory(header, &status); 2762 } 2763 else 2764 { 2765 nkeyrec = 1; 2766 for(auto &fitsKeyword : m_HeaderRecords) 2767 { 2768 QByteArray rec; 2769 rec.append(fitsKeyword.key.leftJustified(8, ' ').toLatin1()); 2770 rec.append("= "); 2771 rec.append(fitsKeyword.value.toByteArray()); 2772 rec.append(" / "); 2773 rec.append(fitsKeyword.comment.toLatin1()); 2774 header_str.append(rec.leftJustified(80, ' ', true)); 2775 nkeyrec++; 2776 } 2777 header_str.append(QByteArray("END").leftJustified(80)); 2778 } 2779 2780 if ((status = wcspih(header_str.data(), nkeyrec, WCSHDR_all, 0, &nreject, &m_nwcs, &m_WCSHandle)) != 0) 2781 { 2782 wcsvfree(&m_nwcs, &m_WCSHandle); 2783 m_WCSHandle = nullptr; 2784 m_nwcs = 0; 2785 m_LastError = QString("wcspih ERROR %1: %2.").arg(status).arg(wcshdr_errmsg[status]); 2786 m_WCSState = Failure; 2787 return false; 2788 } 2789 2790 if (m_WCSHandle == nullptr) 2791 { 2792 m_LastError = i18n("No world coordinate systems found."); 2793 m_WCSState = Failure; 2794 return false; 2795 } 2796 2797 // FIXME: Call above goes through EVEN if no WCS is present, so we're adding this to return for now. 2798 if (m_WCSHandle->crpix[0] == 0) 2799 { 2800 wcsvfree(&m_nwcs, &m_WCSHandle); 2801 m_WCSHandle = nullptr; 2802 m_nwcs = 0; 2803 m_LastError = i18n("No world coordinate systems found."); 2804 m_WCSState = Failure; 2805 return false; 2806 } 2807 2808 cdfix(m_WCSHandle); 2809 if ((status = wcsset(m_WCSHandle)) != 0) 2810 { 2811 wcsvfree(&m_nwcs, &m_WCSHandle); 2812 m_WCSHandle = nullptr; 2813 m_nwcs = 0; 2814 m_LastError = QString("wcsset error %1: %2.").arg(status).arg(wcs_errmsg[status]); 2815 m_WCSState = Failure; 2816 return false; 2817 } 2818 2819 m_ObjectsSearched = false; 2820 m_WCSState = Success; 2821 HasWCS = true; 2822 2823 qCDebug(KSTARS_FITS) << "Finished WCS Data processing..."; 2824 2825 return true; 2826 #else 2827 return false; 2828 #endif 2829 } 2830 2831 bool FITSData::wcsToPixel(const SkyPoint &wcsCoord, QPointF &wcsPixelPoint, QPointF &wcsImagePoint) 2832 { 2833 #if !defined(KSTARS_LITE) && defined(HAVE_WCSLIB) 2834 int status, stat[NWCSFIX]; 2835 double imgcrd[NWCSFIX], phi, pixcrd[NWCSFIX], theta, worldcrd[NWCSFIX]; 2836 2837 if (m_WCSHandle == nullptr) 2838 { 2839 m_LastError = i18n("No world coordinate systems found."); 2840 return false; 2841 } 2842 2843 worldcrd[0] = wcsCoord.ra0().Degrees(); 2844 worldcrd[1] = wcsCoord.dec0().Degrees(); 2845 2846 if ((status = wcss2p(m_WCSHandle, 1, 2, worldcrd, &phi, &theta, imgcrd, pixcrd, stat)) != 0) 2847 { 2848 m_LastError = QString("wcss2p error %1: %2.").arg(status).arg(wcs_errmsg[status]); 2849 return false; 2850 } 2851 2852 wcsImagePoint.setX(imgcrd[0]); 2853 wcsImagePoint.setY(imgcrd[1]); 2854 2855 wcsPixelPoint.setX(pixcrd[0]); 2856 wcsPixelPoint.setY(pixcrd[1]); 2857 2858 return true; 2859 #else 2860 Q_UNUSED(wcsCoord); 2861 Q_UNUSED(wcsPixelPoint); 2862 Q_UNUSED(wcsImagePoint); 2863 return false; 2864 #endif 2865 } 2866 2867 bool FITSData::pixelToWCS(const QPointF &wcsPixelPoint, SkyPoint &wcsCoord) 2868 { 2869 #if !defined(KSTARS_LITE) && defined(HAVE_WCSLIB) 2870 int status, stat[NWCSFIX]; 2871 double imgcrd[NWCSFIX], phi, pixcrd[NWCSFIX], theta, world[NWCSFIX]; 2872 2873 if (m_WCSHandle == nullptr) 2874 { 2875 m_LastError = i18n("No world coordinate systems found."); 2876 return false; 2877 } 2878 2879 pixcrd[0] = wcsPixelPoint.x(); 2880 pixcrd[1] = wcsPixelPoint.y(); 2881 2882 if ((status = wcsp2s(m_WCSHandle, 1, 2, pixcrd, imgcrd, &phi, &theta, world, stat)) != 0) 2883 { 2884 m_LastError = QString("wcsp2s error %1: %2.").arg(status).arg(wcs_errmsg[status]); 2885 return false; 2886 } 2887 else 2888 { 2889 wcsCoord.setRA0(world[0] / 15.0); 2890 wcsCoord.setDec0(world[1]); 2891 } 2892 2893 return true; 2894 #else 2895 Q_UNUSED(wcsPixelPoint); 2896 Q_UNUSED(wcsCoord); 2897 return false; 2898 #endif 2899 } 2900 2901 #if !defined(KSTARS_LITE) && defined(HAVE_WCSLIB) 2902 bool FITSData::searchObjects() 2903 { 2904 if (m_ObjectsSearched) 2905 return true; 2906 2907 m_ObjectsSearched = true; 2908 2909 SkyPoint startPoint; 2910 SkyPoint endPoint; 2911 2912 pixelToWCS(QPointF(0, 0), startPoint); 2913 pixelToWCS(QPointF(width() - 1, height() - 1), endPoint); 2914 2915 return findObjectsInImage(startPoint, endPoint); 2916 } 2917 2918 bool FITSData::findWCSBounds(double &minRA, double &maxRA, double &minDec, double &maxDec) 2919 { 2920 if (m_WCSHandle == nullptr) 2921 { 2922 m_LastError = i18n("No world coordinate systems found."); 2923 return false; 2924 } 2925 2926 maxRA = -1000; 2927 minRA = 1000; 2928 maxDec = -1000; 2929 minDec = 1000; 2930 2931 auto updateMinMax = [&](int x, int y) 2932 { 2933 int stat[2]; 2934 double imgcrd[2], phi, pixcrd[2], theta, world[2]; 2935 2936 pixcrd[0] = x; 2937 pixcrd[1] = y; 2938 2939 if (wcsp2s(m_WCSHandle, 1, 2, &pixcrd[0], &imgcrd[0], &phi, &theta, &world[0], &stat[0])) 2940 return; 2941 2942 minRA = std::min(minRA, world[0]); 2943 maxRA = std::max(maxRA, world[0]); 2944 minDec = std::min(minDec, world[1]); 2945 maxDec = std::max(maxDec, world[1]); 2946 }; 2947 2948 // Find min and max values from edges 2949 for (int y = 0; y < height(); y++) 2950 { 2951 updateMinMax(0, y); 2952 updateMinMax(width() - 1, y); 2953 } 2954 2955 for (int x = 1; x < width() - 1; x++) 2956 { 2957 updateMinMax(x, 0); 2958 updateMinMax(x, height() - 1); 2959 } 2960 2961 // Check if either pole is in the image 2962 SkyPoint NCP(0, 90); 2963 SkyPoint SCP(0, -90); 2964 QPointF pixelPoint, imagePoint, pPoint; 2965 if (wcsToPixel(NCP, pPoint, imagePoint)) 2966 { 2967 if (pPoint.x() > 0 && pPoint.x() < width() && pPoint.y() > 0 && pPoint.y() < height()) 2968 { 2969 maxDec = 90; 2970 } 2971 } 2972 if (wcsToPixel(SCP, pPoint, imagePoint)) 2973 { 2974 if (pPoint.x() > 0 && pPoint.x() < width() && pPoint.y() > 0 && pPoint.y() < height()) 2975 { 2976 minDec = -90; 2977 } 2978 } 2979 return true; 2980 } 2981 #endif 2982 2983 #if !defined(KSTARS_LITE) && defined(HAVE_WCSLIB) 2984 bool FITSData::findObjectsInImage(SkyPoint startPoint, SkyPoint endPoint) 2985 { 2986 if (KStarsData::Instance() == nullptr) 2987 return false; 2988 2989 int w = width(); 2990 int h = height(); 2991 QVariant date; 2992 KSNumbers * num = nullptr; 2993 2994 if (getRecordValue("DATE-OBS", date)) 2995 { 2996 QString tsString(date.toString()); 2997 tsString = tsString.remove('\'').trimmed(); 2998 // Add Zulu time to indicate UTC 2999 tsString += "Z"; 3000 3001 QDateTime ts = QDateTime::fromString(tsString, Qt::ISODate); 3002 3003 if (ts.isValid()) 3004 num = new KSNumbers(KStarsDateTime(ts).djd()); 3005 } 3006 3007 //Set to current time if the above does not work. 3008 if (num == nullptr) 3009 num = new KSNumbers(KStarsData::Instance()->ut().djd()); 3010 3011 startPoint.updateCoordsNow(num); 3012 endPoint.updateCoordsNow(num); 3013 3014 m_SkyObjects.clear(); 3015 3016 QList<SkyObject *> list = KStarsData::Instance()->skyComposite()->findObjectsInArea(startPoint, endPoint); 3017 list.erase(std::remove_if(list.begin(), list.end(), [](SkyObject * oneObject) 3018 { 3019 int type = oneObject->type(); 3020 return (type == SkyObject::STAR || type == SkyObject::PLANET || type == SkyObject::ASTEROID || 3021 type == SkyObject::COMET || type == SkyObject::SUPERNOVA || type == SkyObject::MOON || 3022 type == SkyObject::SATELLITE); 3023 }), list.end()); 3024 3025 double world[2], phi, theta, imgcrd[2], pixcrd[2]; 3026 int stat[2]; 3027 for (auto &object : list) 3028 { 3029 world[0] = object->ra0().Degrees(); 3030 world[1] = object->dec0().Degrees(); 3031 3032 if (wcss2p(m_WCSHandle, 1, 2, &world[0], &phi, &theta, &imgcrd[0], &pixcrd[0], &stat[0]) == 0) 3033 { 3034 //The X and Y are set to the found position if it does work. 3035 int x = pixcrd[0]; 3036 int y = pixcrd[1]; 3037 if (x > 0 && y > 0 && x < w && y < h) 3038 m_SkyObjects.append(new FITSSkyObject(object, x, y)); 3039 } 3040 } 3041 3042 delete (num); 3043 return true; 3044 } 3045 #endif 3046 3047 int FITSData::getFlipVCounter() const 3048 { 3049 return flipVCounter; 3050 } 3051 3052 void FITSData::setFlipVCounter(int value) 3053 { 3054 flipVCounter = value; 3055 } 3056 3057 int FITSData::getFlipHCounter() const 3058 { 3059 return flipHCounter; 3060 } 3061 3062 void FITSData::setFlipHCounter(int value) 3063 { 3064 flipHCounter = value; 3065 } 3066 3067 int FITSData::getRotCounter() const 3068 { 3069 return rotCounter; 3070 } 3071 3072 void FITSData::setRotCounter(int value) 3073 { 3074 rotCounter = value; 3075 } 3076 3077 /* Rotate an image by 90, 180, or 270 degrees, with an optional 3078 * reflection across the vertical or horizontal axis. 3079 * verbose generates extra info on stdout. 3080 * return nullptr if successful or rotated image. 3081 */ 3082 template <typename T> 3083 bool FITSData::rotFITS(int rotate, int mirror) 3084 { 3085 int ny, nx; 3086 int x1, y1, x2, y2; 3087 uint8_t * rotimage = nullptr; 3088 int offset = 0; 3089 3090 if (rotate == 1) 3091 rotate = 90; 3092 else if (rotate == 2) 3093 rotate = 180; 3094 else if (rotate == 3) 3095 rotate = 270; 3096 else if (rotate < 0) 3097 rotate = rotate + 360; 3098 3099 nx = m_Statistics.width; 3100 ny = m_Statistics.height; 3101 3102 int BBP = m_Statistics.bytesPerPixel; 3103 3104 /* Allocate buffer for rotated image */ 3105 rotimage = new uint8_t[m_Statistics.samples_per_channel * m_Statistics.channels * BBP]; 3106 3107 if (rotimage == nullptr) 3108 { 3109 qWarning() << "Unable to allocate memory for rotated image buffer!"; 3110 return false; 3111 } 3112 3113 auto * rotBuffer = reinterpret_cast<T *>(rotimage); 3114 auto * buffer = reinterpret_cast<T *>(m_ImageBuffer); 3115 3116 /* Mirror image without rotation */ 3117 if (rotate < 45 && rotate > -45) 3118 { 3119 if (mirror == 1) 3120 { 3121 for (int i = 0; i < m_Statistics.channels; i++) 3122 { 3123 offset = m_Statistics.samples_per_channel * i; 3124 for (x1 = 0; x1 < nx; x1++) 3125 { 3126 x2 = nx - x1 - 1; 3127 for (y1 = 0; y1 < ny; y1++) 3128 rotBuffer[(y1 * nx) + x2 + offset] = buffer[(y1 * nx) + x1 + offset]; 3129 } 3130 } 3131 } 3132 else if (mirror == 2) 3133 { 3134 for (int i = 0; i < m_Statistics.channels; i++) 3135 { 3136 offset = m_Statistics.samples_per_channel * i; 3137 for (y1 = 0; y1 < ny; y1++) 3138 { 3139 y2 = ny - y1 - 1; 3140 for (x1 = 0; x1 < nx; x1++) 3141 rotBuffer[(y2 * nx) + x1 + offset] = buffer[(y1 * nx) + x1 + offset]; 3142 } 3143 } 3144 } 3145 else 3146 { 3147 for (int i = 0; i < m_Statistics.channels; i++) 3148 { 3149 offset = m_Statistics.samples_per_channel * i; 3150 for (y1 = 0; y1 < ny; y1++) 3151 { 3152 for (x1 = 0; x1 < nx; x1++) 3153 rotBuffer[(y1 * nx) + x1 + offset] = buffer[(y1 * nx) + x1 + offset]; 3154 } 3155 } 3156 } 3157 } 3158 3159 /* Rotate by 90 degrees */ 3160 else if (rotate >= 45 && rotate < 135) 3161 { 3162 if (mirror == 1) 3163 { 3164 for (int i = 0; i < m_Statistics.channels; i++) 3165 { 3166 offset = m_Statistics.samples_per_channel * i; 3167 for (y1 = 0; y1 < ny; y1++) 3168 { 3169 x2 = ny - y1 - 1; 3170 for (x1 = 0; x1 < nx; x1++) 3171 { 3172 y2 = nx - x1 - 1; 3173 rotBuffer[(y2 * ny) + x2 + offset] = buffer[(y1 * nx) + x1 + offset]; 3174 } 3175 } 3176 } 3177 } 3178 else if (mirror == 2) 3179 { 3180 for (int i = 0; i < m_Statistics.channels; i++) 3181 { 3182 offset = m_Statistics.samples_per_channel * i; 3183 for (y1 = 0; y1 < ny; y1++) 3184 { 3185 for (x1 = 0; x1 < nx; x1++) 3186 rotBuffer[(x1 * ny) + y1 + offset] = buffer[(y1 * nx) + x1 + offset]; 3187 } 3188 } 3189 } 3190 else 3191 { 3192 for (int i = 0; i < m_Statistics.channels; i++) 3193 { 3194 offset = m_Statistics.samples_per_channel * i; 3195 for (y1 = 0; y1 < ny; y1++) 3196 { 3197 x2 = ny - y1 - 1; 3198 for (x1 = 0; x1 < nx; x1++) 3199 { 3200 y2 = x1; 3201 rotBuffer[(y2 * ny) + x2 + offset] = buffer[(y1 * nx) + x1 + offset]; 3202 } 3203 } 3204 } 3205 } 3206 3207 m_Statistics.width = ny; 3208 m_Statistics.height = nx; 3209 } 3210 3211 /* Rotate by 180 degrees */ 3212 else if (rotate >= 135 && rotate < 225) 3213 { 3214 if (mirror == 1) 3215 { 3216 for (int i = 0; i < m_Statistics.channels; i++) 3217 { 3218 offset = m_Statistics.samples_per_channel * i; 3219 for (y1 = 0; y1 < ny; y1++) 3220 { 3221 y2 = ny - y1 - 1; 3222 for (x1 = 0; x1 < nx; x1++) 3223 rotBuffer[(y2 * nx) + x1 + offset] = buffer[(y1 * nx) + x1 + offset]; 3224 } 3225 } 3226 } 3227 else if (mirror == 2) 3228 { 3229 for (int i = 0; i < m_Statistics.channels; i++) 3230 { 3231 offset = m_Statistics.samples_per_channel * i; 3232 for (x1 = 0; x1 < nx; x1++) 3233 { 3234 x2 = nx - x1 - 1; 3235 for (y1 = 0; y1 < ny; y1++) 3236 rotBuffer[(y1 * nx) + x2 + offset] = buffer[(y1 * nx) + x1 + offset]; 3237 } 3238 } 3239 } 3240 else 3241 { 3242 for (int i = 0; i < m_Statistics.channels; i++) 3243 { 3244 offset = m_Statistics.samples_per_channel * i; 3245 for (y1 = 0; y1 < ny; y1++) 3246 { 3247 y2 = ny - y1 - 1; 3248 for (x1 = 0; x1 < nx; x1++) 3249 { 3250 x2 = nx - x1 - 1; 3251 rotBuffer[(y2 * nx) + x2 + offset] = buffer[(y1 * nx) + x1 + offset]; 3252 } 3253 } 3254 } 3255 } 3256 } 3257 3258 /* Rotate by 270 degrees */ 3259 else if (rotate >= 225 && rotate < 315) 3260 { 3261 if (mirror == 1) 3262 { 3263 for (int i = 0; i < m_Statistics.channels; i++) 3264 { 3265 offset = m_Statistics.samples_per_channel * i; 3266 for (y1 = 0; y1 < ny; y1++) 3267 { 3268 for (x1 = 0; x1 < nx; x1++) 3269 rotBuffer[(x1 * ny) + y1 + offset] = buffer[(y1 * nx) + x1 + offset]; 3270 } 3271 } 3272 } 3273 else if (mirror == 2) 3274 { 3275 for (int i = 0; i < m_Statistics.channels; i++) 3276 { 3277 offset = m_Statistics.samples_per_channel * i; 3278 for (y1 = 0; y1 < ny; y1++) 3279 { 3280 x2 = ny - y1 - 1; 3281 for (x1 = 0; x1 < nx; x1++) 3282 { 3283 y2 = nx - x1 - 1; 3284 rotBuffer[(y2 * ny) + x2 + offset] = buffer[(y1 * nx) + x1 + offset]; 3285 } 3286 } 3287 } 3288 } 3289 else 3290 { 3291 for (int i = 0; i < m_Statistics.channels; i++) 3292 { 3293 offset = m_Statistics.samples_per_channel * i; 3294 for (y1 = 0; y1 < ny; y1++) 3295 { 3296 x2 = y1; 3297 for (x1 = 0; x1 < nx; x1++) 3298 { 3299 y2 = nx - x1 - 1; 3300 rotBuffer[(y2 * ny) + x2 + offset] = buffer[(y1 * nx) + x1 + offset]; 3301 } 3302 } 3303 } 3304 } 3305 3306 m_Statistics.width = ny; 3307 m_Statistics.height = nx; 3308 } 3309 3310 /* If rotating by more than 315 degrees, assume top-bottom reflection */ 3311 else if (rotate >= 315 && mirror) 3312 { 3313 for (int i = 0; i < m_Statistics.channels; i++) 3314 { 3315 offset = m_Statistics.samples_per_channel * i; 3316 for (y1 = 0; y1 < ny; y1++) 3317 { 3318 for (x1 = 0; x1 < nx; x1++) 3319 { 3320 x2 = y1; 3321 y2 = x1; 3322 rotBuffer[(y2 * ny) + x2 + offset] = buffer[(y1 * nx) + x1 + offset]; 3323 } 3324 } 3325 } 3326 } 3327 3328 delete[] m_ImageBuffer; 3329 m_ImageBuffer = rotimage; 3330 3331 return true; 3332 } 3333 3334 void FITSData::rotWCSFITS(int angle, int mirror) 3335 { 3336 int status = 0; 3337 char comment[100]; 3338 double ctemp1, ctemp2, ctemp3, ctemp4, naxis1, naxis2; 3339 int WCS_DECIMALS = 6; 3340 3341 naxis1 = m_Statistics.width; 3342 naxis2 = m_Statistics.height; 3343 3344 if (fits_read_key_dbl(fptr, "CD1_1", &ctemp1, comment, &status)) 3345 { 3346 // No WCS keywords 3347 return; 3348 } 3349 3350 /* Reset CROTAn and CD matrix if axes have been exchanged */ 3351 if (angle == 90) 3352 { 3353 if (!fits_read_key_dbl(fptr, "CROTA1", &ctemp1, comment, &status)) 3354 fits_update_key_dbl(fptr, "CROTA1", ctemp1 + 90.0, WCS_DECIMALS, comment, &status); 3355 3356 if (!fits_read_key_dbl(fptr, "CROTA2", &ctemp1, comment, &status)) 3357 fits_update_key_dbl(fptr, "CROTA2", ctemp1 + 90.0, WCS_DECIMALS, comment, &status); 3358 } 3359 3360 status = 0; 3361 3362 /* Negate rotation angle if mirrored */ 3363 if (mirror != 0) 3364 { 3365 if (!fits_read_key_dbl(fptr, "CROTA1", &ctemp1, comment, &status)) 3366 fits_update_key_dbl(fptr, "CROTA1", -ctemp1, WCS_DECIMALS, comment, &status); 3367 3368 if (!fits_read_key_dbl(fptr, "CROTA2", &ctemp1, comment, &status)) 3369 fits_update_key_dbl(fptr, "CROTA2", -ctemp1, WCS_DECIMALS, comment, &status); 3370 3371 status = 0; 3372 3373 if (!fits_read_key_dbl(fptr, "LTM1_1", &ctemp1, comment, &status)) 3374 fits_update_key_dbl(fptr, "LTM1_1", -ctemp1, WCS_DECIMALS, comment, &status); 3375 3376 status = 0; 3377 3378 if (!fits_read_key_dbl(fptr, "CD1_1", &ctemp1, comment, &status)) 3379 fits_update_key_dbl(fptr, "CD1_1", -ctemp1, WCS_DECIMALS, comment, &status); 3380 3381 if (!fits_read_key_dbl(fptr, "CD1_2", &ctemp1, comment, &status)) 3382 fits_update_key_dbl(fptr, "CD1_2", -ctemp1, WCS_DECIMALS, comment, &status); 3383 3384 if (!fits_read_key_dbl(fptr, "CD2_1", &ctemp1, comment, &status)) 3385 fits_update_key_dbl(fptr, "CD2_1", -ctemp1, WCS_DECIMALS, comment, &status); 3386 } 3387 3388 status = 0; 3389 3390 /* Unbin CRPIX and CD matrix */ 3391 if (!fits_read_key_dbl(fptr, "LTM1_1", &ctemp1, comment, &status)) 3392 { 3393 if (ctemp1 != 1.0) 3394 { 3395 if (!fits_read_key_dbl(fptr, "LTM2_2", &ctemp2, comment, &status)) 3396 if (ctemp1 == ctemp2) 3397 { 3398 double ltv1 = 0.0; 3399 double ltv2 = 0.0; 3400 status = 0; 3401 if (!fits_read_key_dbl(fptr, "LTV1", <v1, comment, &status)) 3402 fits_delete_key(fptr, "LTV1", &status); 3403 if (!fits_read_key_dbl(fptr, "LTV2", <v2, comment, &status)) 3404 fits_delete_key(fptr, "LTV2", &status); 3405 3406 status = 0; 3407 3408 if (!fits_read_key_dbl(fptr, "CRPIX1", &ctemp3, comment, &status)) 3409 fits_update_key_dbl(fptr, "CRPIX1", (ctemp3 - ltv1) / ctemp1, WCS_DECIMALS, comment, &status); 3410 3411 if (!fits_read_key_dbl(fptr, "CRPIX2", &ctemp3, comment, &status)) 3412 fits_update_key_dbl(fptr, "CRPIX2", (ctemp3 - ltv2) / ctemp1, WCS_DECIMALS, comment, &status); 3413 3414 status = 0; 3415 3416 if (!fits_read_key_dbl(fptr, "CD1_1", &ctemp3, comment, &status)) 3417 fits_update_key_dbl(fptr, "CD1_1", ctemp3 / ctemp1, WCS_DECIMALS, comment, &status); 3418 3419 if (!fits_read_key_dbl(fptr, "CD1_2", &ctemp3, comment, &status)) 3420 fits_update_key_dbl(fptr, "CD1_2", ctemp3 / ctemp1, WCS_DECIMALS, comment, &status); 3421 3422 if (!fits_read_key_dbl(fptr, "CD2_1", &ctemp3, comment, &status)) 3423 fits_update_key_dbl(fptr, "CD2_1", ctemp3 / ctemp1, WCS_DECIMALS, comment, &status); 3424 3425 if (!fits_read_key_dbl(fptr, "CD2_2", &ctemp3, comment, &status)) 3426 fits_update_key_dbl(fptr, "CD2_2", ctemp3 / ctemp1, WCS_DECIMALS, comment, &status); 3427 3428 status = 0; 3429 3430 fits_delete_key(fptr, "LTM1_1", &status); 3431 fits_delete_key(fptr, "LTM1_2", &status); 3432 } 3433 } 3434 } 3435 3436 status = 0; 3437 3438 /* Reset CRPIXn */ 3439 if (!fits_read_key_dbl(fptr, "CRPIX1", &ctemp1, comment, &status) && 3440 !fits_read_key_dbl(fptr, "CRPIX2", &ctemp2, comment, &status)) 3441 { 3442 if (mirror != 0) 3443 { 3444 if (angle == 0) 3445 fits_update_key_dbl(fptr, "CRPIX1", naxis1 - ctemp1, WCS_DECIMALS, comment, &status); 3446 else if (angle == 90) 3447 { 3448 fits_update_key_dbl(fptr, "CRPIX1", naxis2 - ctemp2, WCS_DECIMALS, comment, &status); 3449 fits_update_key_dbl(fptr, "CRPIX2", naxis1 - ctemp1, WCS_DECIMALS, comment, &status); 3450 } 3451 else if (angle == 180) 3452 { 3453 fits_update_key_dbl(fptr, "CRPIX1", ctemp1, WCS_DECIMALS, comment, &status); 3454 fits_update_key_dbl(fptr, "CRPIX2", naxis2 - ctemp2, WCS_DECIMALS, comment, &status); 3455 } 3456 else if (angle == 270) 3457 { 3458 fits_update_key_dbl(fptr, "CRPIX1", ctemp2, WCS_DECIMALS, comment, &status); 3459 fits_update_key_dbl(fptr, "CRPIX2", ctemp1, WCS_DECIMALS, comment, &status); 3460 } 3461 } 3462 else 3463 { 3464 if (angle == 90) 3465 { 3466 fits_update_key_dbl(fptr, "CRPIX1", naxis2 - ctemp2, WCS_DECIMALS, comment, &status); 3467 fits_update_key_dbl(fptr, "CRPIX2", ctemp1, WCS_DECIMALS, comment, &status); 3468 } 3469 else if (angle == 180) 3470 { 3471 fits_update_key_dbl(fptr, "CRPIX1", naxis1 - ctemp1, WCS_DECIMALS, comment, &status); 3472 fits_update_key_dbl(fptr, "CRPIX2", naxis2 - ctemp2, WCS_DECIMALS, comment, &status); 3473 } 3474 else if (angle == 270) 3475 { 3476 fits_update_key_dbl(fptr, "CRPIX1", ctemp2, WCS_DECIMALS, comment, &status); 3477 fits_update_key_dbl(fptr, "CRPIX2", naxis1 - ctemp1, WCS_DECIMALS, comment, &status); 3478 } 3479 } 3480 } 3481 3482 status = 0; 3483 3484 /* Reset CDELTn (degrees per pixel) */ 3485 if (!fits_read_key_dbl(fptr, "CDELT1", &ctemp1, comment, &status) && 3486 !fits_read_key_dbl(fptr, "CDELT2", &ctemp2, comment, &status)) 3487 { 3488 if (mirror != 0) 3489 { 3490 if (angle == 0) 3491 fits_update_key_dbl(fptr, "CDELT1", -ctemp1, WCS_DECIMALS, comment, &status); 3492 else if (angle == 90) 3493 { 3494 fits_update_key_dbl(fptr, "CDELT1", -ctemp2, WCS_DECIMALS, comment, &status); 3495 fits_update_key_dbl(fptr, "CDELT2", -ctemp1, WCS_DECIMALS, comment, &status); 3496 } 3497 else if (angle == 180) 3498 { 3499 fits_update_key_dbl(fptr, "CDELT1", ctemp1, WCS_DECIMALS, comment, &status); 3500 fits_update_key_dbl(fptr, "CDELT2", -ctemp2, WCS_DECIMALS, comment, &status); 3501 } 3502 else if (angle == 270) 3503 { 3504 fits_update_key_dbl(fptr, "CDELT1", ctemp2, WCS_DECIMALS, comment, &status); 3505 fits_update_key_dbl(fptr, "CDELT2", ctemp1, WCS_DECIMALS, comment, &status); 3506 } 3507 } 3508 else 3509 { 3510 if (angle == 90) 3511 { 3512 fits_update_key_dbl(fptr, "CDELT1", -ctemp2, WCS_DECIMALS, comment, &status); 3513 fits_update_key_dbl(fptr, "CDELT2", ctemp1, WCS_DECIMALS, comment, &status); 3514 } 3515 else if (angle == 180) 3516 { 3517 fits_update_key_dbl(fptr, "CDELT1", -ctemp1, WCS_DECIMALS, comment, &status); 3518 fits_update_key_dbl(fptr, "CDELT2", -ctemp2, WCS_DECIMALS, comment, &status); 3519 } 3520 else if (angle == 270) 3521 { 3522 fits_update_key_dbl(fptr, "CDELT1", ctemp2, WCS_DECIMALS, comment, &status); 3523 fits_update_key_dbl(fptr, "CDELT2", -ctemp1, WCS_DECIMALS, comment, &status); 3524 } 3525 } 3526 } 3527 3528 /* Reset CD matrix, if present */ 3529 ctemp1 = 0.0; 3530 ctemp2 = 0.0; 3531 ctemp3 = 0.0; 3532 ctemp4 = 0.0; 3533 status = 0; 3534 if (!fits_read_key_dbl(fptr, "CD1_1", &ctemp1, comment, &status)) 3535 { 3536 fits_read_key_dbl(fptr, "CD1_2", &ctemp2, comment, &status); 3537 fits_read_key_dbl(fptr, "CD2_1", &ctemp3, comment, &status); 3538 fits_read_key_dbl(fptr, "CD2_2", &ctemp4, comment, &status); 3539 status = 0; 3540 if (mirror != 0) 3541 { 3542 if (angle == 0) 3543 { 3544 fits_update_key_dbl(fptr, "CD1_2", -ctemp2, WCS_DECIMALS, comment, &status); 3545 fits_update_key_dbl(fptr, "CD2_1", -ctemp3, WCS_DECIMALS, comment, &status); 3546 } 3547 else if (angle == 90) 3548 { 3549 fits_update_key_dbl(fptr, "CD1_1", -ctemp4, WCS_DECIMALS, comment, &status); 3550 fits_update_key_dbl(fptr, "CD1_2", -ctemp3, WCS_DECIMALS, comment, &status); 3551 fits_update_key_dbl(fptr, "CD2_1", -ctemp2, WCS_DECIMALS, comment, &status); 3552 fits_update_key_dbl(fptr, "CD2_2", -ctemp1, WCS_DECIMALS, comment, &status); 3553 } 3554 else if (angle == 180) 3555 { 3556 fits_update_key_dbl(fptr, "CD1_1", ctemp1, WCS_DECIMALS, comment, &status); 3557 fits_update_key_dbl(fptr, "CD1_2", ctemp2, WCS_DECIMALS, comment, &status); 3558 fits_update_key_dbl(fptr, "CD2_1", -ctemp3, WCS_DECIMALS, comment, &status); 3559 fits_update_key_dbl(fptr, "CD2_2", -ctemp4, WCS_DECIMALS, comment, &status); 3560 } 3561 else if (angle == 270) 3562 { 3563 fits_update_key_dbl(fptr, "CD1_1", ctemp4, WCS_DECIMALS, comment, &status); 3564 fits_update_key_dbl(fptr, "CD1_2", ctemp3, WCS_DECIMALS, comment, &status); 3565 fits_update_key_dbl(fptr, "CD2_1", ctemp3, WCS_DECIMALS, comment, &status); 3566 fits_update_key_dbl(fptr, "CD2_2", ctemp1, WCS_DECIMALS, comment, &status); 3567 } 3568 } 3569 else 3570 { 3571 if (angle == 90) 3572 { 3573 fits_update_key_dbl(fptr, "CD1_1", -ctemp4, WCS_DECIMALS, comment, &status); 3574 fits_update_key_dbl(fptr, "CD1_2", -ctemp3, WCS_DECIMALS, comment, &status); 3575 fits_update_key_dbl(fptr, "CD2_1", ctemp1, WCS_DECIMALS, comment, &status); 3576 fits_update_key_dbl(fptr, "CD2_2", ctemp1, WCS_DECIMALS, comment, &status); 3577 } 3578 else if (angle == 180) 3579 { 3580 fits_update_key_dbl(fptr, "CD1_1", -ctemp1, WCS_DECIMALS, comment, &status); 3581 fits_update_key_dbl(fptr, "CD1_2", -ctemp2, WCS_DECIMALS, comment, &status); 3582 fits_update_key_dbl(fptr, "CD2_1", -ctemp3, WCS_DECIMALS, comment, &status); 3583 fits_update_key_dbl(fptr, "CD2_2", -ctemp4, WCS_DECIMALS, comment, &status); 3584 } 3585 else if (angle == 270) 3586 { 3587 fits_update_key_dbl(fptr, "CD1_1", ctemp4, WCS_DECIMALS, comment, &status); 3588 fits_update_key_dbl(fptr, "CD1_2", ctemp3, WCS_DECIMALS, comment, &status); 3589 fits_update_key_dbl(fptr, "CD2_1", -ctemp2, WCS_DECIMALS, comment, &status); 3590 fits_update_key_dbl(fptr, "CD2_2", -ctemp1, WCS_DECIMALS, comment, &status); 3591 } 3592 } 3593 } 3594 3595 /* Delete any polynomial solution */ 3596 /* (These could maybe be switched, but I don't want to work them out yet */ 3597 status = 0; 3598 if (!fits_read_key_dbl(fptr, "CO1_1", &ctemp1, comment, &status)) 3599 { 3600 int i; 3601 char keyword[16]; 3602 3603 for (i = 1; i < 13; i++) 3604 { 3605 sprintf(keyword, "CO1_%d", i); 3606 fits_delete_key(fptr, keyword, &status); 3607 } 3608 for (i = 1; i < 13; i++) 3609 { 3610 sprintf(keyword, "CO2_%d", i); 3611 fits_delete_key(fptr, keyword, &status); 3612 } 3613 } 3614 3615 } 3616 3617 uint8_t * FITSData::getWritableImageBuffer() 3618 { 3619 return m_ImageBuffer; 3620 } 3621 3622 uint8_t const * FITSData::getImageBuffer() const 3623 { 3624 return m_ImageBuffer; 3625 } 3626 3627 void FITSData::setImageBuffer(uint8_t * buffer) 3628 { 3629 delete[] m_ImageBuffer; 3630 m_ImageBuffer = buffer; 3631 } 3632 3633 bool FITSData::checkDebayer() 3634 { 3635 int status = 0; 3636 char bayerPattern[64], roworder[64]; 3637 3638 // Let's search for BAYERPAT keyword, if it's not found we return as there is no bayer pattern in this image 3639 if (fits_read_keyword(fptr, "BAYERPAT", bayerPattern, nullptr, &status)) 3640 return false; 3641 3642 if (m_Statistics.dataType != TUSHORT && m_Statistics.dataType != TBYTE) 3643 { 3644 m_LastError = i18n("Only 8 and 16 bits bayered images supported."); 3645 return false; 3646 } 3647 QString pattern(bayerPattern); 3648 pattern = pattern.remove('\'').trimmed(); 3649 3650 QString order(roworder); 3651 order = order.remove('\'').trimmed(); 3652 3653 if (order == "BOTTOM-UP" && !(m_Statistics.height % 2)) 3654 { 3655 if (pattern == "RGGB") 3656 pattern = "GBRG"; 3657 else if (pattern == "GBRG") 3658 pattern = "RGGB"; 3659 else if (pattern == "GRBG") 3660 pattern = "BGGR"; 3661 else if (pattern == "BGGR") 3662 pattern = "GRBG"; 3663 else return false; 3664 } 3665 3666 if (pattern == "RGGB") 3667 debayerParams.filter = DC1394_COLOR_FILTER_RGGB; 3668 else if (pattern == "GBRG") 3669 debayerParams.filter = DC1394_COLOR_FILTER_GBRG; 3670 else if (pattern == "GRBG") 3671 debayerParams.filter = DC1394_COLOR_FILTER_GRBG; 3672 else if (pattern == "BGGR") 3673 debayerParams.filter = DC1394_COLOR_FILTER_BGGR; 3674 // We return unless we find a valid pattern 3675 else 3676 { 3677 m_LastError = i18n("Unsupported bayer pattern %1.", pattern); 3678 return false; 3679 } 3680 3681 fits_read_key(fptr, TINT, "XBAYROFF", &debayerParams.offsetX, nullptr, &status); 3682 fits_read_key(fptr, TINT, "YBAYROFF", &debayerParams.offsetY, nullptr, &status); 3683 3684 if (debayerParams.offsetX == 1) 3685 { 3686 // This may leave odd values in the 0th column if the color filter is not there 3687 // in the sensor, but otherwise should process the offset correctly. 3688 // Only offsets of 0 or 1 are implemented in debayer_8bit() and debayer_16bit(). 3689 switch (debayerParams.filter) 3690 { 3691 case DC1394_COLOR_FILTER_RGGB: 3692 debayerParams.filter = DC1394_COLOR_FILTER_GRBG; 3693 break; 3694 case DC1394_COLOR_FILTER_GBRG: 3695 debayerParams.filter = DC1394_COLOR_FILTER_BGGR; 3696 break; 3697 case DC1394_COLOR_FILTER_GRBG: 3698 debayerParams.filter = DC1394_COLOR_FILTER_RGGB; 3699 break; 3700 case DC1394_COLOR_FILTER_BGGR: 3701 debayerParams.filter = DC1394_COLOR_FILTER_GBRG; 3702 break; 3703 } 3704 debayerParams.offsetX = 0; 3705 } 3706 if (debayerParams.offsetX != 0 || debayerParams.offsetY > 1 || debayerParams.offsetY < 0) 3707 { 3708 m_LastError = i18n("Unsupported bayer offsets %1 %2.", debayerParams.offsetX, debayerParams.offsetY); 3709 return false; 3710 } 3711 3712 HasDebayer = true; 3713 3714 return true; 3715 } 3716 3717 void FITSData::getBayerParams(BayerParams * param) 3718 { 3719 param->method = debayerParams.method; 3720 param->filter = debayerParams.filter; 3721 param->offsetX = debayerParams.offsetX; 3722 param->offsetY = debayerParams.offsetY; 3723 } 3724 3725 void FITSData::setBayerParams(BayerParams * param) 3726 { 3727 debayerParams.method = param->method; 3728 debayerParams.filter = param->filter; 3729 debayerParams.offsetX = param->offsetX; 3730 debayerParams.offsetY = param->offsetY; 3731 } 3732 3733 bool FITSData::debayer(bool reload) 3734 { 3735 if (reload) 3736 { 3737 int anynull = 0, status = 0; 3738 3739 if (fits_read_img(fptr, m_Statistics.dataType, 1, m_Statistics.samples_per_channel, nullptr, m_ImageBuffer, 3740 &anynull, &status)) 3741 { 3742 // char errmsg[512]; 3743 // fits_get_errstatus(status, errmsg); 3744 // KSNotification::error(i18n("Error reading image: %1", QString(errmsg)), i18n("Debayer error")); 3745 return false; 3746 } 3747 } 3748 3749 switch (m_Statistics.dataType) 3750 { 3751 case TBYTE: 3752 return debayer_8bit(); 3753 3754 case TUSHORT: 3755 return debayer_16bit(); 3756 3757 default: 3758 return false; 3759 } 3760 } 3761 3762 bool FITSData::debayer_8bit() 3763 { 3764 dc1394error_t error_code; 3765 3766 uint32_t rgb_size = m_Statistics.samples_per_channel * 3 * m_Statistics.bytesPerPixel; 3767 uint8_t * destinationBuffer = nullptr; 3768 3769 try 3770 { 3771 destinationBuffer = new uint8_t[rgb_size]; 3772 } 3773 catch (const std::bad_alloc &e) 3774 { 3775 logOOMError(rgb_size); 3776 m_LastError = i18n("Unable to allocate memory for temporary bayer buffer: %1", e.what()); 3777 return false; 3778 } 3779 3780 auto * bayer_source_buffer = reinterpret_cast<uint8_t *>(m_ImageBuffer); 3781 auto * bayer_destination_buffer = reinterpret_cast<uint8_t *>(destinationBuffer); 3782 3783 if (bayer_destination_buffer == nullptr) 3784 { 3785 logOOMError(rgb_size); 3786 m_LastError = i18n("Unable to allocate memory for temporary bayer buffer."); 3787 return false; 3788 } 3789 3790 int ds1394_height = m_Statistics.height; 3791 auto dc1394_source = bayer_source_buffer; 3792 3793 if (debayerParams.offsetY == 1) 3794 { 3795 dc1394_source += m_Statistics.width; 3796 ds1394_height--; 3797 } 3798 // offsetX == 1 is handled in checkDebayer() and should be 0 here. 3799 3800 error_code = dc1394_bayer_decoding_8bit(dc1394_source, bayer_destination_buffer, m_Statistics.width, ds1394_height, 3801 debayerParams.filter, 3802 debayerParams.method); 3803 3804 if (error_code != DC1394_SUCCESS) 3805 { 3806 m_LastError = i18n("Debayer failed (%1)", error_code); 3807 m_Statistics.channels = 1; 3808 delete[] destinationBuffer; 3809 return false; 3810 } 3811 3812 if (m_ImageBufferSize != rgb_size) 3813 { 3814 delete[] m_ImageBuffer; 3815 try 3816 { 3817 m_ImageBuffer = new uint8_t[rgb_size]; 3818 } 3819 catch (const std::bad_alloc &e) 3820 { 3821 delete[] destinationBuffer; 3822 logOOMError(rgb_size); 3823 m_LastError = i18n("Unable to allocate memory for temporary bayer buffer: %1", e.what()); 3824 return false; 3825 } 3826 3827 m_ImageBufferSize = rgb_size; 3828 } 3829 3830 auto bayered_buffer = reinterpret_cast<uint8_t *>(m_ImageBuffer); 3831 3832 // Data in R1G1B1, we need to copy them into 3 layers for FITS 3833 3834 uint8_t * rBuff = bayered_buffer; 3835 uint8_t * gBuff = bayered_buffer + (m_Statistics.width * m_Statistics.height); 3836 uint8_t * bBuff = bayered_buffer + (m_Statistics.width * m_Statistics.height * 2); 3837 3838 int imax = m_Statistics.samples_per_channel * 3 - 3; 3839 for (int i = 0; i <= imax; i += 3) 3840 { 3841 *rBuff++ = bayer_destination_buffer[i]; 3842 *gBuff++ = bayer_destination_buffer[i + 1]; 3843 *bBuff++ = bayer_destination_buffer[i + 2]; 3844 } 3845 3846 // TODO Maybe all should be treated the same 3847 // Doing single channel saves lots of memory though for non-essential 3848 // frames 3849 m_Statistics.channels = (m_Mode == FITS_NORMAL || m_Mode == FITS_CALIBRATE) ? 3 : 1; 3850 m_Statistics.dataType = TBYTE; 3851 delete[] destinationBuffer; 3852 return true; 3853 } 3854 3855 bool FITSData::debayer_16bit() 3856 { 3857 dc1394error_t error_code; 3858 3859 uint32_t rgb_size = m_Statistics.samples_per_channel * 3 * m_Statistics.bytesPerPixel; 3860 uint8_t *destinationBuffer = nullptr; 3861 try 3862 { 3863 destinationBuffer = new uint8_t[rgb_size]; 3864 } 3865 catch (const std::bad_alloc &e) 3866 { 3867 logOOMError(rgb_size); 3868 m_LastError = i18n("Unable to allocate memory for temporary bayer buffer: %1", e.what()); 3869 return false; 3870 } 3871 3872 auto * bayer_source_buffer = reinterpret_cast<uint16_t *>(m_ImageBuffer); 3873 auto * bayer_destination_buffer = reinterpret_cast<uint16_t *>(destinationBuffer); 3874 3875 if (bayer_destination_buffer == nullptr) 3876 { 3877 logOOMError(rgb_size); 3878 m_LastError = i18n("Unable to allocate memory for temporary bayer buffer."); 3879 return false; 3880 } 3881 3882 int ds1394_height = m_Statistics.height; 3883 auto dc1394_source = bayer_source_buffer; 3884 3885 if (debayerParams.offsetY == 1) 3886 { 3887 dc1394_source += m_Statistics.width; 3888 ds1394_height--; 3889 } 3890 // offsetX == 1 is handled in checkDebayer() and should be 0 here. 3891 3892 error_code = dc1394_bayer_decoding_16bit(dc1394_source, bayer_destination_buffer, m_Statistics.width, ds1394_height, 3893 debayerParams.filter, 3894 debayerParams.method, 16); 3895 3896 if (error_code != DC1394_SUCCESS) 3897 { 3898 m_LastError = i18n("Debayer failed (%1)"); 3899 m_Statistics.channels = 1; 3900 delete[] destinationBuffer; 3901 return false; 3902 } 3903 3904 if (m_ImageBufferSize != rgb_size) 3905 { 3906 delete[] m_ImageBuffer; 3907 try 3908 { 3909 m_ImageBuffer = new uint8_t[rgb_size]; 3910 } 3911 catch (const std::bad_alloc &e) 3912 { 3913 logOOMError(rgb_size); 3914 delete[] destinationBuffer; 3915 m_LastError = i18n("Unable to allocate memory for temporary bayer buffer: %1", e.what()); 3916 return false; 3917 } 3918 3919 m_ImageBufferSize = rgb_size; 3920 } 3921 3922 auto bayered_buffer = reinterpret_cast<uint16_t *>(m_ImageBuffer); 3923 3924 // Data in R1G1B1, we need to copy them into 3 layers for FITS 3925 3926 uint16_t * rBuff = bayered_buffer; 3927 uint16_t * gBuff = bayered_buffer + (m_Statistics.width * m_Statistics.height); 3928 uint16_t * bBuff = bayered_buffer + (m_Statistics.width * m_Statistics.height * 2); 3929 3930 int imax = m_Statistics.samples_per_channel * 3 - 3; 3931 for (int i = 0; i <= imax; i += 3) 3932 { 3933 *rBuff++ = bayer_destination_buffer[i]; 3934 *gBuff++ = bayer_destination_buffer[i + 1]; 3935 *bBuff++ = bayer_destination_buffer[i + 2]; 3936 } 3937 3938 m_Statistics.channels = (m_Mode == FITS_NORMAL || m_Mode == FITS_CALIBRATE) ? 3 : 1; 3939 m_Statistics.dataType = TUSHORT; 3940 delete[] destinationBuffer; 3941 return true; 3942 } 3943 3944 void FITSData::logOOMError(uint32_t requiredMemory) 3945 { 3946 qCCritical(KSTARS_FITS) << "Debayed memory allocation failure. Required Memory:" << KFormat().formatByteSize(requiredMemory) 3947 << "Available system memory:" << KSUtils::getAvailableRAM(); 3948 } 3949 3950 double FITSData::getADU() const 3951 { 3952 double adu = 0; 3953 for (int i = 0; i < m_Statistics.channels; i++) 3954 adu += m_Statistics.mean[i]; 3955 3956 return (adu / static_cast<double>(m_Statistics.channels)); 3957 } 3958 3959 QString FITSData::getLastError() const 3960 { 3961 return m_LastError; 3962 } 3963 3964 template <typename T> 3965 void FITSData::convertToQImage(double dataMin, double dataMax, double scale, double zero, QImage &image) 3966 { 3967 const auto * buffer = reinterpret_cast<const T *>(getImageBuffer()); 3968 const T limit = std::numeric_limits<T>::max(); 3969 T bMin = dataMin < 0 ? 0 : dataMin; 3970 T bMax = dataMax > limit ? limit : dataMax; 3971 uint16_t w = width(); 3972 uint16_t h = height(); 3973 uint32_t size = w * h; 3974 double val; 3975 3976 if (channels() == 1) 3977 { 3978 /* Fill in pixel values using indexed map, linear scale */ 3979 for (int j = 0; j < h; j++) 3980 { 3981 unsigned char * scanLine = image.scanLine(j); 3982 3983 for (int i = 0; i < w; i++) 3984 { 3985 val = qBound(bMin, buffer[j * w + i], bMax); 3986 val = val * scale + zero; 3987 scanLine[i] = qBound<unsigned char>(0, static_cast<uint8_t>(val), 255); 3988 } 3989 } 3990 } 3991 else 3992 { 3993 double rval = 0, gval = 0, bval = 0; 3994 QRgb value; 3995 /* Fill in pixel values using indexed map, linear scale */ 3996 for (int j = 0; j < h; j++) 3997 { 3998 auto * scanLine = reinterpret_cast<QRgb *>((image.scanLine(j))); 3999 4000 for (int i = 0; i < w; i++) 4001 { 4002 rval = qBound(bMin, buffer[j * w + i], bMax); 4003 gval = qBound(bMin, buffer[j * w + i + size], bMax); 4004 bval = qBound(bMin, buffer[j * w + i + size * 2], bMax); 4005 4006 value = qRgb(rval * scale + zero, gval * scale + zero, bval * scale + zero); 4007 4008 scanLine[i] = value; 4009 } 4010 } 4011 } 4012 } 4013 4014 QImage FITSData::FITSToImage(const QString &filename) 4015 { 4016 QImage fitsImage; 4017 double min, max; 4018 4019 FITSData data; 4020 4021 QFuture<bool> future = data.loadFromFile(filename); 4022 4023 // Wait synchronously 4024 future.waitForFinished(); 4025 if (future.result() == false) 4026 return fitsImage; 4027 4028 data.getMinMax(&min, &max); 4029 4030 if (min == max) 4031 { 4032 fitsImage.fill(Qt::white); 4033 return fitsImage; 4034 } 4035 4036 if (data.channels() == 1) 4037 { 4038 fitsImage = QImage(data.width(), data.height(), QImage::Format_Indexed8); 4039 4040 fitsImage.setColorCount(256); 4041 for (int i = 0; i < 256; i++) 4042 fitsImage.setColor(i, qRgb(i, i, i)); 4043 } 4044 else 4045 { 4046 fitsImage = QImage(data.width(), data.height(), QImage::Format_RGB32); 4047 } 4048 4049 double dataMin = data.m_Statistics.mean[0] - data.m_Statistics.stddev[0]; 4050 double dataMax = data.m_Statistics.mean[0] + data.m_Statistics.stddev[0] * 3; 4051 4052 double bscale = 255. / (dataMax - dataMin); 4053 double bzero = (-dataMin) * (255. / (dataMax - dataMin)); 4054 4055 // Long way to do this since we do not want to use templated functions here 4056 switch (data.m_Statistics.dataType) 4057 { 4058 case TBYTE: 4059 data.convertToQImage<uint8_t>(dataMin, dataMax, bscale, bzero, fitsImage); 4060 break; 4061 4062 case TSHORT: 4063 data.convertToQImage<int16_t>(dataMin, dataMax, bscale, bzero, fitsImage); 4064 break; 4065 4066 case TUSHORT: 4067 data.convertToQImage<uint16_t>(dataMin, dataMax, bscale, bzero, fitsImage); 4068 break; 4069 4070 case TLONG: 4071 data.convertToQImage<int32_t>(dataMin, dataMax, bscale, bzero, fitsImage); 4072 break; 4073 4074 case TULONG: 4075 data.convertToQImage<uint32_t>(dataMin, dataMax, bscale, bzero, fitsImage); 4076 break; 4077 4078 case TFLOAT: 4079 data.convertToQImage<float>(dataMin, dataMax, bscale, bzero, fitsImage); 4080 break; 4081 4082 case TLONGLONG: 4083 data.convertToQImage<int64_t>(dataMin, dataMax, bscale, bzero, fitsImage); 4084 break; 4085 4086 case TDOUBLE: 4087 data.convertToQImage<double>(dataMin, dataMax, bscale, bzero, fitsImage); 4088 break; 4089 4090 default: 4091 break; 4092 } 4093 4094 return fitsImage; 4095 } 4096 4097 bool FITSData::ImageToFITS(const QString &filename, const QString &format, QString &output) 4098 { 4099 if (QImageReader::supportedImageFormats().contains(format.toLatin1()) == false) 4100 { 4101 qCCritical(KSTARS_FITS) << "Failed to convert" << filename << "to FITS since format" << format << "is not supported in Qt"; 4102 return false; 4103 } 4104 4105 QImage input; 4106 4107 if (input.load(filename, format.toLatin1()) == false) 4108 { 4109 qCCritical(KSTARS_FITS) << "Failed to open image" << filename; 4110 return false; 4111 } 4112 4113 output = QDir(getTemporaryPath()).filePath(filename + ".fits"); 4114 4115 //This section sets up the FITS File 4116 fitsfile *fptr = nullptr; 4117 int status = 0; 4118 long fpixel = 1, naxis = input.allGray() ? 2 : 3, nelements, exposure; 4119 long naxes[3] = { input.width(), input.height(), naxis == 3 ? 3 : 1 }; 4120 char error_status[512] = {0}; 4121 4122 if (fits_create_file(&fptr, QString('!' + output).toLocal8Bit().data(), &status)) 4123 { 4124 fits_get_errstatus(status, error_status); 4125 qCCritical(KSTARS_FITS) << "Failed to create FITS file. Error:" << error_status; 4126 return false; 4127 } 4128 4129 if (fits_create_img(fptr, BYTE_IMG, naxis, naxes, &status)) 4130 { 4131 qCWarning(KSTARS_FITS) << "fits_create_img failed:" << error_status; 4132 status = 0; 4133 fits_flush_file(fptr, &status); 4134 fits_close_file(fptr, &status); 4135 return false; 4136 } 4137 4138 exposure = 1; 4139 fits_update_key(fptr, TLONG, "EXPOSURE", &exposure, "Total Exposure Time", &status); 4140 4141 // Gray image 4142 if (naxis == 2) 4143 { 4144 nelements = naxes[0] * naxes[1]; 4145 if (fits_write_img(fptr, TBYTE, fpixel, nelements, input.bits(), &status)) 4146 { 4147 fits_get_errstatus(status, error_status); 4148 qCWarning(KSTARS_FITS) << "fits_write_img GRAY failed:" << error_status; 4149 status = 0; 4150 fits_flush_file(fptr, &status); 4151 fits_close_file(fptr, &status); 4152 return false; 4153 } 4154 } 4155 // RGB image, we have to convert from ARGB format to R G B for each plane 4156 else 4157 { 4158 nelements = naxes[0] * naxes[1] * 3; 4159 4160 uint8_t *srcBuffer = input.bits(); 4161 // ARGB 4162 uint32_t srcBytes = naxes[0] * naxes[1] * 4 - 4; 4163 4164 uint8_t *rgbBuffer = new uint8_t[nelements]; 4165 if (rgbBuffer == nullptr) 4166 { 4167 qCWarning(KSTARS_FITS) << "Not enough memory for RGB buffer"; 4168 fits_flush_file(fptr, &status); 4169 fits_close_file(fptr, &status); 4170 return false; 4171 } 4172 4173 uint8_t *subR = rgbBuffer; 4174 uint8_t *subG = rgbBuffer + naxes[0] * naxes[1]; 4175 uint8_t *subB = rgbBuffer + naxes[0] * naxes[1] * 2; 4176 for (uint32_t i = 0; i < srcBytes; i += 4) 4177 { 4178 *subB++ = srcBuffer[i]; 4179 *subG++ = srcBuffer[i + 1]; 4180 *subR++ = srcBuffer[i + 2]; 4181 } 4182 4183 if (fits_write_img(fptr, TBYTE, fpixel, nelements, rgbBuffer, &status)) 4184 { 4185 fits_get_errstatus(status, error_status); 4186 qCWarning(KSTARS_FITS) << "fits_write_img RGB failed:" << error_status; 4187 status = 0; 4188 fits_flush_file(fptr, &status); 4189 fits_close_file(fptr, &status); 4190 delete [] rgbBuffer; 4191 return false; 4192 } 4193 4194 delete [] rgbBuffer; 4195 } 4196 4197 if (fits_flush_file(fptr, &status)) 4198 { 4199 fits_get_errstatus(status, error_status); 4200 qCWarning(KSTARS_FITS) << "fits_flush_file failed:" << error_status; 4201 status = 0; 4202 fits_close_file(fptr, &status); 4203 return false; 4204 } 4205 4206 if (fits_close_file(fptr, &status)) 4207 { 4208 fits_get_errstatus(status, error_status); 4209 qCWarning(KSTARS_FITS) << "fits_close_file failed:" << error_status; 4210 return false; 4211 } 4212 4213 return true; 4214 } 4215 4216 void FITSData::injectWCS(double orientation, double ra, double dec, double pixscale, bool eastToTheRight) 4217 { 4218 int status = 0; 4219 4220 if (fptr == nullptr) 4221 return; 4222 4223 fits_update_key(fptr, TDOUBLE, "OBJCTRA", &ra, "Object RA", &status); 4224 fits_update_key(fptr, TDOUBLE, "OBJCTDEC", &dec, "Object DEC", &status); 4225 4226 int epoch = 2000; 4227 4228 fits_update_key(fptr, TINT, "EQUINOX", &epoch, "Equinox", &status); 4229 4230 fits_update_key(fptr, TDOUBLE, "CRVAL1", &ra, "CRVAL1", &status); 4231 fits_update_key(fptr, TDOUBLE, "CRVAL2", &dec, "CRVAL1", &status); 4232 4233 char radecsys[8] = "FK5"; 4234 char ctype1[16] = "RA---TAN"; 4235 char ctype2[16] = "DEC--TAN"; 4236 4237 fits_update_key(fptr, TSTRING, "RADECSYS", radecsys, "RADECSYS", &status); 4238 fits_update_key(fptr, TSTRING, "CTYPE1", ctype1, "CTYPE1", &status); 4239 fits_update_key(fptr, TSTRING, "CTYPE2", ctype2, "CTYPE2", &status); 4240 4241 double crpix1 = width() / 2.0; 4242 double crpix2 = height() / 2.0; 4243 4244 fits_update_key(fptr, TDOUBLE, "CRPIX1", &crpix1, "CRPIX1", &status); 4245 fits_update_key(fptr, TDOUBLE, "CRPIX2", &crpix2, "CRPIX2", &status); 4246 4247 // Arcsecs per Pixel 4248 double secpix1 = eastToTheRight ? pixscale : -pixscale; 4249 double secpix2 = pixscale; 4250 4251 fits_update_key(fptr, TDOUBLE, "SECPIX1", &secpix1, "SECPIX1", &status); 4252 fits_update_key(fptr, TDOUBLE, "SECPIX2", &secpix2, "SECPIX2", &status); 4253 4254 double degpix1 = secpix1 / 3600.0; 4255 double degpix2 = secpix2 / 3600.0; 4256 4257 fits_update_key(fptr, TDOUBLE, "CDELT1", °pix1, "CDELT1", &status); 4258 fits_update_key(fptr, TDOUBLE, "CDELT2", °pix2, "CDELT2", &status); 4259 4260 // Rotation is CW, we need to convert it to CCW per CROTA1 definition 4261 double rotation = 360 - orientation; 4262 if (rotation > 360) 4263 rotation -= 360; 4264 4265 fits_update_key(fptr, TDOUBLE, "CROTA1", &rotation, "CROTA1", &status); 4266 fits_update_key(fptr, TDOUBLE, "CROTA2", &rotation, "CROTA2", &status); 4267 4268 m_WCSState = Idle; 4269 } 4270 4271 bool FITSData::contains(const QPointF &point) const 4272 { 4273 return (point.x() >= 0 && point.y() >= 0 && point.x() <= m_Statistics.width && point.y() <= m_Statistics.height); 4274 } 4275 4276 void FITSData::saveStatistics(FITSImage::Statistic &other) 4277 { 4278 other = m_Statistics; 4279 } 4280 4281 void FITSData::restoreStatistics(FITSImage::Statistic &other) 4282 { 4283 m_Statistics = other; 4284 4285 emit dataChanged(); 4286 } 4287 4288 void FITSData::constructHistogram() 4289 { 4290 switch (m_Statistics.dataType) 4291 { 4292 case TBYTE: 4293 constructHistogramInternal<uint8_t>(); 4294 break; 4295 4296 case TSHORT: 4297 constructHistogramInternal<int16_t>(); 4298 break; 4299 4300 case TUSHORT: 4301 constructHistogramInternal<uint16_t>(); 4302 break; 4303 4304 case TLONG: 4305 constructHistogramInternal<int32_t>(); 4306 break; 4307 4308 case TULONG: 4309 constructHistogramInternal<uint32_t>(); 4310 break; 4311 4312 case TFLOAT: 4313 constructHistogramInternal<float>(); 4314 break; 4315 4316 case TLONGLONG: 4317 constructHistogramInternal<int64_t>(); 4318 break; 4319 4320 case TDOUBLE: 4321 constructHistogramInternal<double>(); 4322 break; 4323 4324 default: 4325 break; 4326 } 4327 } 4328 4329 template <typename T> int32_t FITSData::histogramBinInternal(T value, int channel) const 4330 { 4331 return qMax(static_cast<T>(0), qMin(static_cast<T>(m_HistogramBinCount), 4332 static_cast<T>(rint((value - m_Statistics.min[channel]) / m_HistogramBinWidth[channel])))); 4333 } 4334 4335 template <typename T> int32_t FITSData::histogramBinInternal(int x, int y, int channel) const 4336 { 4337 if (!m_ImageBuffer || !isHistogramConstructed()) 4338 return 0; 4339 uint32_t samples = m_Statistics.width * m_Statistics.height; 4340 uint32_t offset = channel * samples; 4341 auto * const buffer = reinterpret_cast<T const *>(m_ImageBuffer); 4342 int index = y * m_Statistics.width + x; 4343 const T &sample = buffer[index + offset]; 4344 return histogramBinInternal(sample, channel); 4345 } 4346 4347 int32_t FITSData::histogramBin(int x, int y, int channel) const 4348 { 4349 switch (m_Statistics.dataType) 4350 { 4351 case TBYTE: 4352 return histogramBinInternal<uint8_t>(x, y, channel); 4353 break; 4354 4355 case TSHORT: 4356 return histogramBinInternal<int16_t>(x, y, channel); 4357 break; 4358 4359 case TUSHORT: 4360 return histogramBinInternal<uint16_t>(x, y, channel); 4361 break; 4362 4363 case TLONG: 4364 return histogramBinInternal<int32_t>(x, y, channel); 4365 break; 4366 4367 case TULONG: 4368 return histogramBinInternal<uint32_t>(x, y, channel); 4369 break; 4370 4371 case TFLOAT: 4372 return histogramBinInternal<float>(x, y, channel); 4373 break; 4374 4375 case TLONGLONG: 4376 return histogramBinInternal<int64_t>(x, y, channel); 4377 break; 4378 4379 case TDOUBLE: 4380 return histogramBinInternal<double>(x, y, channel); 4381 break; 4382 4383 default: 4384 return 0; 4385 break; 4386 } 4387 } 4388 4389 template <typename T> void FITSData::constructHistogramInternal() 4390 { 4391 auto * const buffer = reinterpret_cast<T const *>(m_ImageBuffer); 4392 uint32_t samples = m_Statistics.width * m_Statistics.height; 4393 const uint32_t sampleBy = samples > 500000 ? samples / 500000 : 1; 4394 4395 m_HistogramBinCount = qMax(0., qMin(m_Statistics.max[0] - m_Statistics.min[0], 256.0)); 4396 if (m_HistogramBinCount <= 0) 4397 m_HistogramBinCount = 256; 4398 4399 for (int n = 0; n < m_Statistics.channels; n++) 4400 { 4401 m_HistogramIntensity[n].fill(0, m_HistogramBinCount + 1); 4402 m_HistogramFrequency[n].fill(0, m_HistogramBinCount + 1); 4403 m_CumulativeFrequency[n].fill(0, m_HistogramBinCount + 1); 4404 // Distinguish between 0-1.0 ranges and ranges with integer values. 4405 const double minBinSize = (m_Statistics.max[n] > 1.1) ? 1.0 : .0001; 4406 m_HistogramBinWidth[n] = qMax(minBinSize, (m_Statistics.max[n] - m_Statistics.min[n]) / m_HistogramBinCount); 4407 } 4408 4409 QVector<QFuture<void>> futures; 4410 4411 for (int n = 0; n < m_Statistics.channels; n++) 4412 { 4413 futures.append(QtConcurrent::run([ = ]() 4414 { 4415 for (int i = 0; i < m_HistogramBinCount; i++) 4416 m_HistogramIntensity[n][i] = m_Statistics.min[n] + (m_HistogramBinWidth[n] * i); 4417 })); 4418 } 4419 4420 for (int n = 0; n < m_Statistics.channels; n++) 4421 { 4422 futures.append(QtConcurrent::run([ = ]() 4423 { 4424 uint32_t offset = n * samples; 4425 4426 for (uint32_t i = 0; i < samples; i += sampleBy) 4427 { 4428 int32_t id = histogramBinInternal<T>(buffer[i + offset], n); 4429 m_HistogramFrequency[n][id] += sampleBy; 4430 } 4431 })); 4432 } 4433 4434 for (QFuture<void> future : futures) 4435 future.waitForFinished(); 4436 4437 futures.clear(); 4438 4439 for (int n = 0; n < m_Statistics.channels; n++) 4440 { 4441 futures.append(QtConcurrent::run([ = ]() 4442 { 4443 uint32_t accumulator = 0; 4444 for (int i = 0; i < m_HistogramBinCount; i++) 4445 { 4446 accumulator += m_HistogramFrequency[n][i]; 4447 m_CumulativeFrequency[n].replace(i, accumulator); 4448 } 4449 })); 4450 } 4451 4452 for (QFuture<void> future : futures) 4453 future.waitForFinished(); 4454 4455 futures.clear(); 4456 4457 // Custom index to indicate the overall contrast of the image 4458 if (m_CumulativeFrequency[RED_CHANNEL][m_HistogramBinCount / 4] > 0) 4459 m_JMIndex = m_CumulativeFrequency[RED_CHANNEL][m_HistogramBinCount / 8] / static_cast<double> 4460 (m_CumulativeFrequency[RED_CHANNEL][m_HistogramBinCount / 4461 4]); 4462 else 4463 m_JMIndex = 1; 4464 4465 qCDebug(KSTARS_FITS) << "FITHistogram: JMIndex " << m_JMIndex; 4466 4467 m_HistogramConstructed = true; 4468 emit histogramReady(); 4469 } 4470 4471 void FITSData::recordLastError(int errorCode) 4472 { 4473 char fitsErrorMessage[512] = {0}; 4474 fits_get_errstatus(errorCode, fitsErrorMessage); 4475 m_LastError = fitsErrorMessage; 4476 } 4477 4478 double FITSData::getAverageMean(bool roi) const 4479 { 4480 const FITSImage::Statistic* ptr = (roi ? &m_ROIStatistics : &m_Statistics); 4481 if (ptr->channels == 1) 4482 return ptr->mean[0]; 4483 else 4484 return (ptr->mean[0] + ptr->mean[1] + ptr->mean[2]) / 3.0; 4485 } 4486 4487 double FITSData::getAverageMedian(bool roi) const 4488 { 4489 const FITSImage::Statistic* ptr = (roi ? &m_ROIStatistics : &m_Statistics); 4490 if (ptr->channels == 1) 4491 return ptr->median[0]; 4492 else 4493 return (ptr->median[0] + ptr->median[1] + ptr->median[2]) / 3.0; 4494 } 4495 4496 double FITSData::getAverageStdDev(bool roi) const 4497 { 4498 const FITSImage::Statistic* ptr = (roi ? &m_ROIStatistics : &m_Statistics); 4499 if (ptr->channels == 1) 4500 return ptr->stddev[0]; 4501 else 4502 return (ptr->stddev[0] + ptr->stddev[1] + ptr->stddev[2]) / 3.0; 4503 }