#include "imageinfodata.h" #include #include #include #include #include #include "database.h" #include "libxisf.h" static const QVector noEditableKey = {"SIMPLE", "BITPIX", "NAXIS", "NAXIS1", "NAXIS2", "NAXIS3", "EXTEND", "BZERO", "BSCALE"}; bool FITSRecord::editable() const { return noEditableKey.count(key); } FITSRecord::FITSRecord(const QByteArray &key, const QVariant &value, const QByteArray &comment) : key(key), value(value), comment(comment) { } FITSRecord::FITSRecord(const LibXISF::FITSKeyword &record) { key = record.name.c_str(); comment = record.comment.c_str(); QString string = record.value.c_str(); if(string.startsWith('\'') && string.endsWith('\'')) { string.chop(1); string.remove(0, 1); } bool isint; bool isdouble; double vald = string.toDouble(&isdouble); long long vall = string.toLongLong(&isint); if(isint) value = vall; else if(isdouble) value = vald; else if(string == "T" || string == "F") value = string == "T"; else value = string; } FITSRecord::FITSRecord(const LibXISF::Property &property) { key = property.id.c_str(); value = QString::fromStdString(property.value.toString()); comment = property.comment.c_str(); xisf = true; } void WCSDataT::freeWCS() { wcsvfree(&nwcs, &wcs); nwcs = 0; wcs = nullptr; } WCSDataT::WCSDataT(int width, int height, char *header, int nrec) : width(width), height(height) { int nreject = 0; int status = wcspih(header, nrec, 1, 0, &nreject, &nwcs, &wcs); if(status != 0) { freeWCS(); return; } status = cdfix(wcs); if(status > 0 || wcs->crpix[0] == 0) freeWCS(); } WCSDataT::WCSDataT(int width, int height, const QVector &header) : width(width), height(height) { int status = 0; QByteArray str; int nrec = 1; for(const FITSRecord &record : header) { if(record.key.startsWith("PV"))continue; if(record.xisf)continue; QByteArray rec; rec.append(record.key.leftJustified(8, ' ')); rec.append("= "); rec.append(record.value.toString().toLatin1()); rec.append(" / "); rec.append(record.comment); str.append(rec.leftJustified(80, ' ', true)); nrec++; } str.append(QByteArray("END").leftJustified(80)); int nreject = 0; status = wcspih(str.data(), nrec, 1, 0, &nreject, &nwcs, &wcs); if(status != 0) { freeWCS(); return; } status = cdfix(wcs); if(status > 0 || wcs->crpix[0] == 0) freeWCS(); } WCSDataT::~WCSDataT() { if(wcs) freeWCS(); } bool WCSDataT::pixelToWorld(const QPointF &pixel, SkyPoint &point) const { if(!valid())return false; double pixcrd[2] = {pixel.x(), pixel.y()}; double imgcrd[8] = {0}; double phi = 0; double theta = 0; double world[8] = {0}; int stat[NWCSFIX] = {0}; int status = wcsp2s(wcs, 1, 2, pixcrd, imgcrd, &phi, &theta, world, stat); if(status == 0) { point = SkyPoint(world[0], world[1]); return true; } return false; } bool WCSDataT::worldToPixel(const SkyPoint &point, QPointF &pixel) const { if(!valid())return false; double world[2] = {point.RA(), point.DEC()}; double phi = 0; double theta = 0; double imgcrd[8] = {0}; double pixcrd[8] = {0}; int stat[NWCSFIX] = {0}; int status = wcss2p(wcs, 1, 2, world, &phi, &theta, imgcrd, pixcrd, stat); if(status == 0) { pixel = QPointF(pixcrd[0], pixcrd[1]); return true; } return false; } bool WCSDataT::calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const { if(wcs == nullptr)return false; minRa = 1000; maxRa = -1000; minDec = 1000; maxDec = -1000; if(wcs->crval) { crVal1 = wcs->crval[0]; crVal2 = wcs->crval[1]; } else { crVal1 = crVal2 = NAN; } auto update = [&](const QPointF &pixel) { SkyPoint point; pixelToWorld(pixel, point); minRa = std::min(minRa, point.RA()); maxRa = std::max(maxRa, point.RA()); minDec = std::min(minDec, point.DEC()); maxDec = std::max(maxDec, point.DEC()); }; for(int x=0; xra = ra; this->dec = dec; } QString SkyPoint::toString() const { if(std::isnan(ra) || std::isnan(dec)) return QString(); QTime t(0, 0); t = t.addSecs(ra * 240); double deg, min, sec; min = std::abs(std::modf(dec, °) * 60); sec = std::modf(min, &min) * 60; return QString("RA: %1 DEC: %2° %3' %4\"").arg(t.toString("HH'h' mm'm' ss's'")).arg(deg, 2, 'f', 0, '0').arg(min, 2, 'f', 0, '0').arg(sec, 2, 'f', 0, '0'); } QString SkyPoint::RAString() const { return toHMS(ra / 15); } QString SkyPoint::DECString() const { return toDMS(dec); } double SkyPoint::fromHMS(const QString &hms) { double deg = fromDMS(hms); if(std::isnan(deg))return deg; return deg * 15.0; } double SkyPoint::fromDMS(const QString &dms) { double deg = 0.0; QString str = dms.trimmed(); str.remove(QRegularExpression("[hdms°'\"]")); str.replace(':', ' '); str.replace(QRegularExpression("\\s+"), " "); QStringList fields = str.split(' '); double sign = 1.0; bool ok = false; if(fields.size() >= 1) deg = fields.at(0).toDouble(&ok); if(!ok)return NAN; if(deg < 0.0) sign = -1.0; if(fields.size() >= 2) deg += sign * fields.at(1).toDouble() / 60.0; if(fields.size() >= 3) deg += sign * fields.at(2).toDouble() / 3600.0; return deg; } QString SkyPoint::toHMS(double decHour) { double h,m,s,md; md = std::modf(decHour, &h) * 60.0; s = std::modf(md, &m) * 60.0; return QString("%1h %2m %3s").arg((int)h, 2, 10, QChar('0')).arg((int)m, 2, 10, QChar('0')).arg((int)s, 2, 10, QChar('0')); } QString SkyPoint::toDMS(double deg) { int sign = deg < 0.0 ? -1 : 1; deg *= sign; double d,m,s,md; md = std::modf(deg, &d) * 60.0; s = std::modf(md, &m) * 60.0; return QString("%1˚ %2' %3\"").arg((int)d * sign, 2, 10, QChar('0')).arg((int)m, 2, 10, QChar('0')).arg((int)s, 2, 10, QChar('0')); } SkyPoint SkyPoint::operator+(const SkyPoint &p) { SkyPoint ret; ret.ra = ra + p.ra; ret.dec = dec + p.dec; return ret; } SkyPointScale ImageInfoData::getCenterRaDec() const { SkyPointScale ret; if(wcs && wcs->valid()) { ret = wcs->getRaDecScale(); } else { double ra,dec,focalLen,scale,pixSizeX,pixSizeY; int binX = 1; int binY = 1; ra = dec = focalLen = scale = pixSizeX = pixSizeY = NAN; bool ok; for(const FITSRecord &header : fitsHeader) { if(header.key == "OBJCTRA") { double tmp = SkyPoint::fromHMS(header.value.toString()); if(!std::isnan(tmp))ra = tmp; } else if(header.key == "RA" && std::isnan(ra)) { double tmp = header.value.toDouble(&ok); if(ok)ra = tmp; } else if(header.key == "OBJCTDEC") { double tmp = SkyPoint::fromDMS(header.value.toString()); if(!std::isnan(tmp))dec = tmp; } else if(header.key == "DEC" && std::isnan(dec)) { double tmp = SkyPoint::fromDMS(header.value.toString()); if(!std::isnan(tmp))dec = tmp; } else if(header.key == "SCALE") { double tmp = header.value.toDouble(&ok); if(ok)scale = tmp; } else if(header.key == "FOCALLEN") { double tmp = header.value.toDouble(&ok); if(ok)focalLen = tmp; } else if(header.key == "PIXSIZE1" || header.key == "XPIXSZ") { pixSizeX = header.value.toDouble(); } else if(header.key == "PIXSIZE2" || header.key == "YPIXSZ") { pixSizeY = header.value.toDouble(); } else if(header.key == "XBINNING") { int tmp = header.value.toInt(&ok); if(ok)binX = tmp; } else if(header.key == "YBINNING") { int tmp = header.value.toInt(&ok); if(ok)binY = tmp; } } ret.point.set(ra, dec); if(!std::isnan(scale)) { ret.scaleLow = ret.scaleHigh = scale; ret.scaleValid = true; } else if(!(std::isnan(focalLen) || std::isnan(pixSizeX) || std::isnan(pixSizeY))) { const double r = 206.2648097656; // (180 * 3600) / (1000 * pi) magic number to convert pixel size to focal length ratio to arcsec. ret.scaleLow = std::min(pixSizeX * binX / focalLen * r, pixSizeY * binY / focalLen * r); ret.scaleHigh = std::max(pixSizeX * binX / focalLen * r, pixSizeY * binY / focalLen * r); ret.scaleValid = true; } } if(ret.scaleValid) { ret.scaleLow *= 0.8; ret.scaleHigh *= 1.2; } return ret; } SkyPoint greatCircle(SkyPoint &p, double dist, double azm) { dist = dist * M_PI / 180; azm = azm * M_PI / 180; double dec0 = p.DEC() * M_PI / 180; double ra0 = p.RA() * M_PI / 180; double dec1 = std::asin(std::sin(dec0) * std::cos(dist) + std::cos(dec0) * std::sin(dist) * std::cos(azm)); double ra1 = ra0 + std::atan2(std::sin(azm) * std::sin(dist) * std::cos(dec0), std::cos(dist) - std::sin(dec0) * std::sin(dec1)); return SkyPoint(ra1 * 180 / M_PI, dec1 * 180 / M_PI); } SkyGrid WCSDataT::prepareGrid(uint32_t w, uint32_t h, Database *database) { SkyGrid skyGrid; if(!wcs)return skyGrid; double minRa, maxRa, minDec, maxDec, crVal1, crVal2; calculateBounds(minRa, maxRa, minDec, maxDec, crVal1, crVal2); QPointF a,b; worldToPixel(SkyPoint(crVal1, crVal2), a); worldToPixel(SkyPoint(crVal1 + 0.01, crVal2), b); skyGrid.rot_ang = std::atan2(b.y() - a.y(), b.x() - a.x()) / M_PI * -180.0; if(database) { skyGrid.objects = database->getObjects(minRa, maxRa, minDec, maxDec); for(auto &object : skyGrid.objects) { QPointF p; if(worldToPixel(object.skyPoint, p)) object.pixel = p; QPointF majax; worldToPixel(greatCircle(object.skyPoint, (object.min_ax + object.maj_ax) / 120.0, object.pos_ang), majax); majax -= p; object.maj_ax = std::sqrt(QPointF::dotProduct(majax, majax)); } } double raStep = 15; double decStep = 15; double raRange = maxRa - minRa; double decRange = maxDec - minDec; const QVector raSteps = {15, 5, 2.5, 1.25, 0.25, 20/240.0, 10/240.0, 5/240.0, 1/240.0}; const QVector decSteps = {20, 10, 5, 2, 1, 20/60.0, 10/60.0, 5/60.0, 2/60.0, 1/60.0, 20/3600.0, 10/3600.0, 5/3600.0, 2/3600.0, 1/3600.0}; for(double ra : raSteps) { if(ra * 5 <= raRange) { raStep = ra; break; } } for(double dec : decSteps) { if(dec * 5 <= decRange) { decStep = dec; break; } } minRa -= std::fmod(minRa, raStep); minDec -= std::fmod(minDec, decStep); if(minRa < 0)minRa -= raStep; if(minDec < 0)minDec -= decStep; QRectF clip(0, 0, w, h); const double step = 0.2; maxRa += raStep; maxDec += decStep; for(double ra = minRa; ra <= maxRa; ra += raStep) { QPointF p; worldToPixel(SkyPoint(ra, minDec), p); skyGrid.grid.moveTo(p); for(double dec = minDec + decStep * step; dec <= maxDec; dec += decStep * step) { worldToPixel(SkyPoint(ra, dec), p); skyGrid.grid.lineTo(p); } } for(double dec = minDec; dec <= maxDec; dec += decStep) { QPointF p; worldToPixel(SkyPoint(minRa, dec), p); skyGrid.grid.moveTo(p); for(double ra = minRa + raStep * step; ra <= maxRa; ra += raStep * step) { worldToPixel(SkyPoint(ra, dec), p); skyGrid.grid.lineTo(p); } } SkyPoint sp1, sp2,orig; pixelToWorld(QPointF(-1, -1), orig); sp1 = orig; for(uint32_t x = 0; x < w; x++) { QPointF p(x, 0); if(!pixelToWorld(p, sp2)) break; if(static_cast(sp1.RA() / raStep) != static_cast(sp2.RA() / raStep)) skyGrid.text.append({p, std::abs(sp1.RA()) > std::abs(sp2.RA()) ? sp1.RAString() : sp2.RAString()}); if(static_cast(sp1.DEC() / decStep) != static_cast(sp2.DEC() / decStep)) skyGrid.text.append({p, std::abs(sp1.DEC()) > std::abs(sp2.DEC()) ? sp1.DECString() : sp2.DECString()}); sp1 = sp2; } sp1 = orig; for(uint32_t y = 0; y < h; y++) { QPointF p(0, y); if(!pixelToWorld(p, sp2)) break; if(static_cast(sp1.RA() / raStep) != static_cast(sp2.RA() / raStep)) skyGrid.text.append({p, std::abs(sp1.RA()) > std::abs(sp2.RA()) ? sp1.RAString() : sp2.RAString()}); if(static_cast(sp1.DEC() / decStep) != static_cast(sp2.DEC() / decStep)) skyGrid.text.append({p, std::abs(sp1.DEC()) > std::abs(sp2.DEC()) ? sp1.DECString() : sp2.DECString()}); sp1 = sp2; } skyGrid.empty = false; return skyGrid; } void SkyGrid::clear() { empty = true; grid.clear(); text.clear(); objects.clear(); }