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
@@ -4141,6 +4141,21 @@ class AstroSphere {
4141
4141
  }
4142
4142
  this._activeHiPS.changeFormat(format);
4143
4143
  }
4144
+ setHiPSFITSScaleFunction(scaleFunction, scaleParam) {
4145
+ if (!this._activeHiPS) {
4146
+ throw new Error("No active HiPS.");
4147
+ }
4148
+ this._activeHiPS.setFITSScaleFunction(scaleFunction, scaleParam);
4149
+ }
4150
+ getActiveHiPSFITSStretch() {
4151
+ return this._activeHiPS?.fitsStretch ?? null;
4152
+ }
4153
+ setHiPSFITSRangeMode(rangeMode) {
4154
+ if (!this._activeHiPS) {
4155
+ throw new Error("No active HiPS.");
4156
+ }
4157
+ this._activeHiPS.setFITSRangeMode(rangeMode);
4158
+ }
4144
4159
  createHiPS(hipsDescriptor) {
4145
4160
  return new HiPS_js_1.HiPS(1, [0.0, 0.0, 0.0], 0, 0, hipsDescriptor, this._webgl, this._healpixGrid);
4146
4161
  }
@@ -5013,6 +5028,15 @@ class AstroViewer {
5013
5028
  changeHiPSFormat(format) {
5014
5029
  this.astroSphere.changeHiPSFormat(format);
5015
5030
  }
5031
+ setHiPSFITSScaleFunction(scaleFunction, scaleParam) {
5032
+ this.astroSphere.setHiPSFITSScaleFunction(scaleFunction, scaleParam);
5033
+ }
5034
+ getActiveHiPSFITSStretch() {
5035
+ return this.astroSphere.getActiveHiPSFITSStretch();
5036
+ }
5037
+ setHiPSFITSRangeMode(rangeMode) {
5038
+ this.astroSphere.setHiPSFITSRangeMode(rangeMode);
5039
+ }
5016
5040
  getActiveHiPSFormats() {
5017
5041
  return this.astroSphere.activeHiPS?.availableFormats ?? [];
5018
5042
  }
@@ -5883,7 +5907,6 @@ var Source_js_1 = __webpack_require__(146);
5883
5907
  Object.defineProperty(exports, "Source", ({ enumerable: true, get: function () { return Source_js_1.Source; } }));
5884
5908
  var Footprint_js_1 = __webpack_require__(2475);
5885
5909
  Object.defineProperty(exports, "Footprint", ({ enumerable: true, get: function () { return Footprint_js_1.Footprint; } }));
5886
- console.log('astroviewer UMD loaded');
5887
5910
 
5888
5911
 
5889
5912
  /***/ },
