import { degToArcsec, degToRad, unitRescale } from "./utils.js";
import { header, product } from "./init.js";
import { Constants } 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 = parseFloat(header.CRPIX1);
this.crpix2 = parseFloat(header.CRPIX2);
this.crpix3 = parseFloat(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.ctype3 === 'FREQUENCY' || this.ctype3 === "FREQ-W2F")
this.ctype3 = 'FREQ';
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.line = "LINE" in header ? header.LINE : undefined;
this.pc1_1 = 'PC1_1' in header ? header.PC1_1 : undefined;
this.pc2_1 = 'PC2_1' in header ? header.PC2_1 : undefined;
this.velolsr = "VELO-LSR" in header ? header["VELO-LSR"] : 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();
}
/*
* 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;
}
}
getCentralPixelPosition(){
return [Math.round(this.naxis1 / 2), Math.round(this.naxis2 / 2)];
}
/**
*
* @returns velolsr in m/s
*/
getVeloLsr(){
return this.velolsr; //* unitRescale("m/s");
}
/**
* 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 = Constants.SPEED_OF_LIGHT / this.restfreq;
} else {
lambda = Constants.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 the dimension of the fits file
* @returns {int}
*/
dimensionality() {
let result;
if (this.naxis >= 3 && this.naxis1 > 1 && this.naxis2 > 1 && this.naxis3 >= 1) {
result = 3;
} else if (this.naxis == 2 && this.naxis1 > 1 && this.naxis2 > 1) {
result = 2;
}
return result;
}
/**
* Returns number of kelvin per jansky ( inverse value of jansky per kelvins)
* @returns {float}
*/
kelvinPerJansky() {
let jperk = this.janskyPerKelvin();
let result = 1.0 / 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)
* @param {boolean} withFrequency if true, frequency is displayed
* @returns {string} HTML formatted text
*/
getFitsSummary(withFrequency) {
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.cdelt3 !== undefined && this.cunit3 !== undefined){
let value = (this.cdelt3*unitRescale(this.cunit3)).toFixed(3);
let unit = "";
if(this.cunit3 === "KM/S"){
unit = this.cunit3;
} else if(this.cunit3 === "M/S"){
unit = "km/s";
} else if(this.cunit3 === "HZ"){
unit = "MHz";
}else if(this.cunit3 === "MHZ"){
unit = "MHz";
}
FITSSummary += `- CDELT3 = <strong>${value} ${unit}</strong>`;
}
if (this.line !== undefined) {
FITSSummary += `- LINE = <strong>${this.line} GHz</strong>`;
}
if (withFrequency && this.restfreq !== undefined) {
FITSSummary += `- RESTFRQ = <strong>${(this.restfreq / 1e9).toFixed(3)} GHz</strong>`;
}
return FITSSummary;
}
getCdelt3prim() {
let result = 0.0;
if ((this.isGILDAS()) && (this.ctype3 === "VRAD")) {
result = Math.abs(this.cdelt3) * Constants.UNIT_FACTOR[this.cunit3] / Constants.UNIT_FACTOR[Constants.DEFAULT_OUTPUT_UNIT[this.ctype3]];
} else if (this.isCASA()) {
if (this.ctype3 === "FREQ") {
//ALTRVAL
result = Constants.SPEED_OF_LIGHT / 1000.0 * Math.abs(this.cdelt3) / this.crval3;
} else {
result = Math.abs(this.cdelt3) * Constants.UNIT_FACTOR[this.cunit3] / Constants.UNIT_FACTOR[Constants.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 (Constants.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 instrument is MEERKAT
* @returns {boolean}
*/
isMEERKAT() {
if (this.instrume.toUpperCase() === "MEERKAT")
return true;
return false;
}
/**
* Returns true if instrument is MIRI
* @returns {boolean}
*/
isMIRI() {
if (this.instrume.toUpperCase() === "MIRI")
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;
}
isNENUFAR(){
if (this.instrume.toUpperCase() === "NENUFAR")
return true;
return false;
}
}
let FITS_HEADER = new FitsHeader(header, product);
export { FITS_HEADER };