Skip to content

Commit 4df7be2

Browse files
authored
Merge pull request #923 from Caltech-IPAC/firefly-271-Wave-TAB
firefly-271-Add WCS-TAB support to spectral FITS parser
2 parents 1ee2f1b + c422bf1 commit 4df7be2

File tree

2 files changed

+276
-37
lines changed

2 files changed

+276
-37
lines changed

src/firefly/js/visualize/projection/Wavelength.js

Lines changed: 259 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
* 3. https://www.aanda.org/articles/aa/full/2002/45/aah3859/aah3859.html,
88
*
99
*/
10-
10+
import {LinearInterpolator} from '../../util/Interp/LinearInterpolator.js';
11+
import {isDefined} from '../../util/WebUtil.js';
1112
export const PLANE = 'PLANE';
1213
export const LINEAR = 'LINEAR';
1314
export const LOG = 'LOG';
@@ -200,7 +201,31 @@ function getWaveLengthNonLinear(ipt, cubeIdx, wlData) {
200201
return lamda;
201202
}
202203

203-
const getArrayDataFromTable= (table, rowIdx, columnName) => undefined; // todo implement
204+
/**
205+
* According to the reference papers above, the coordinate has M+1 dimension where the M means the
206+
* dependence on the M axis, Coords[M][K1][K2[]...[Km]. In our case, the M=1, the coordinate is the
207+
* two dimensional array, and the first dimension is M=1 and the second dimension is K1 which is the sampling
208+
* number the data has been sampled. Thus, each image point, it corresponding coordinate is
209+
* coords[1][K1]. For example, if the sampling count is K1=100, each cell in the coordinate table, contains 100
210+
* data values. The coordinate column can convert to 100 rows of the single value table.
211+
*
212+
*
213+
* @param table
214+
* @param columnName
215+
* @returns {*}
216+
*/
217+
const getArrayDataFromTable= (table, columnName) => {
218+
let tableData = table.tableData;
219+
let arrayData = tableData.data;
220+
let columns = tableData.columns;
221+
for (let i=0; i<columns.length; i++){
222+
if (columns[i].name.toUpperCase() === columnName.toUpperCase()){
223+
return arrayData[0][i];
224+
225+
}
226+
}
227+
return undefined;
228+
};
204229

205230