@@ -11673,8 +11696,14 @@ class HealpixGrid extends AbstractSkyEntity_js_1.AbstractSkyEntity {
11673
11696
  // FIX: pass model & pMatrix to match FoVUtils TS signature
11674
11697
  const center = FoVUtils_js_1.FoVUtils.getCenterJ2000(gl.canvas, this, this._webgl, camera, pMatrix);
11675
11698
  const fovMin = (this.getMinFoV() * Math.PI) / 180 / 2;
11699
+ const visibleHealpix = Global_js_1.default.getHealpix(this._visibleorder);
11700
+ const maxPixel = visibleHealpix.getNPix();
11676
11701
  for (let p = 0; p < pixels.length; p++) {
11677
- const pixCenter = Global_js_1.default.getHealpix(this._visibleorder).pix2vec(pixels[p]);
11702
+ const pixel = pixels[p];
11703
+ if (!Number.isInteger(pixel) || pixel < 0 || pixel >= maxPixel) {
11704
+ continue;
11705
+ }
11706
+ const pixCenter = visibleHealpix.pix2vec(pixel);
11678
11707
  // const pixCenter = (global.getHealpix(global.order).pix2vec(pixels[p]) as BoundVec);
11679
11708
  const point = new Point_js_1.Point({ x: pixCenter.x, y: pixCenter.y, z: pixCenter.z }, CoordsType_js_1.CoordsType.CARTESIAN);
11680
11709
  const distance = GeomUtils_js_1.default.orthodromicDistance(center, point);
@@ -12104,6 +12133,7 @@ class AllSky {
12104
12133
  async loadFits() {
12105
12134
  try {
12106
12135
  this._fitsTile = await (0, FitsTileLoader_js_1.loadFitsTile)(this._texurl);
12136
+ this._hips.observeFITSDataRange(this._fitsTile);
12107
12137
  this.textureLoaded();
12108
12138
  this.initModelBuffer();
12109
12139
  this._textureLoaded = true;
@@ -12148,30 +12178,7 @@ class AllSky {
12148
12178
  }
12149
12179
  }
12150
12180
  getFitsDataRange() {
12151
- const hipsRange = this._hips.dataRange;
12152
- if (hipsRange.min !== undefined &&
12153
- hipsRange.max !== undefined &&
12154
- Number.isFinite(hipsRange.min) &&
12155
- Number.isFinite(hipsRange.max) &&
12156
- hipsRange.max > hipsRange.min) {
12157
- return {
12158
- min: hipsRange.min,
12159
- max: hipsRange.max,
12160
- };
12161
- }
12162
- const fits = this._fitsTile;
12163
- if (fits &&
12164
- fits.dataMin !== null &&
12165
- fits.dataMax !== null &&
12166
- Number.isFinite(fits.dataMin) &&
12167
- Number.isFinite(fits.dataMax) &&
12168
- fits.dataMax > fits.dataMin) {
12169
- return {
12170
- min: fits.dataMin,
12171
- max: fits.dataMax,
12172
- };
12173
- }
12174
- return null;
12181
+ return this._hips.getFITSDisplayRange(this._fitsTile);
12175
12182
  }
12176
12183
  initModelBuffer() {
12177
12184
  const gl = this._webgl;
@@ -12285,7 +12292,7 @@ class AllSky {
12285
12292
  console.error(`[AllSky] No display range available for FITS atlas ${this._texurl}`);
12286
12293
  return false;
12287
12294
  }
12288
- this._hipsShaderProgram.setTextureDataMode(this._hipsShaderIndex, true, range.min, range.max);
12295
+ this._hipsShaderProgram.setTextureDataMode(this._hipsShaderIndex, true, range.min, range.max, this._hips.fitsStretch.scaleFunctionIndex, this._hips.fitsStretch.scaleParam);
12289
12296
  }
12290
12297
  else {
12291
12298
  this._hipsShaderProgram.setTextureDataMode(this._hipsShaderIndex, false);
@@ -12423,6 +12430,7 @@ class AncestorTile {
12423
12430
  async loadFits() {
12424
12431
  try {
12425
12432
  this._fitsTile = await (0, FitsTileLoader_js_1.loadFitsTile)(this._texurl);
12433
+ this._hips.observeFITSDataRange(this._fitsTile);
12426
12434
  this.textureLoaded();
12427
12435
  const fits = this._fitsTile;
12428
12436
  if (!fits) {
@@ -12480,30 +12488,7 @@ class AncestorTile {
12480
12488
  }
12481
12489
  }
12482
12490
  getFitsDataRange() {
12483
- const hipsRange = this._hips.dataRange;
12484
- if (hipsRange.min !== undefined &&
12485
- hipsRange.max !== undefined &&
12486
- Number.isFinite(hipsRange.min) &&
12487
- Number.isFinite(hipsRange.max) &&
12488
- hipsRange.max > hipsRange.min) {
12489
- return {
12490
- min: hipsRange.min,
12491
- max: hipsRange.max,
12492
- };
12493
- }
12494
- const fits = this._fitsTile;
12495
- if (fits &&
12496
- fits.dataMin !== null &&
12497
- fits.dataMax !== null &&
12498
- Number.isFinite(fits.dataMin) &&
12499
- Number.isFinite(fits.dataMax) &&
12500
- fits.dataMax > fits.dataMin) {
12501
- return {
12502
- min: fits.dataMin,
12503
- max: fits.dataMax,
12504
- };
12505
- }
12506
- return null;
12491
+ return this._hips.getFITSDisplayRange(this._fitsTile);
12507
12492
  }
12508
12493
  initModelBuffer() {
12509
12494
  const gl = this._webgl;
@@ -12603,7 +12588,7 @@ class AncestorTile {
12603
12588
  console.error(`[AncestorTile] No display range available for FITS tile ${this._texurl}`);
12604
12589
  return false;
12605
12590
  }
12606
- this._hipsShaderProgram.setTextureDataMode(this._hipsShaderIndex, true, range.min, range.max);
12591
+ this._hipsShaderProgram.setTextureDataMode(this._hipsShaderIndex, true, range.min, range.max, this._hips.fitsStretch.scaleFunctionIndex, this._hips.fitsStretch.scaleParam);
12607
12592
  }
12608
12593
  else {
12609
12594
  this._hipsShaderProgram.setTextureDataMode(this._hipsShaderIndex, false);
@@ -12681,6 +12666,9 @@ var __webpack_unused_export__;
12681
12666
  __webpack_unused_export__ = ({ value: true });
12682
12667
  exports.loadFitsTile = loadFitsTile;
12683
12668
  const jsfitsio_1 = __webpack_require__(1677);
12669
+ const ROBUST_SAMPLE_COUNT = 8192;
12670
+ const ROBUST_LOW_PERCENTILE = 0.005;
12671
+ const ROBUST_HIGH_PERCENTILE = 0.995;
12684
12672
  async function loadFitsTile(url) {
12685
12673
  const fitsFile = await jsfitsio_1.FITSParser.loadFITSFile(url);
12686
12674
  if (!fitsFile?.primaryHDU) {
@@ -12710,22 +12698,57 @@ async function loadFitsTile(url) {
12710
12698
  const dataMin = getNumericHeaderValue(image.header, jsfitsio_1.FITSHeaderManager.DATAMIN);
12711
12699
  const dataMax = getNumericHeaderValue(image.header, jsfitsio_1.FITSHeaderManager.DATAMAX);
12712
12700
  const pixels = new Float32Array(data.length);
12701
+ let physicalMin = null;
12702
+ let physicalMax = null;
12703
+ const robustSamples = [];
12704
+ const sampleStep = Math.max(1, Math.floor(data.length / ROBUST_SAMPLE_COUNT));
12713
12705
  for (let i = 0; i < data.length; i++) {
12714
12706
  const rawValue = data[i];
12715
12707
  if (!Number.isFinite(rawValue) || (blank !== null && rawValue === blank)) {
12716
12708
  pixels[i] = Number.NaN;
12717
12709
  continue;
12718
12710
  }
12719
- pixels[i] = rawValue * bscale + bzero;
12711
+ const physicalValue = rawValue * bscale + bzero;
12712
+ pixels[i] = physicalValue;
12713
+ if (Number.isFinite(physicalValue)) {
12714
+ physicalMin =
12715
+ physicalMin === null ? physicalValue : Math.min(physicalMin, physicalValue);
12716
+ physicalMax =
12717
+ physicalMax === null ? physicalValue : Math.max(physicalMax, physicalValue);
12718
+ if (i % sampleStep === 0) {
12719
+ robustSamples.push(physicalValue);
12720
+ }
12721
+ }
12720
12722
  }
12723
+ const robustRange = computeRobustRange(robustSamples);
12721
12724
  return {
12722
12725
  width,
12723
12726
  height,
12724
12727
  pixels,
12725
12728
  dataMin,
12726
12729
  dataMax,
12730
+ physicalMin,
12731
+ physicalMax,
12732
+ robustMin: robustRange?.min ?? null,
12733
+ robustMax: robustRange?.max ?? null,
12727
12734
  };
12728
12735
  }
12736
+ function computeRobustRange(samples) {
12737
+ if (samples.length === 0) {
12738
+ return null;
12739
+ }
12740
+ samples.sort((a, b) => a - b);
12741
+ const min = percentile(samples, ROBUST_LOW_PERCENTILE);
12742
+ const max = percentile(samples, ROBUST_HIGH_PERCENTILE);
12743
+ if (!Number.isFinite(min) || !Number.isFinite(max) || max <= min) {
12744
+ return null;
12745
+ }
12746
+ return { min, max };
12747
+ }
12748
+ function percentile(sortedValues, p) {
12749
+ const index = Math.min(sortedValues.length - 1, Math.max(0, Math.round((sortedValues.length - 1) * p)));
12750
+ return sortedValues[index];
12751
+ }
12729
12752
  function getNumericHeaderValue(header, key) {
12730
12753
  const item = header.findById(key);
12731
12754
  if (!item) {
@@ -12931,6 +12954,21 @@ const ColorMaps_js_1 = __importDefault(__webpack_require__(619));
12931
12954
  const AncestorTile_js_1 = __importDefault(__webpack_require__(2885));
12932
12955
  const AllSky_js_1 = __importDefault(__webpack_require__(2368));
12933
12956
  const HiPSCoverage_js_1 = __webpack_require__(9912);
12957
+ const FITS_SCALE_FUNCTION_INDEX = {
12958
+ linear: 0,
12959
+ sqrt: 1,
12960
+ log: 2,
12961
+ asinh: 3,
12962
+ gamma: 4,
12963
+ };
12964
+ const DEFAULT_FITS_SCALE_PARAM = {
12965
+ linear: 1,
12966
+ sqrt: 1,
12967
+ log: 100,
12968
+ asinh: 10,
12969
+ gamma: 0.5,
12970
+ };
12971
+ const MAX_ROBUST_TILE_RANGES = 512;
12934
12972
  class HiPS extends AbstractSkyEntity_js_1.AbstractSkyEntity {
12935
12973
  _ancestorTiles;
12936
12974
  _allSkyTile;
@@ -12947,6 +12985,13 @@ class HiPS extends AbstractSkyEntity_js_1.AbstractSkyEntity {
12947
12985
  samplerIdx = 0;
12948
12986
  colorMapIdx = 0;
12949
12987
  colorMap = ColorMaps_js_1.default["native"];
12988
+ _fitsStretch = {
12989
+ scaleFunction: "linear",
12990
+ scaleFunctionIndex: FITS_SCALE_FUNCTION_INDEX.linear,
12991
+ scaleParam: DEFAULT_FITS_SCALE_PARAM.linear,
12992
+ rangeMode: "robust",
12993
+ };
12994
+ _robustFITSRanges = [];
12950
12995
  _healpixGrid;
12951
12996
  // exposed read-only helpers
12952
12997
  get maxOrder() {
@@ -13182,6 +13227,137 @@ class HiPS extends AbstractSkyEntity_js_1.AbstractSkyEntity {
13182
13227
  get dataRange() {
13183
13228
  return this._descriptor.dataRange;
13184
13229
  }
13230
+ get fitsStretch() {
13231
+ return this._fitsStretch;
13232
+ }
13233
+ setFITSScaleFunction(scaleFunction, scaleParam) {
13234
+ const scaleFunctionIndex = FITS_SCALE_FUNCTION_INDEX[scaleFunction];
13235
+ if (scaleFunctionIndex === undefined) {
13236
+ throw new Error(`Unsupported FITS scale function: ${scaleFunction}`);
13237
+ }
13238
+ const defaultParam = DEFAULT_FITS_SCALE_PARAM[scaleFunction];
13239
+ const param = scaleParam ?? defaultParam;
13240
+ if (!Number.isFinite(param) || param <= 0) {
13241
+ throw new Error(`Invalid FITS scale parameter: ${param}`);
13242
+ }
13243
+ this._fitsStretch = {
13244
+ ...this._fitsStretch,
13245
+ scaleFunction,
13246
+ scaleFunctionIndex,
13247
+ scaleParam: param,
13248
+ };
13249
+ }
13250
+ setFITSRangeMode(rangeMode) {
13251
+ if (rangeMode !== "robust" && rangeMode !== "hips" && rangeMode !== "tile") {
13252
+ throw new Error(`Unsupported FITS range mode: ${rangeMode}`);
13253
+ }
13254
+ this._fitsStretch = {
13255
+ ...this._fitsStretch,
13256
+ rangeMode,
13257
+ };
13258
+ }
13259
+ observeFITSDataRange(fits) {
13260
+ if (fits.robustMin === null ||
13261
+ fits.robustMax === null ||
13262
+ !Number.isFinite(fits.robustMin) ||
13263
+ !Number.isFinite(fits.robustMax) ||
13264
+ fits.robustMax <= fits.robustMin) {
13265
+ return;
13266
+ }
13267
+ this._robustFITSRanges.push({
13268
+ min: fits.robustMin,
13269
+ max: fits.robustMax,
13270
+ });
13271
+ if (this._robustFITSRanges.length > MAX_ROBUST_TILE_RANGES) {
13272
+ this._robustFITSRanges.shift();
13273
+ }
13274
+ }
13275
+ getFITSDisplayRange(fits) {
13276
+ const hipsRange = this.getHiPSDataRange();
13277
+ if (this._fitsStretch.rangeMode === "hips" && hipsRange) {
13278
+ return hipsRange;
13279
+ }
13280
+ if (this._fitsStretch.rangeMode === "robust") {
13281
+ const robustRange = this.getRobustFITSRange() ?? this.getTileRobustRange(fits);
13282
+ if (robustRange) {
13283
+ return robustRange;
13284
+ }
13285
+ }
13286
+ const tileRange = this.getTilePhysicalRange(fits) ?? this.getTileHeaderRange(fits);
13287
+ if (tileRange) {
13288
+ return tileRange;
13289
+ }
13290
+ return hipsRange;
13291
+ }
13292
+ getHiPSDataRange() {
13293
+ const hipsRange = this.dataRange;
13294
+ if (hipsRange.min !== undefined &&
13295
+ hipsRange.max !== undefined &&
13296
+ Number.isFinite(hipsRange.min) &&
13297
+ Number.isFinite(hipsRange.max) &&
13298
+ hipsRange.max > hipsRange.min) {
13299
+ return {
13300
+ min: hipsRange.min,
13301
+ max: hipsRange.max,
13302
+ };
13303
+ }
13304
+ return null;
13305
+ }
13306
+ getRobustFITSRange() {
13307
+ if (this._robustFITSRanges.length === 0) {
13308
+ return null;
13309
+ }
13310
+ const lows = this._robustFITSRanges.map((range) => range.min).sort((a, b) => a - b);
13311
+ const highs = this._robustFITSRanges.map((range) => range.max).sort((a, b) => a - b);
13312
+ const min = lows[Math.floor((lows.length - 1) * 0.1)];
13313
+ const max = highs[Math.ceil((highs.length - 1) * 0.9)];
13314
+ if (!Number.isFinite(min) || !Number.isFinite(max) || max <= min) {
13315
+ return null;
13316
+ }
13317
+ return { min, max };
13318
+ }
13319
+ getTileRobustRange(fits) {
13320
+ if (fits &&
13321
+ fits.robustMin !== null &&
13322
+ fits.robustMax !== null &&
13323
+ Number.isFinite(fits.robustMin) &&
13324
+ Number.isFinite(fits.robustMax) &&
13325
+ fits.robustMax > fits.robustMin) {
13326
+ return {
13327
+ min: fits.robustMin,
13328
+ max: fits.robustMax,
13329
+ };
13330
+ }
13331
+ return null;
13332
+ }
13333
+ getTilePhysicalRange(fits) {
13334
+ if (fits &&
13335
+ fits.physicalMin !== null &&
13336
+ fits.physicalMax !== null &&
13337
+ Number.isFinite(fits.physicalMin) &&
13338
+ Number.isFinite(fits.physicalMax) &&
13339
+ fits.physicalMax > fits.physicalMin) {
13340
+ return {
13341
+ min: fits.physicalMin,
13342
+ max: fits.physicalMax,
13343
+ };
13344
+ }
13345
+ return null;
13346
+ }
13347
+ getTileHeaderRange(fits) {
13348
+ if (fits &&
13349
+ fits.dataMin !== null &&
13350
+ fits.dataMax !== null &&
13351
+ Number.isFinite(fits.dataMin) &&
13352
+ Number.isFinite(fits.dataMax) &&
13353
+ fits.dataMax > fits.dataMin) {
13354
+ return {
13355
+ min: fits.dataMin,
13356
+ max: fits.dataMax,
13357
+ };
13358
+ }
13359
+ return null;
13360
+ }
13185
13361
  getCurrentHealpixOrder() {
13186
13362
  return this._visibleorder;
13187
13363
  }
@@ -13695,6 +13871,7 @@ class Tile {
13695
13871
  async loadFits() {
13696
13872
  try {
13697
13873
  this._fitsTile = await (0, FitsTileLoader_js_1.loadFitsTile)(this._texurl);
13874
+ this._hips.observeFITSDataRange(this._fitsTile);
13698
13875
  this._ready = true;
13699
13876
  }
13700
13877
  catch (error) {
@@ -13833,30 +14010,7 @@ class Tile {
13833
14010
  gl.bufferData(gl.ARRAY_BUFFER, this.vertexPosition[qidx], gl.STATIC_DRAW);
13834
14011
  }
13835
14012
  getFitsDataRange() {
13836
- const hipsRange = this._hips.dataRange;
13837
- if (hipsRange.min !== undefined &&
13838
- hipsRange.max !== undefined &&
13839
- Number.isFinite(hipsRange.min) &&
13840
- Number.isFinite(hipsRange.max) &&
13841
- hipsRange.max > hipsRange.min) {
13842
- return {
13843
- min: hipsRange.min,
13844
- max: hipsRange.max,
13845
- };
13846
- }
13847
- const fits = this._fitsTile;
13848
- if (fits &&
13849
- fits.dataMin !== null &&
13850
- fits.dataMax !== null &&
13851
- Number.isFinite(fits.dataMin) &&
13852
- Number.isFinite(fits.dataMax) &&
13853
- fits.dataMax > fits.dataMin) {
13854
- return {
13855
- min: fits.dataMin,
13856
- max: fits.dataMax,
13857
- };
13858
- }
13859
- return null;
14013
+ return this._hips.getFITSDisplayRange(this._fitsTile);
13860
14014
  }
13861
14015
  get inView() {
13862
14016
  return this._inView;
@@ -13960,7 +14114,7 @@ class Tile {
13960
14114
  console.error(`[Tile] No display range available for FITS tile ${this._texurl}`);
13961
14115
  return;
13962
14116
  }
13963
- hipsShaderProgram.setTextureDataMode(this._hipsShaderIndex, true, range.min, range.max);
14117
+ hipsShaderProgram.setTextureDataMode(this._hipsShaderIndex, true, range.min, range.max, this._hips.fitsStretch.scaleFunctionIndex, this._hips.fitsStretch.scaleParam);
13964
14118
  }
13965
14119
  else {
13966
14120
  hipsShaderProgram.setTextureDataMode(this._hipsShaderIndex, false);
@@ -16823,6 +16977,8 @@ class HiPSShaderProgram {
16823
16977
  textureMode: "uTextureMode",
16824
16978
  dataMin: "uDataMin",
16825
16979
  dataMax: "uDataMax",
16980
+ scaleFunction: "uScaleFunction",
16981
+ scaleParam: "uScaleParam",
16826
16982
  };
16827
16983
  this.gl_attributes = {
16828
16984
  vertex_pos: "aVertexPosition",
@@ -16837,6 +16993,8 @@ class HiPSShaderProgram {
16837
16993
  textureMode: new Array(8).fill(null),
16838
16994
  dataMin: new Array(8).fill(null),
16839
16995
  dataMax: new Array(8).fill(null),
16996
+ scaleFunction: new Array(8).fill(null),
16997
+ scaleParam: new Array(8).fill(null),
16840
16998
  layerOpacity: null,
16841
16999
  clorMapIdx: null,
16842
17000
  vertexPositionAttribute: -1,
@@ -16970,7 +17128,7 @@ class HiPSShaderProgram {
16970
17128
  }
16971
17129
  gl.useProgram(this.shaderProgram);
16972
17130
  }
16973
- setTextureDataMode(samplerIndex, fits, min, max) {
17131
+ setTextureDataMode(samplerIndex, fits, min, max, scaleFunction = 0, scaleParam = 1) {
16974
17132
  if (samplerIndex < 0 || samplerIndex >= 8) {
16975
17133
  throw new Error(`Invalid HiPS sampler index: ${samplerIndex}`);
16976
17134
  }
@@ -16991,12 +17149,20 @@ class HiPSShaderProgram {
16991
17149
  }
16992
17150
  const minLocation = this.locations.dataMin[samplerIndex];
16993
17151
  const maxLocation = this.locations.dataMax[samplerIndex];
17152
+ const scaleFunctionLocation = this.locations.scaleFunction[samplerIndex];
17153
+ const scaleParamLocation = this.locations.scaleParam[samplerIndex];
16994
17154
  if (minLocation) {
16995
17155
  gl.uniform1f(minLocation, min);
16996
17156
  }
16997
17157
  if (maxLocation) {
16998
17158
  gl.uniform1f(maxLocation, max);
16999
17159
  }
17160
+ if (scaleFunctionLocation) {
17161
+ gl.uniform1i(scaleFunctionLocation, scaleFunction);
17162
+ }
17163
+ if (scaleParamLocation) {
17164
+ gl.uniform1f(scaleParamLocation, scaleParam);
17165
+ }
17000
17166
  }
17001
17167
  enableShaders(pMatrix, vMatrix, mMatrix, colorMapIdx) {
17002
17168
  // const gl = global.gl
@@ -17012,6 +17178,8 @@ class HiPSShaderProgram {
17012
17178
  this.locations.textureMode[i] = gl.getUniformLocation(this.shaderProgram, `uTextureMode${i}`);
17013
17179
  this.locations.dataMin[i] = gl.getUniformLocation(this.shaderProgram, `uDataMin${i}`);
17014
17180
  this.locations.dataMax[i] = gl.getUniformLocation(this.shaderProgram, `uDataMax${i}`);
17181
+ this.locations.scaleFunction[i] = gl.getUniformLocation(this.shaderProgram, `uScaleFunction${i}`);
17182
+ this.locations.scaleParam[i] = gl.getUniformLocation(this.shaderProgram, `uScaleParam${i}`);
17015
17183
  if (this.locations.samplers[i]) {
17016
17184
  gl.uniform1i(this.locations.samplers[i], i);
17017
17185
  }
@@ -17350,7 +17518,7 @@ class ShaderManager {
17350
17518
  }
17351
17519
  static footprintVS() {
17352
17520
  return `#version 300 es
17353
- precision highp float;
17521
+ precision mediump float;
17354
17522
 
17355
17523
  layout(location = 0) in vec4 aCatPosition;
17356
17524
 
@@ -17392,7 +17560,7 @@ class ShaderManager {
17392
17560
  }
17393
17561
  static hipsNativeFS() {
17394
17562
  return `#version 300 es
17395
- precision mediump float;
17563
+ precision highp float;
17396
17564
 
17397
17565
  in vec2 vTextureCoord;
17398
17566
 
@@ -17458,7 +17626,7 @@ class ShaderManager {
17458
17626
  }
17459
17627
  static hipsGrayscaleFS() {
17460
17628
  return `#version 300 es
17461
- precision mediump float;
17629
+ precision highp float;
17462
17630
 
17463
17631
  in vec2 vTextureCoord;
17464
17632
 
@@ -17532,7 +17700,7 @@ class ShaderManager {
17532
17700
  }
17533
17701
  static hipsColorMapFS() {
17534
17702
  return `#version 300 es
17535
- precision mediump float;
17703
+ precision highp float;
17536
17704
 
17537
17705
  uniform float uLayerOpacity;
17538
17706
 
@@ -17637,6 +17805,36 @@ class ShaderManager {
17637
17805
  uniform int uTextureMode${index};
17638
17806
  uniform float uDataMin${index};
17639
17807
  uniform float uDataMax${index};
17808
+ uniform int uScaleFunction${index};
17809
+ uniform float uScaleParam${index};
17810
+
17811
+ float asinhCompat${index}(float value) {
17812
+ return log(value + sqrt(value * value + 1.0));
17813
+ }
17814
+
17815
+ float applyFitsScale${index}(float normalized) {
17816
+ float x = clamp(normalized, 0.0, 1.0);
17817
+
17818
+ if (uScaleFunction${index} == 1) {
17819
+ return sqrt(x);
17820
+ }
17821
+
17822
+ if (uScaleFunction${index} == 2) {
17823
+ float strength = max(uScaleParam${index}, 1.0);
17824
+ return log(1.0 + strength * x) / log(1.0 + strength);
17825
+ }
17826
+
17827
+ if (uScaleFunction${index} == 3) {
17828
+ float strength = max(uScaleParam${index}, 1.0);
17829
+ return asinhCompat${index}(strength * x) / asinhCompat${index}(strength);
17830
+ }
17831
+
17832
+ if (uScaleFunction${index} == 4) {
17833
+ return pow(x, max(uScaleParam${index}, 0.0001));
17834
+ }
17835
+
17836
+ return x;
17837
+ }
17640
17838
 
17641
17839
  vec4 sampleHiPSTexture${index}(vec2 uv) {
17642
17840
  vec4 color = texture(uSampler${index}, uv);
@@ -17660,10 +17858,12 @@ class ShaderManager {
17660
17858
  1.0
17661
17859
  );
17662
17860
 
17861
+ float scaled = applyFitsScale${index}(normalized);
17862
+
17663
17863
  return vec4(
17664
- normalized,
17665
- normalized,
17666
- normalized,
17864
+ scaled,
17865
+ scaled,
17866
+ scaled,
17667
17867
  1.0
17668
17868
  );
17669
17869
  }