astro-viewer 3.8.0 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +325 -222
  2. package/dist/astroviewer.cjs +286 -86
  3. package/dist/astroviewer.cjs.map +1 -1
  4. package/dist/astroviewer.js +286 -86
  5. package/dist/astroviewer.js.map +1 -1
  6. package/dist/astroviewer.min.js +1 -1
  7. package/dist/astroviewer.min.js.map +1 -1
  8. package/lib-esm/AstroSphere.d.ts +4 -0
  9. package/lib-esm/AstroSphere.d.ts.map +1 -1
  10. package/lib-esm/AstroSphere.js +15 -0
  11. package/lib-esm/AstroSphere.js.map +1 -1
  12. package/lib-esm/AstroViewer.d.ts +4 -0
  13. package/lib-esm/AstroViewer.d.ts.map +1 -1
  14. package/lib-esm/AstroViewer.js +9 -0
  15. package/lib-esm/AstroViewer.js.map +1 -1
  16. package/lib-esm/index.d.ts +1 -0
  17. package/lib-esm/index.d.ts.map +1 -1
  18. package/lib-esm/index.js +0 -1
  19. package/lib-esm/index.js.map +1 -1
  20. package/lib-esm/model/grid/HealpixGrid.d.ts.map +1 -1
  21. package/lib-esm/model/grid/HealpixGrid.js +7 -1
  22. package/lib-esm/model/grid/HealpixGrid.js.map +1 -1
  23. package/lib-esm/model/hips/AllSky.d.ts.map +1 -1
  24. package/lib-esm/model/hips/AllSky.js +3 -25
  25. package/lib-esm/model/hips/AllSky.js.map +1 -1
  26. package/lib-esm/model/hips/AncestorTile.d.ts.map +1 -1
  27. package/lib-esm/model/hips/AncestorTile.js +3 -25
  28. package/lib-esm/model/hips/AncestorTile.js.map +1 -1
  29. package/lib-esm/model/hips/FitsTileLoader.d.ts +4 -0
  30. package/lib-esm/model/hips/FitsTileLoader.d.ts.map +1 -1
  31. package/lib-esm/model/hips/FitsTileLoader.js +39 -1
  32. package/lib-esm/model/hips/FitsTileLoader.js.map +1 -1
  33. package/lib-esm/model/hips/HiPS.d.ts +24 -0
  34. package/lib-esm/model/hips/HiPS.d.ts.map +1 -1
  35. package/lib-esm/model/hips/HiPS.js +153 -0
  36. package/lib-esm/model/hips/HiPS.js.map +1 -1
  37. package/lib-esm/model/hips/Tile.d.ts.map +1 -1
  38. package/lib-esm/model/hips/Tile.js +3 -25
  39. package/lib-esm/model/hips/Tile.js.map +1 -1
  40. package/lib-esm/shader/HiPSShaderProgram.d.ts +5 -1
  41. package/lib-esm/shader/HiPSShaderProgram.d.ts.map +1 -1
  42. package/lib-esm/shader/HiPSShaderProgram.js +15 -1
  43. package/lib-esm/shader/HiPSShaderProgram.js.map +1 -1
  44. package/lib-esm/shader/ShaderManager.d.ts.map +1 -1
  45. package/lib-esm/shader/ShaderManager.js +39 -7
  46. package/lib-esm/shader/ShaderManager.js.map +1 -1
  47. package/package.json +1 -1
@@ -4130,6 +4130,21 @@ class AstroSphere {
4130
4130
  }
4131
4131
  this._activeHiPS.changeFormat(format);
4132
4132
  }
4133
+ setHiPSFITSScaleFunction(scaleFunction, scaleParam) {
4134
+ if (!this._activeHiPS) {
4135
+ throw new Error("No active HiPS.");
4136
+ }
4137
+ this._activeHiPS.setFITSScaleFunction(scaleFunction, scaleParam);
4138
+ }
4139
+ getActiveHiPSFITSStretch() {
4140
+ return this._activeHiPS?.fitsStretch ?? null;
4141
+ }
4142
+ setHiPSFITSRangeMode(rangeMode) {
4143
+ if (!this._activeHiPS) {
4144
+ throw new Error("No active HiPS.");
4145
+ }
4146
+ this._activeHiPS.setFITSRangeMode(rangeMode);
4147
+ }
4133
4148
  createHiPS(hipsDescriptor) {
4134
4149
  return new HiPS_js_1.HiPS(1, [0.0, 0.0, 0.0], 0, 0, hipsDescriptor, this._webgl, this._healpixGrid);
4135
4150
  }