206231
/**
@@ -228,48 +253,239 @@ function getWaveLengthTable(ipt, cubeIdx, wlData) {
228253

229254

230255
//read the cell coordinate from the FITS table and save to two one dimensional arrays
231-
const coordData = getArrayDataFromTable(wlTable,0, ps3_1 || 'COORDS');
256+
const coordData = getArrayDataFromTable(wlTable, ps3_1 || 'COORDS');
232257
if (!coordData) return NaN;
233258

234-
const indexData = getArrayDataFromTable(wlTable,0, ps3_2 || 'INDEX');
235-
if (!indexData) return coordData[psi_m];
259+
const indexData = getArrayDataFromTable(wlTable, ps3_2 || 'INDEX');
260+
if (!indexData) return coordData[parseInt(Math.abs(psi_m)) ];
236261

237-
const psiIndex = searchIndex(indexData, psi_m);
238-
if (isNaN(psiIndex) || psiIndex===-1) return NaN; // No index found in the index array, gamma is undefined, so is coordinate
239262

240-
const gamma_m = calculateGamma_m(indexData, psi_m, psiIndex);
241-
if (isNaN(gamma_m)) return NaN;
263+
const gamma_m = calculateGamma_m(indexData, psi_m);
264+
if (!isDefined(gamma_m)) return NaN;
265+
266+
return calculateC_m( gamma_m, coordData, indexData.length) ;
242267

243-
return coordData[psiIndex] + (gamma_m - psiIndex) * (coordData[psiIndex+1] - coordData[psiIndex]);
244268
}
245269

270+
/**
271+
* In the case of a single separable coordinate with 1 ≤ k ≤ Υm < k+1 ≤K,
272+
* the coordinate value is given by
273+
* Cm = Ck + (Υm − k) (Ck+1 − Ck)
274+
* For Υm such that 0.5 ≤ Υm < 1 or K < Υm ≤ K + 0.5 linear
275+
* extrapolation is permitted, with Cm = C1 when K = 1.
276+
*
277+
* @param gamma_m
278+
* @param coordData
279+
* @param kMax
280+
* @returns {undefined}
281+
*/
282+
function calculateC_m(gamma_m, coordData, kMax){
283+
284+
let C_m = undefined;
285+
if (kMax===1) {
286+
C_m = coordData[0];
287+
}
288+
const kIndex = Math.floor(gamma_m);
289+
if (kIndex>=0 && kIndex <= gamma_m && gamma_m < kIndex+1 && kIndex+1 <=kMax-1){
290+
C_m = coordData[kIndex] + (gamma_m - kIndex-1) * (coordData[kIndex+1]-coordData[kIndex]);
291+
}
292+
else {
293+
const inCoords = Array.from(new Array(coordData.length), (x,i) => i);
294+
const linearInterpolator = LinearInterpolator(inCoords, coordData, true);
295+
if (gamma_m>=0.5 && gamma_m<1) {
296+
297+
//linear extrapolation CoorData
298+
const coordData_far_left = linearInterpolator(0.5);
299+
C_m = coordData_far_left + gamma_m/(coordData[0]-coordData_far_left);
300+
301+
}
302+
else if( kMax <gamma_m && gamma_m <= kMax+ 0.5) {
303+
//linear extrapolation CoorData
304+
const coordData_far_right = linearInterpolator(kMax + 0.5);
305+
C_m = coordData[kMax-1] + (gamma_m-kMax) /(coordData_far_right - coordData[kMax-1]);
306+
307+
}
308+
309+
}
310+
return C_m;
311+
}
312+
313+
function getGammaByInterpolation(psiIndex, psi_m, indexVec, sort){
314+
315+
let gamma_m=undefined;
316+
/*--------------------------------------------------------------------------------
317+
* psiIndex is found, however the following four cases, gamma_m is undefined and so is C_m
318+
*/
319+
//case 1: Ψk+1=Ψk=Ψm, return undefined
320+
if (indexVec[psiIndex]===indexVec[psiIndex+1] && indexVec[psiIndex]===psi_m) {
321+
return gamma_m;
322+
}
246323

324+
/* case 2: Ψk < ψm = Ψk+1 for monotonically increasing index values,except when ψm = Ψ1,
325+
* If Ψk+2 = Ψk+1 = ψm (or Ψ2 = Ψ1 = ψm), then Υm and Cm are undefined.
326+
*/
327+
if (sort===1 && psiIndex!==0 && indexVec[psiIndex] < psi_m && psi_m===indexVec[psiIndex+1] ||
328+
329+
/* case 2: Ψk > ψm = Ψk+1 for monotonically decreasing index values,except when ψm = Ψ1
330+
* Ψk > ψm = Ψk+1 for monotonically decreasing index values,except when ψm = Ψ1
331+
*/
332+
sort===-1 && psiIndex!==0 && indexVec[psiIndex] >psi_m && psi_m===indexVec[psiIndex+1] ){
333+
334+
/* Since two consecutive index values may be equal, the index following
335+
* the matched index must be examined. If Ψk+2 = Ψk+1 = ψm
336+
* (or Ψ2 = Ψ1 = ψm), then Υm and Cm are undefined
337+
*/
338+
if (indexVec[psiIndex+2]===psi_m){
339+
return gamma_m;
340+
}
341+
}
342+
//case 4: ψm = Ψ1
343+
if (indexVec[0]===psi_m && indexVec[1]===psi_m ){
344+
return gamma_m;
345+
}
346+
347+
/*--------------------------------------------------------------------------------
348+
* psiIndex is found, the gamma_m can be interpolated
349+
*/
350+
if (indexVec[psiIndex]!==indexVec[psiIndex + 1] ) {
351+
// Υm = k + (ψm − Ψk) / (Ψk+1− Ψk) where k = 1,... Kmax,
352+
// array index here is from 0, so the k = psiIndex +1. For example is psiIndex =0, it means k=1, Ψ1=ψm
353+
gamma_m =psiIndex+1 + (psi_m - indexVec[psiIndex]) / (indexVec[psiIndex+1] - indexVec[psiIndex]);
354+
return gamma_m;
355+
}
356+
}
357+
function getGammaByExtrarpolation(psiIndex, psi_m, indexVec, sort){
358+
let gamma_m=undefined;
359+
const kMax = indexVec.length;
360+
const inCoords = Array.from(new Array(indexVec.length), (x,i) => i);
361+
const linearInterpolator = LinearInterpolator(inCoords, indexVec, true);
362+
const left = indexVec[0] - (indexVec[1] - indexVec[0]) / 2;
363+
const right = indexVec[kMax - 1] + (indexVec[kMax - 1] - indexVec[kMax - 2]) / 2;
364+
365+
// When no psi_m is found in the vector psiIndex=-1), we need to check if the gamma_m can be extrapolated.
366+
367+
if (indexVec.length===1){
368+
/* When the indexVector's length = 1 with ψm in the range Ψ1 − 0.5 ≤ ψm ≤ Ψ1 + 0.5 (noting that Ψ1
369+
* should be equal to 1 in this case) whence Υm = ψm
370+
*/
371+
if (indexVec[0] - 0.5 <=psi_m && psi_m <=indexVec[0] + 0.5){
372+
gamma_m= psi_m;
373+
}
374+
}
375+
else { //do extrapolation
376+
switch (sort) {
377+
case 1:
378+
379+
if (left <= psi_m && psi_m < indexVec[0]) {
380+
const psi_left = linearInterpolator( left);
381+
gamma_m = (psi_m - psi_left) /(indexVec[0] -psi_left);
382+
383+
}
384+
else if (indexVec[kMax - 1] < psi_m && psi_m <= right) {
385+
const psi_right =linearInterpolator( right );
386+
gamma_m = indexVec.length + 1 + (psi_m - indexVec[kMax - 1]) /
387+
(psi_right - indexVec[kMax - 1]);
388+
}
389+
390+
break;
391+
case -1:
392+
if (left >= psi_m && psi_m > indexVec[0]) {
393+
const psi_left = linearInterpolator( left);
394+
gamma_m = (psi_m - psi_left) /
395+
(indexVec[0] - psi_left);
396+
397+
}
398+
else if (indexVec[kMax - 1] > psi_m && psi_m >= right) {
399+
const psi_right = linearInterpolator( right);
400+
gamma_m = indexVec.length + 1 + (psi_m - indexVec[kMax - 1])/(psi_right - indexVec[kMax - 1]);
401+
}
402+
403+
break;
404+
}
405+
}
406+
407+
return gamma_m;
408+
409+
}
247410
/**
411+
* The algorithm for computing Υm, and thence Cm,is as follows:
412+
* Scan the indexing vector, (Ψ1,Ψ2, . . .), sequentially
413+
* starting from the first element, Ψ1, until a successive
414+
* pair of index values is found that encompass ψm (i.e. such that
415+
* Ψk ≤ ψm ≤ Ψk+1 for monotonically increasing index values or
416+
* Ψk ≥ ψm ≥ Ψk+1 for monotonically decreasing index values
417+
* for some k). Then, when Ψk  Ψk+1, interpolate linearly on the
418+
* indices Υm = k + (ψm − Ψk)/ (Ψk+1 − Ψk). The Ym (gamma_m) is the index in
419+
* the coordinate array.
420+
*
421+
* However if Ψk+1 = Ψk, the Υm is undefined.
422+
*
423+
* NOTE, the array index starting from 1, not 0 as in javascript
424+
*
425+
* Υm = (k + (psi_m-psi_k)/psi_k+1 - psi_k) )
426+
* Υm = k + ( (ψm − Ψk ) /( Ψk+1 − Ψk) )
427+
* How to calculate gamma_m (Υm):
428+
* case 1:
429+
* where psi is the index vector. In our case, it is indexVec.
430+
* If (psi_k <= psi_m <= psi_k+1) for for monotonically increasing index values
431+
* or
432+
* If (psi_k >= psi_m >= psi_k+1) for for monotonically decreasing index values
433+
* Then the gamma_m = (k + (psi_m-psi_k)/psi_k+1 - psi_k) )
434+
*
435+
* case 2:
436+
* if psi_k = psi_k+1 = psi_m, based on the formula above, the value is undefined. In this case,
437+
* we need to find a psi_k such that psi_k < psi_m = psi_k+1 for monotonically increasing index values
438+
* or psi_k > psi_m = psi_k+1 for monotonically decreasing index values. Since two consecutive index
439+
* values may be equal, the index following the matched index must be examined. If Ψk+2 = Ψk+1 = ψm
440+
* (or Ψ2 = Ψ1 = ψm), then Υm and Cm are undefined.
441+
*
442+
* case 3: linear interpolation
443+
* If psi_m in the range psi_1 to psi_k inclusive, interpolation is needed.
444+
*
445+
* case 4:
446+
* if Ψk+1 = Ψk, the Υm is undefined.
447+
*
448+
* case 5: extrapolation
449+
* psi_m outside range psi_1 - psi_k, and k>1,
450+
* if (psi_1 - (psi_2-psi_1)/2 ) < = psi_m <psi_1 or
451+
* psi_k <psi_m <= (psi_k + (psi_k - psi_k-1) /2)
452+
* monotonically increasing index values
453+
*
454+
* (psi_1 - (psi_2-psi_1)/2 ) > = psi_m > psi_1 or
455+
* psi_k > psi_m >= (psi_k + (psi_k - psi_k-1) /2)
456+
* for monotonically decreasing index values
457+
*
248458
*
249459
* @param {Array.<number>} indexVec
250-
* @param {number} psi
251-
* @param {number} idx
252-
* @return {*}
460+
* @param {number} psi_m
461+
* @param sort
462+
* @returns {*}
253463
*/
254-
function calculateGamma_m(indexVec, psi, idx) {
255-
/* Scan the indexing vector, (Ψ1, Ψ2,...), sequen-tially starting from the first element, Ψ1,
256-
* until a successive pair of index values is found that encompass ψm (i.e. such that Ψk ≤ ψm ≤ Ψk+1
257-
* for monotonically increasing index values or Ψk ≥ ψm ≥ Ψk+1 for monotonically decreasing index values
258-
* for some k). Then, when Ψk  Ψk+1, interpolate linearly on the indices
259-
*/
464+
function calculateGamma_m(indexVec, psi_m, sort) {
465+
466+
const psiIndex = searchIndex(indexVec, psi_m, sort);
260467

261468

262-
if (idx!==-1 && indexVec[idx-1]!==indexVec[idx]){
263-
// Υm = k + (ψm − Ψk) / (Ψk+1− Ψk)
264-
return idx + (psi-indexVec[idx-1])/(indexVec[idx]-indexVec[idx-1]);
469+
470+
if (isNaN(psiIndex)) {//The index vector is not sorted, return undefined
471+
return undefined;
472+
}
473+
474+
475+
if (psiIndex!==-1){
476+
/*
477+
Ψk ≤ ψm ≤ Ψk+1 for monotonically increasing index values or
478+
Ψk ≥ ψm ≥ Ψk+1 for monotonically decreasing index values for some k (here, we use psiIndex)
479+
*/
480+
return getGammaByInterpolation(psiIndex, psi_m, indexVec, sort);
265481
}
266482
else {
267-
return NaN; // No index found in the index array, gamma is undefined, so is coordinate
483+
// psiIndex=-1: no range found, the psi_m is outside of the indexVector's range
484+
return getGammaByExtrarpolation(psiIndex, psi_m, indexVec, sort);
268485
}
269486

270-
}
271-
272487

488+
}
273489

274490

275491

@@ -289,30 +505,37 @@ function isSorted(intArray) {
289505
}
290506

291507

508+
/**
509+
/* Scan the indexing vector, (Ψ1, Ψ2,...), sequentially starting from the first element, Ψ1,
510+
* until a successive pair of index values is found that encompass ψm (i.e. such that Ψk ≤ ψm ≤ Ψk+1
511+
* for monotonically increasing index values or Ψk ≥ ψm ≥ Ψk+1 for monotonically decreasing index values
512+
* for some k). Then, when Ψk  Ψk+1, interpolate linearly on the indices
513+
*
514+
* NOTE: the index in the algorthim is starting with 1, in Javascript, the index is starting from 0.
515+
*
516+
* @param indexVec
517+
* @param psi_m
518+
* @returns {number}
519+
*/
520+
function searchIndex(indexVec, psi_m) {
292521

293522

294-
function searchIndex(indexVec, psi) {
295-
/*Scan the indexing vector, (Ψ1, Ψ2,...), sequen-tially starting from the first element, Ψ1,
296-
until a successive pair of index values is found that encompass ψm (i.e. such that Ψk ≤ ψm ≤ Ψk+1
297-
for monotonically increasing index values or Ψk ≥ ψm ≥ Ψk+1 for monotonically decreasing index values
298-
for some k). Then, when Ψk  Ψk+1, interpolate linearly on the indices
299-
*/
300-
301523
const sort = isSorted(indexVec); //1:ascending, -1: descending, 0: not sorted
302524
if (sort===0){
303525
return NaN; //The vector index array has to be either ascending or descending
304526
}
305527

306-
307-
for (let i=1; i<indexVec.length; i++){
308-
if (sort===1 && indexVec[i-1]<=psi && psi<=indexVec[i]){
528+
for (let i=0; i<indexVec.length-1; i++){
529+
if (sort===1 && indexVec[i]<=psi_m && psi_m<=indexVec[i+1]){
309530
return i;
310531
}
311-
if (sort===-1 && indexVec[i-1]>=psi && psi>=indexVec[i]){
532+
if (sort===-1 && indexVec[i]>=psi_m && psi_m>=indexVec[i+1]){
312533
return i;
313534

314535
}
315536
}
537+
538+
//no range found, the psi_m is outside of the indexVector's range
316539
return -1;
317540
}
318541

src/firefly/js/visualize/projection/WavelengthHeaderParser.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,22 @@ function calculateWavelengthParams(parse, altWcs, which, pc_3j_key,wlTable) {
217217
* position. For example, the algorithm is TAB.
218218
*/
219219
const algorithmForPlane = isWL? algorithm: PLANE;
220-
return makeSimplePlaneBased(crpix, crval, cdelt,nAxis, algorithmForPlane, wlType, units, 'use PLANE since is cube and parameters missing');
220+
const ret = makeSimplePlaneBased(crpix, crval, cdelt,nAxis, algorithmForPlane, wlType, units, 'use PLANE since is cube and parameters missing');
221+
if (algorithm==='TAB') {
222+
const part1 = {
223+
N, algorithm, ctype, restWAV, pc_3j, r_j,
224+
ps3_0, ps3_1, ps3_2, pv3_0, pv3_1, pv3_2,
225+
s_3: isNaN(cdelt) ? 0 : cdelt,
226+
lambda_r: isNaN(crval) ? 0 : crval,
227+
wlTable
228+
};
229+
230+
return {...part1, ...ret};
231+
}
232+
else {
233+
return ret;
234+
}
235+
221236
}
222237

223238
//Plot and display the wavelength as one of the mouse readout only if the FITs header
@@ -264,6 +279,7 @@ function calculateWavelengthParams(parse, altWcs, which, pc_3j_key,wlTable) {
264279
function makeSimplePlaneBased(crpix,crval, cdelt, nAxis,algorithm, wlType, units, reason) {
265280
if (allGoodValues(crpix,crval,cdelt) && nAxis===3 ) {
266281
//return {algorithm: PLANE, wlType: wlType || WAVE, crpix, crval, cdelt, units, reason};
282+
267283
return {algorithm: algorithm, wlType: wlType, crpix, crval, cdelt, units, reason};
268284
}
269285
else {

0 commit comments

Comments
 (0)