import { degToArcsec, degToRad } from "./utils.js";
import { header, product } from "./init.js";
import { SPEED_OF_LIGHT, UNIT_FACTOR, DEFAULT_OUTPUT_UNIT, KELVIN_UNITS } from "./constants.js";
/**
* Class representing a FITS header.
* @typedef {Object} FitsHeader
*/
class FitsHeader {
/**
* Note : during initialization, cunit3 is set to M/S if this is a GILDAS spectrum and CTYPE3 is VRAD
*
* @param {object} header FITS file header
* @param {string} product Name of product
*/
constructor(header, product) {
this.product = product;
this.object = header["OBJECT"];
// number of axis
this.naxis = parseInt(header["NAXIS"]);
// number of points of nth axis
this.naxis1 = parseInt(header["NAXIS1"]);
this.naxis2 = parseInt(header["NAXIS2"]);
this.naxis3 = parseInt(header["NAXIS3"]);
this.naxis4 = "NAXIS4" in header ? parseInt(header["NAXIS4"]) : undefined;
// array location of the reference point in pixels of nth axis
this.crpix1 = parseInt(header["CRPIX1"]);
this.crpix2 = parseInt(header["CRPIX2"]);
this.crpix3 = parseInt(header["CRPIX3"]);
this.crpix = [parseFloat(header["CRPIX1"]), parseFloat(header["CRPIX2"])];
// coordinate value at reference point of nth axis
this.crval1 = parseFloat(header["CRVAL1"]);
this.crval2 = parseFloat(header["CRVAL2"]);
this.crval3 = parseFloat(header["CRVAL3"]);
// name of nth axis
this.ctype1 = "CTYPE1" in header ? header["CTYPE1"].toUpperCase().trim() : undefined;
this.ctype2 = "CTYPE2" in header ? header["CTYPE2"].toUpperCase().trim() : undefined;
this.ctype3 = "CTYPE3" in header ? header["CTYPE3"].toUpperCase().trim() : undefined;
if(this.ctype1 !== undefined){
this.projectionType = this.ctype1.slice(-3);
console.log(this.projectionType);
}
else{
this.projectionType = undefined;
}
this.cunit1 = "CUNIT1" in header ? header["CUNIT1"].toUpperCase().trim() : undefined;
this.cunit2 = "CUNIT2" in header ? header["CUNIT2"].toUpperCase().trim() : undefined;
// coordinate increment at reference point in degrees on axis 1/2/3
this.cdelt1 = "CDELT1" in header ? parseFloat(header["CDELT1"]) : undefined;
this.cdelt2 = "CDELT2" in header ? parseFloat(header["CDELT2"]) : undefined;
this.cdelt3 = "CDELT3" in header ? parseFloat(header["CDELT3"]) : undefined;
// Image units (K, Jy/beam, etc)
this.bunit = header["BUNIT"];
this.instrume = "INSTRUME" in header ? header["INSTRUME"] : undefined;
this.origin = "ORIGIN" in header ? header["ORIGIN"] : undefined;
this.specsys = "SPECSYS" in header ? header["SPECSYS"] : undefined;
// Major axis of the clean beam
this.bmaj = "BMAJ" in header ? parseFloat(header["BMAJ"]) : undefined;
// Minor axis of the clean beam
this.bmin = "BMIN" in header ? parseFloat(header["BMIN"]) : undefined;
// Position angle of the clean beam
this.bpa = "BPA" in header ? parseFloat(header["BPA"]) : undefined;
this.restfreq = "RESTFRQ" in header ? header["RESTFRQ"] : undefined;
this.pc1_1 = 'PC1_1' in header ? header["PC1_1"] : undefined;
this.pc2_1 = 'PC2_1' in header ? header["PC2_1"] : undefined;
// Rotation angle
this.crota2 = "CROTA2" in header ? parseFloat(header["CROTA2"]) : undefined;
if ((this.isGILDAS()) && (this.ctype3 === "VRAD")) {
this.cunit3 = "M/S";
} else {
this.cunit3 = "CUNIT3" in header ? header["CUNIT3"].toUpperCase().trim() : undefined;
}
this.cdelt3prim = this.getCdelt3prim();
this.width = this.naxis1;
this.height = this.naxis2;
this._setSquaredDimensions();
console.log(this);
}
/*
* Set the same values for width and height
*/
_setSquaredDimensions() {
if (this.width > this.height) {
this.height = this.width;
} else if (this.width < this.height) {
this.width = this.height;
}
}
/**
* Returns number of jansky per kelvins from bmin, bmaj, restfreq and crval3 values
* @returns {float}
*/
janskyPerKelvin() {
let kb = 1.380649e-23;
let bmin = degToRad(this.bmin);
let bmaj = degToRad(this.bmaj);
let lambda = null;
if (this.isGILDAS()) {
lambda = SPEED_OF_LIGHT / this.restfreq;
} else {
lambda = SPEED_OF_LIGHT / this.crval3;
}
let omega = Math.PI * bmin * bmaj / 4 / Math.log(2);
let step1 = 2 * kb * omega / (lambda * lambda);
let result = step1 / 1e-26;
return result;
}
/**
* Returns number of kelvin per jansky ( inverse value of jansky per kelvins)
* @returns {float}
*/
kelvinPerJansky() {
let jperk = this.janskyPerKelvin();
let result = 1. / jperk;
return result;
}
/**
* Returns a title to be displayed above the spectrum
* @returns {string} HTML formatted text
*/
getSpectrumTitle() {
let title = "<span>" +
Number(degToArcsec(this.bmin)).toFixed(2) + "' x " + "</span>" +
"<span>" + Number(degToArcsec(this.bmaj)).toFixed(2) + "', " + "</span>" +
"<span>" + "PA " + Number(this.bpa).toFixed(1) + "°" + "</span>";
if (!this.isSpectrumInK()) {
title = title + "<span>, " + Number(this.janskyPerKelvin()).toExponential(2) + " Jy/K</span>";
}
return title;
}
/**
* Returns a velocity centered to 0 if ctype3 is frequence or radial velocity
* or else returns crval3
* @returns {float}
*/
getVCenter() {
let vcenter = this.crval3;
if (this.ctype3 === "FREQ" || this.ctype3 === "VRAD") {
vcenter = 0;
}
return vcenter;
}
/**
* Returns a summary of opened fits file (product name and list of axis)
* @returns {string} HTML formatted text
*/
getFitsSummary() {
let FITSSummary = '<strong>' + this.product + '</strong> - OBJECT = <strong>' +
this.object + '</strong> - NAXIS = <strong>' +
this.naxis + '</strong> - NAXIS1 = <strong>' +
this.naxis1 + '</strong> - NAXIS2 = <strong>' +
this.naxis2 + '</strong> - NAXIS3 = <strong>' +
this.naxis3 + '</strong>';
if (this.naxis4 !== undefined) {
FITSSummary += ' - NAXIS4 = <strong>' + this.axis4 + '</strong>';
}
return FITSSummary;
}
getCdelt3prim() {
let result = 0.;
if ((this.isGILDAS()) && (this.ctype3 === "VRAD")) {
result = Math.abs(this.cdelt3) * UNIT_FACTOR[this.cunit3] / UNIT_FACTOR[DEFAULT_OUTPUT_UNIT[this.ctype3]];
} else if (this.isCASA()) {
if (this.ctype3 === "FREQ") {
result = SPEED_OF_LIGHT / 1000. * Math.abs(this.cdelt3) / this.restfreq;
} else {
result = Math.abs(this.cdelt3) * UNIT_FACTOR[this.cunit3] / UNIT_FACTOR[DEFAULT_OUTPUT_UNIT[this.ctype3]];
}
} else if (this.isSITELLE()) {
if (this.cdelt1 && this.cdelt2) {
result = this.cdelt3 / Math.abs(this.cdelt1 * this.cdelt2 * 3600.0 * 3600.0);
} else {
result = this.cdelt3;
}
} else if (this.isMUSE()) {
result = 1;
}
return result;
}
/**
* Returns true if spectrum is in Kelvin
* @returns {boolean}
*/
isSpectrumInK() {
if (KELVIN_UNITS.has(this.bunit) || this.bunit.startsWith("K"))
return true;
/*else {
if (this.bunit.startsWith("K")) {
let error = "bunit is probably Kelvin but not recognized as such.";
alert(error);
throw new Error(error);
}
}*/
return false;
}
/**
* Returns true if field instrume is SITELLE
* @returns {boolean}
*/
isSITELLE() {
if (this.instrume === "SITELLE")
return true;
return false;
}
/**
* Returns true if field instrume is MUSE
* @returns {boolean}
*/
isMUSE() {
if (this.instrume === "MUSE")
return true;
return false;
}
/**
* Returns true if field origin starts with CASA
* @returns {boolean}
*/
isCASA() {
if (this.origin.startsWith("CASA"))
return true;
return false;
}
/**
* Returns true if field origin starts with GILDAS
* @returns {boolean}
*/
isGILDAS() {
//this is the general method to check that a file comes from GILDAS
if (this.origin.startsWith("GILDAS"))
return true;
return false;
}
/**
* Returns true if field origin starts with "Miriad fits"
* @returns {boolean}
*/
isMIRIAD() {
//this is the general method to check that a file comes from Miriad
if (this.origin.startsWith("Miriad fits"))
return true;
return false;
}
}
let FITS_HEADER = new FitsHeader(header, product);
export { FITS_HEADER }