@@ -5001,6 +5016,15 @@ class AstroViewer {
5001
5016
  changeHiPSFormat(format) {
5002
5017
  this.astroSphere.changeHiPSFormat(format);
5003
5018
  }
5019
+ setHiPSFITSScaleFunction(scaleFunction, scaleParam) {
5020
+ this.astroSphere.setHiPSFITSScaleFunction(scaleFunction, scaleParam);
5021
+ }
5022
+ getActiveHiPSFITSStretch() {
5023
+ return this.astroSphere.getActiveHiPSFITSStretch();
5024
+ }
5025
+ setHiPSFITSRangeMode(rangeMode) {
5026
+ this.astroSphere.setHiPSFITSRangeMode(rangeMode);
5027
+ }
5004
5028
  getActiveHiPSFormats() {
5005
5029
  return this.astroSphere.activeHiPS?.availableFormats ?? [];
5006
5030
  }
@@ -5867,7 +5891,6 @@ var Source_js_1 = __webpack_require__(146);
5867
5891
  Object.defineProperty(exports, "Source", ({ enumerable: true, get: function () { return Source_js_1.Source; } }));
5868
5892
  var Footprint_js_1 = __webpack_require__(2475);
5869
5893
  Object.defineProperty(exports, "Footprint", ({ enumerable: true, get: function () { return Footprint_js_1.Footprint; } }));
5870
- console.log('astroviewer UMD loaded');
5871
5894
 
5872
5895
 
5873
5896
  /***/ },
@@ -11633,8 +11656,14 @@ class HealpixGrid extends AbstractSkyEntity_js_1.AbstractSkyEntity {
11633
11656
  // FIX: pass model & pMatrix to match FoVUtils TS signature
11634
11657
  const center = FoVUtils_js_1.FoVUtils.getCenterJ2000(gl.canvas, this, this._webgl, camera, pMatrix);
11635
11658
  const fovMin = (this.getMinFoV() * Math.PI) / 180 / 2;
11659
+ const visibleHealpix = Global_js_1.default.getHealpix(this._visibleorder);
11660
+ const maxPixel = visibleHealpix.getNPix();
11636
11661
  for (let p = 0; p < pixels.length; p++) {
11637
- const pixCenter = Global_js_1.default.getHealpix(this._visibleorder).pix2vec(pixels[p]);
11662
+ const pixel = pixels[p];
11663
+ if (!Number.isInteger(pixel) || pixel < 0 || pixel >= maxPixel) {
11664
+ continue;
11665
+ }
11666
+ const pixCenter = visibleHealpix.pix2vec(pixel);
11638
11667
  // const pixCenter = (global.getHealpix(global.order).pix2vec(pixels[p]) as BoundVec);
11639
11668
  const point = new Point_js_1.Point({ x: pixCenter.x, y: pixCenter.y, z: pixCenter.z }, CoordsType_js_1.CoordsType.CARTESIAN);
11640
11669
  const distance = GeomUtils_js_1.default.orthodromicDistance(center, point);
@@ -12062,6 +12091,7 @@ class AllSky {
12062
12091
  async loadFits() {
12063
12092
  try {
12064
12093
  this._fitsTile = await (0, FitsTileLoader_js_1.loadFitsTile)(this._texurl);
12094
+ this._hips.observeFITSDataRange(this._fitsTile);
12065
12095
  this.textureLoaded();
12066
12096
  this.initModelBuffer();
12067
12097
  this._textureLoaded = true;
@@ -12106,30 +12136,7 @@ class AllSky {
12106
12136
  }
12107
12137
  }
12108
12138
  getFitsDataRange() {
12109
- const hipsRange = this._hips.dataRange;
12110
- if (hipsRange.min !== undefined &&
12111
- hipsRange.max !== undefined &&
12112
- Number.isFinite(hipsRange.min) &&
12113
- Number.isFinite(hipsRange.max) &&
12114
- hipsRange.max > hipsRange.min) {
12115
- return {
12116
- min: hipsRange.min,
12117
- max: hipsRange.max,
12118
- };
12119
- }
12120
- const fits = this._fitsTile;
12121
- if (fits &&
12122
- fits.dataMin !== null &&
12123
- fits.dataMax !== null &&
12124
- Number.isFinite(fits.dataMin) &&
12125
- Number.isFinite(fits.dataMax) &&
12126
- fits.dataMax > fits.dataMin) {
12127
- return {
12128
- min: fits.dataMin,
12129
- max: fits.dataMax,
12130
- };
12131
- }
12132
- return null;
12139
+ return this._hips.getFITSDisplayRange(this._fitsTile);
12133
12140
  }
12134
12141
  initModelBuffer() {
12135
12142
  const gl = this._webgl;
@@ -12243,7 +12250,7 @@ class AllSky {
12243
12250
  console.error(`[AllSky] No display range available for FITS atlas ${this._texurl}`);
12244
12251
  return false;
12245
12252
  }
12246
- this._hipsShaderProgram.setTextureDataMode(this._hipsShaderIndex, true, range.min, range.max);
12253
+ this._hipsShaderProgram.setTextureDataMode(this._hipsShaderIndex, true, range.min, range.max, this._hips.fitsStretch.scaleFunctionIndex, this._hips.fitsStretch.scaleParam);
12247
12254
  }
12248
12255
  else {
12249
12256
  this._hipsShaderProgram.setTextureDataMode(this._hipsShaderIndex, false);
@@ -12380,6 +12387,7 @@ class AncestorTile {
12380
12387
  async loadFits() {
12381
12388
  try {
12382
12389
  this._fitsTile = await (0, FitsTileLoader_js_1.loadFitsTile)(this._texurl);
12390
+ this._hips.observeFITSDataRange(this._fitsTile);
12383
12391
  this.textureLoaded();
12384
12392
  const fits = this._fitsTile;
12385
12393
  if (!fits) {
@@ -12437,30 +12445,7 @@ class AncestorTile {
12437
12445
  }
12438
12446
  }
12439
12447
  getFitsDataRange() {
12440
- const hipsRange = this._hips.dataRange;
12441
- if (hipsRange.min !== undefined &&
12442
- hipsRange.max !== undefined &&
12443
- Number.isFinite(hipsRange.min) &&
12444
- Number.isFinite(hipsRange.max) &&
12445
- hipsRange.max > hipsRange.min) {
12446
- return {
12447
- min: hipsRange.min,
12448
- max: hipsRange.max,
12449
- };
12450
- }
12451
- const fits = this._fitsTile;
12452
- if (fits &&
12453
- fits.dataMin !== null &&
12454
- fits.dataMax !== null &&
12455
- Number.isFinite(fits.dataMin) &&
12456
- Number.isFinite(fits.dataMax) &&
12457
- fits.dataMax > fits.dataMin) {
12458
- return {
12459
- min: fits.dataMin,
12460
- max: fits.dataMax,
12461
- };
12462
- }
12463
- return null;
12448
+ return this._hips.getFITSDisplayRange(this._fitsTile);
12464
12449
  }
12465
12450
  initModelBuffer() {
12466
12451
  const gl = this._webgl;
@@ -12560,7 +12545,7 @@ class AncestorTile {
12560
12545
  console.error(`[AncestorTile] No display range available for FITS tile ${this._texurl}`);
12561
12546
  return false;
12562
12547
  }
12563
- this._hipsShaderProgram.setTextureDataMode(this._hipsShaderIndex, true, range.min, range.max);
12548
+ this._hipsShaderProgram.setTextureDataMode(this._hipsShaderIndex, true, range.min, range.max, this._hips.fitsStretch.scaleFunctionIndex, this._hips.fitsStretch.scaleParam);
12564
12549
  }
12565
12550
  else {
12566
12551
  this._hipsShaderProgram.setTextureDataMode(this._hipsShaderIndex, false);
@@ -12637,6 +12622,9 @@ var __webpack_unused_export__;
12637
12622
  __webpack_unused_export__ = ({ value: true });
12638
12623
  exports.loadFitsTile = loadFitsTile;
12639
12624
  const jsfitsio_1 = __webpack_require__(4971);
12625
+ const ROBUST_SAMPLE_COUNT = 8192;
12626
+ const ROBUST_LOW_PERCENTILE = 0.005;
12627
+ const ROBUST_HIGH_PERCENTILE = 0.995;
12640
12628
  async function loadFitsTile(url) {
12641
12629
  const fitsFile = await jsfitsio_1.FITSParser.loadFITSFile(url);
12642
12630
  if (!fitsFile?.primaryHDU) {
@@ -12666,22 +12654,57 @@ async function loadFitsTile(url) {
12666
12654
  const dataMin = getNumericHeaderValue(image.header, jsfitsio_1.FITSHeaderManager.DATAMIN);
12667
12655
  const dataMax = getNumericHeaderValue(image.header, jsfitsio_1.FITSHeaderManager.DATAMAX);
12668
12656
  const pixels = new Float32Array(data.length);
12657
+ let physicalMin = null;
12658
+ let physicalMax = null;
12659
+ const robustSamples = [];
12660
+ const sampleStep = Math.max(1, Math.floor(data.length / ROBUST_SAMPLE_COUNT));
12669
12661
  for (let i = 0; i < data.length; i++) {
12670
12662
  const rawValue = data[i];
12671
12663
  if (!Number.isFinite(rawValue) || (blank !== null && rawValue === blank)) {
12672
12664
  pixels[i] = Number.NaN;
12673
12665
  continue;
12674
12666
  }
12675
- pixels[i] = rawValue * bscale + bzero;
12667
+ const physicalValue = rawValue * bscale + bzero;
12668
+ pixels[i] = physicalValue;
12669
+ if (Number.isFinite(physicalValue)) {
12670
+ physicalMin =
12671
+ physicalMin === null ? physicalValue : Math.min(physicalMin, physicalValue);
12672
+ physicalMax =
12673
+ physicalMax === null ? physicalValue : Math.max(physicalMax, physicalValue);
12674
+ if (i % sampleStep === 0) {
12675
+ robustSamples.push(physicalValue);
12676
+ }
12677
+ }
12676
12678
  }
12679
+ const robustRange = computeRobustRange(robustSamples);
12677
12680
  return {
12678
12681
  width,
12679
12682
  height,
12680
12683
  pixels,
12681
12684
  dataMin,
12682
12685
  dataMax,
12686
+ physicalMin,
12687
+ physicalMax,
12688
+ robustMin: robustRange?.min ?? null,
12689
+ robustMax: robustRange?.max ?? null,
12683
12690
  };
12684
12691
  }
12692
+ function computeRobustRange(samples) {
12693
+ if (samples.length === 0) {
12694
+ return null;
12695
+ }
12696
+ samples.sort((a, b) => a - b);
12697
+ const min = percentile(samples, ROBUST_LOW_PERCENTILE);
12698
+ const max = percentile(samples, ROBUST_HIGH_PERCENTILE);
12699
+ if (!Number.isFinite(min) || !Number.isFinite(max) || max <= min) {
12700
+ return null;
12701
+ }
12702
+ return { min, max };
12703
+ }
12704
+ function percentile(sortedValues, p) {
12705
+ const index = Math.min(sortedValues.length - 1, Math.max(0, Math.round((sortedValues.length - 1) * p)));
12706
+ return sortedValues[index];
12707
+ }
12685
12708
  function getNumericHeaderValue(header, key) {
12686
12709
  const item = header.findById(key);
12687
12710
  if (!item) {
@@ -12885,6 +12908,21 @@ const ColorMaps_js_1 = __importDefault(__webpack_require__(619));
12885
12908
  const AncestorTile_js_1 = __importDefault(__webpack_require__(2885));
12886
12909
  const AllSky_js_1 = __importDefault(__webpack_require__(2368));
12887
12910
  const HiPSCoverage_js_1 = __webpack_require__(9912);
12911
+ const FITS_SCALE_FUNCTION_INDEX = {
12912
+ linear: 0,
12913
+ sqrt: 1,
12914
+ log: 2,
12915
+ asinh: 3,
12916
+ gamma: 4,
12917
+ };
12918
+ const DEFAULT_FITS_SCALE_PARAM = {
12919
+ linear: 1,
12920
+ sqrt: 1,
12921
+ log: 100,
12922
+ asinh: 10,
12923
+ gamma: 0.5,
12924
+ };
12925
+ const MAX_ROBUST_TILE_RANGES = 512;
12888
12926
  class HiPS extends AbstractSkyEntity_js_1.AbstractSkyEntity {
12889
12927
  _ancestorTiles;
12890
12928
  _allSkyTile;
@@ -12901,6 +12939,13 @@ class HiPS extends AbstractSkyEntity_js_1.AbstractSkyEntity {
12901
12939
  samplerIdx = 0;
12902
12940
  colorMapIdx = 0;
12903
12941
  colorMap = ColorMaps_js_1.default["native"];
12942
+ _fitsStretch = {
12943
+ scaleFunction: "linear",
12944
+ scaleFunctionIndex: FITS_SCALE_FUNCTION_INDEX.linear,
12945
+ scaleParam: DEFAULT_FITS_SCALE_PARAM.linear,
12946
+ rangeMode: "robust",
12947
+ };
12948
+ _robustFITSRanges = [];
12904
12949
  _healpixGrid;
12905
12950
  // exposed read-only helpers
12906
12951
  get maxOrder() {
@@ -13136,6 +13181,137 @@ class HiPS extends AbstractSkyEntity_js_1.AbstractSkyEntity {
13136
13181
  get dataRange() {
13137
13182
  return this._descriptor.dataRange;
13138
13183
  }
13184
+ get fitsStretch() {
13185
+ return this._fitsStretch;
13186
+ }
13187
+ setFITSScaleFunction(scaleFunction, scaleParam) {
13188
+ const scaleFunctionIndex = FITS_SCALE_FUNCTION_INDEX[scaleFunction];
13189
+ if (scaleFunctionIndex === undefined) {
13190
+ throw new Error(`Unsupported FITS scale function: ${scaleFunction}`);
13191
+ }
13192
+ const defaultParam = DEFAULT_FITS_SCALE_PARAM[scaleFunction];
13193
+ const param = scaleParam ?? defaultParam;
13194
+ if (!Number.isFinite(param) || param <= 0) {
13195
+ throw new Error(`Invalid FITS scale parameter: ${param}`);
13196
+ }
13197
+ this._fitsStretch = {
13198
+ ...this._fitsStretch,
13199
+ scaleFunction,
13200
+ scaleFunctionIndex,
13201
+ scaleParam: param,
13202
+ };
13203
+ }
13204
+ setFITSRangeMode(rangeMode) {
13205
+ if (rangeMode !== "robust" && rangeMode !== "hips" && rangeMode !== "tile") {
13206
+ throw new Error(`Unsupported FITS range mode: ${rangeMode}`);
13207
+ }
13208
+ this._fitsStretch = {
13209
+ ...this._fitsStretch,
13210
+ rangeMode,
13211
+ };
13212
+ }
13213
+ observeFITSDataRange(fits) {
13214
+ if (fits.robustMin === null ||
13215
+ fits.robustMax === null ||
13216
+ !Number.isFinite(fits.robustMin) ||
13217
+ !Number.isFinite(fits.robustMax) ||
13218
+ fits.robustMax <= fits.robustMin) {
13219
+ return;
13220
+ }
13221
+ this._robustFITSRanges.push({
13222
+ min: fits.robustMin,
13223
+ max: fits.robustMax,
13224
+ });
13225
+ if (this._robustFITSRanges.length > MAX_ROBUST_TILE_RANGES) {
13226
+ this._robustFITSRanges.shift();
13227
+ }
13228
+ }
13229
+ getFITSDisplayRange(fits) {
13230
+ const hipsRange = this.getHiPSDataRange();
13231
+ if (this._fitsStretch.rangeMode === "hips" && hipsRange) {
13232
+ return hipsRange;
13233
+ }
13234
+ if (this._fitsStretch.rangeMode === "robust") {
13235
+ const robustRange = this.getRobustFITSRange() ?? this.getTileRobustRange(fits);
13236
+ if (robustRange) {
13237
+ return robustRange;
13238
+ }
13239
+ }
13240
+ const tileRange = this.getTilePhysicalRange(fits) ?? this.getTileHeaderRange(fits);
13241
+ if (tileRange) {
13242
+ return tileRange;
13243
+ }
13244
+ return hipsRange;
13245
+ }
13246
+ getHiPSDataRange() {
13247
+ const hipsRange = this.dataRange;
13248
+ if (hipsRange.min !== undefined &&
13249
+ hipsRange.max !== undefined &&
13250
+ Number.isFinite(hipsRange.min) &&
13251
+ Number.isFinite(hipsRange.max) &&
13252
+ hipsRange.max > hipsRange.min) {
13253
+ return {
13254
+ min: hipsRange.min,
13255
+ max: hipsRange.max,
13256
+ };
13257
+ }
13258
+ return null;
13259
+ }
13260
+ getRobustFITSRange() {
13261
+ if (this._robustFITSRanges.length === 0) {
13262
+ return null;
13263
+ }
13264
+ const lows = this._robustFITSRanges.map((range) => range.min).sort((a, b) => a - b);
13265
+ const highs = this._robustFITSRanges.map((range) => range.max).sort((a, b) => a - b);
13266
+ const min = lows[Math.floor((lows.length - 1) * 0.1)];
13267
+ const max = highs[Math.ceil((highs.length - 1) * 0.9)];
13268
+ if (!Number.isFinite(min) || !Number.isFinite(max) || max <= min) {
13269
+ return null;
13270
+ }
13271
+ return { min, max };
13272
+ }
13273
+ getTileRobustRange(fits) {
13274
+ if (fits &&
13275
+ fits.robustMin !== null &&
13276
+ fits.robustMax !== null &&
13277
+ Number.isFinite(fits.robustMin) &&
13278
+ Number.isFinite(fits.robustMax) &&
13279
+ fits.robustMax > fits.robustMin) {
13280
+ return {
13281
+ min: fits.robustMin,
13282
+ max: fits.robustMax,
13283
+ };
13284
+ }
13285
+ return null;
13286
+ }
13287
+ getTilePhysicalRange(fits) {
13288
+ if (fits &&
13289
+ fits.physicalMin !== null &&
13290
+ fits.physicalMax !== null &&
13291
+ Number.isFinite(fits.physicalMin) &&
13292
+ Number.isFinite(fits.physicalMax) &&
13293
+ fits.physicalMax > fits.physicalMin) {
13294
+ return {
13295
+ min: fits.physicalMin,
13296
+ max: fits.physicalMax,
13297
+ };
13298
+ }
13299
+ return null;
13300
+ }
13301
+ getTileHeaderRange(fits) {
13302
+ if (fits &&
13303
+ fits.dataMin !== null &&
13304
+ fits.dataMax !== null &&
13305
+ Number.isFinite(fits.dataMin) &&
13306
+ Number.isFinite(fits.dataMax) &&
13307
+ fits.dataMax > fits.dataMin) {
13308
+ return {
13309
+ min: fits.dataMin,
13310
+ max: fits.dataMax,
13311
+ };
13312
+ }
13313
+ return null;
13314
+ }
13139
13315
  getCurrentHealpixOrder() {
13140
13316
  return this._visibleorder;
13141
13317
  }
@@ -13646,6 +13822,7 @@ class Tile {
13646
13822
  async loadFits() {
13647
13823
  try {
13648
13824
  this._fitsTile = await (0, FitsTileLoader_js_1.loadFitsTile)(this._texurl);
13825
+ this._hips.observeFITSDataRange(this._fitsTile);
13649
13826
  this._ready = true;
13650
13827
  }
13651
13828
  catch (error) {
@@ -13784,30 +13961,7 @@ class Tile {
13784
13961
  gl.bufferData(gl.ARRAY_BUFFER, this.vertexPosition[qidx], gl.STATIC_DRAW);
13785
13962
  }
13786
13963
  getFitsDataRange() {
13787
- const hipsRange = this._hips.dataRange;
13788
- if (hipsRange.min !== undefined &&
13789
- hipsRange.max !== undefined &&
13790
- Number.isFinite(hipsRange.min) &&
13791
- Number.isFinite(hipsRange.max) &&
13792
- hipsRange.max > hipsRange.min) {
13793
- return {
13794
- min: hipsRange.min,
13795
- max: hipsRange.max,
13796
- };
13797
- }
13798
- const fits = this._fitsTile;
13799
- if (fits &&
13800
- fits.dataMin !== null &&
13801
- fits.dataMax !== null &&
13802
- Number.isFinite(fits.dataMin) &&
13803
- Number.isFinite(fits.dataMax) &&
13804
- fits.dataMax > fits.dataMin) {
13805
- return {
13806
- min: fits.dataMin,
13807
- max: fits.dataMax,
13808
- };
13809
- }
13810
- return null;
13964
+ return this._hips.getFITSDisplayRange(this._fitsTile);
13811
13965
  }
13812
13966
  get inView() {
13813
13967
  return this._inView;
@@ -13911,7 +14065,7 @@ class Tile {
13911
14065
  console.error(`[Tile] No display range available for FITS tile ${this._texurl}`);
13912
14066
  return;
13913
14067
  }
13914
- hipsShaderProgram.setTextureDataMode(this._hipsShaderIndex, true, range.min, range.max);
14068
+ hipsShaderProgram.setTextureDataMode(this._hipsShaderIndex, true, range.min, range.max, this._hips.fitsStretch.scaleFunctionIndex, this._hips.fitsStretch.scaleParam);
13915
14069
  }
13916
14070
  else {
13917
14071
  hipsShaderProgram.setTextureDataMode(this._hipsShaderIndex, false);
@@ -16756,6 +16910,8 @@ class HiPSShaderProgram {
16756
16910
  textureMode: "uTextureMode",
16757
16911
  dataMin: "uDataMin",
16758
16912
  dataMax: "uDataMax",
16913
+ scaleFunction: "uScaleFunction",
16914
+ scaleParam: "uScaleParam",
16759
16915
  };
16760
16916
  this.gl_attributes = {
16761
16917
  vertex_pos: "aVertexPosition",
@@ -16770,6 +16926,8 @@ class HiPSShaderProgram {
16770
16926
  textureMode: new Array(8).fill(null),
16771
16927
  dataMin: new Array(8).fill(null),
16772
16928
  dataMax: new Array(8).fill(null),
16929
+ scaleFunction: new Array(8).fill(null),
16930
+ scaleParam: new Array(8).fill(null),
16773
16931
  layerOpacity: null,
16774
16932
  clorMapIdx: null,
16775
16933
  vertexPositionAttribute: -1,
@@ -16903,7 +17061,7 @@ class HiPSShaderProgram {
16903
17061
  }
16904
17062
  gl.useProgram(this.shaderProgram);
16905
17063
  }
16906
- setTextureDataMode(samplerIndex, fits, min, max) {
17064
+ setTextureDataMode(samplerIndex, fits, min, max, scaleFunction = 0, scaleParam = 1) {
16907
17065
  if (samplerIndex < 0 || samplerIndex >= 8) {
16908
17066
  throw new Error(`Invalid HiPS sampler index: ${samplerIndex}`);
16909
17067
  }
@@ -16924,12 +17082,20 @@ class HiPSShaderProgram {
16924
17082
  }
16925
17083
  const minLocation = this.locations.dataMin[samplerIndex];
16926
17084
  const maxLocation = this.locations.dataMax[samplerIndex];
17085
+ const scaleFunctionLocation = this.locations.scaleFunction[samplerIndex];
17086
+ const scaleParamLocation = this.locations.scaleParam[samplerIndex];
16927
17087
  if (minLocation) {
16928
17088
  gl.uniform1f(minLocation, min);
16929
17089
  }
16930
17090
  if (maxLocation) {
16931
17091
  gl.uniform1f(maxLocation, max);
16932
17092
  }
17093
+ if (scaleFunctionLocation) {
17094
+ gl.uniform1i(scaleFunctionLocation, scaleFunction);
17095
+ }
17096
+ if (scaleParamLocation) {
17097
+ gl.uniform1f(scaleParamLocation, scaleParam);
17098
+ }
16933
17099
  }
16934
17100
  enableShaders(pMatrix, vMatrix, mMatrix, colorMapIdx) {
16935
17101
  // const gl = global.gl
@@ -16945,6 +17111,8 @@ class HiPSShaderProgram {
16945
17111
  this.locations.textureMode[i] = gl.getUniformLocation(this.shaderProgram, `uTextureMode${i}`);
16946
17112
  this.locations.dataMin[i] = gl.getUniformLocation(this.shaderProgram, `uDataMin${i}`);
16947
17113
  this.locations.dataMax[i] = gl.getUniformLocation(this.shaderProgram, `uDataMax${i}`);
17114
+ this.locations.scaleFunction[i] = gl.getUniformLocation(this.shaderProgram, `uScaleFunction${i}`);
17115
+ this.locations.scaleParam[i] = gl.getUniformLocation(this.shaderProgram, `uScaleParam${i}`);
16948
17116
  if (this.locations.samplers[i]) {
16949
17117
  gl.uniform1i(this.locations.samplers[i], i);
16950
17118
  }
@@ -17281,7 +17449,7 @@ class ShaderManager {
17281
17449
  }
17282
17450
  static footprintVS() {
17283
17451
  return `#version 300 es
17284
- precision highp float;
17452
+ precision mediump float;
17285
17453
 
17286
17454
  layout(location = 0) in vec4 aCatPosition;
17287
17455
 
@@ -17323,7 +17491,7 @@ class ShaderManager {
17323
17491
  }
17324
17492
  static hipsNativeFS() {
17325
17493
  return `#version 300 es
17326
- precision mediump float;
17494
+ precision highp float;
17327
17495
 
17328
17496
  in vec2 vTextureCoord;
17329
17497
 
@@ -17389,7 +17557,7 @@ class ShaderManager {
17389
17557
  }
17390
17558
  static hipsGrayscaleFS() {
17391
17559
  return `#version 300 es
17392
- precision mediump float;
17560
+ precision highp float;
17393
17561
 
17394
17562
  in vec2 vTextureCoord;
17395
17563
 
@@ -17463,7 +17631,7 @@ class ShaderManager {
17463
17631
  }
17464
17632
  static hipsColorMapFS() {
17465
17633
  return `#version 300 es
17466
- precision mediump float;
17634
+ precision highp float;
17467
17635
 
17468
17636
  uniform float uLayerOpacity;
17469
17637
 
@@ -17568,6 +17736,36 @@ class ShaderManager {
17568
17736
  uniform int uTextureMode${index};
17569
17737
  uniform float uDataMin${index};
17570
17738
  uniform float uDataMax${index};
17739
+ uniform int uScaleFunction${index};
17740
+ uniform float uScaleParam${index};
17741
+
17742
+ float asinhCompat${index}(float value) {
17743
+ return log(value + sqrt(value * value + 1.0));
17744
+ }
17745
+
17746
+ float applyFitsScale${index}(float normalized) {
17747
+ float x = clamp(normalized, 0.0, 1.0);
17748
+
17749
+ if (uScaleFunction${index} == 1) {
17750
+ return sqrt(x);
17751
+ }
17752
+
17753
+ if (uScaleFunction${index} == 2) {
17754
+ float strength = max(uScaleParam${index}, 1.0);
17755
+ return log(1.0 + strength * x) / log(1.0 + strength);
17756
+ }
17757
+
17758
+ if (uScaleFunction${index} == 3) {
17759
+ float strength = max(uScaleParam${index}, 1.0);
17760
+ return asinhCompat${index}(strength * x) / asinhCompat${index}(strength);
17761
+ }
17762
+
17763
+ if (uScaleFunction${index} == 4) {
17764
+ return pow(x, max(uScaleParam${index}, 0.0001));
17765
+ }
17766
+
17767
+ return x;
17768
+ }
17571
17769
 
17572
17770
  vec4 sampleHiPSTexture${index}(vec2 uv) {
17573
17771
  vec4 color = texture(uSampler${index}, uv);
@@ -17591,10 +17789,12 @@ class ShaderManager {
17591
17789
  1.0
17592
17790
  );
17593
17791
 
17792
+ float scaled = applyFitsScale${index}(normalized);
17793
+
17594
17794
  return vec4(
17595
- normalized,
17596
- normalized,
17597
- normalized,
17795
+ scaled,
17796
+ scaled,
17797
+ scaled,
17598
17798
  1.0
17599
17799
  );
17600
17800
  }