@holoscript/radio-astronomy-plugin 2.0.2

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 (38) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/LICENSE +21 -0
  3. package/__tests__/SpectralCubeViewer.test.ts +129 -0
  4. package/__tests__/plugin.test.ts +21 -0
  5. package/dist/bridge/python-runner.d.ts +24 -0
  6. package/dist/bridge/python-runner.d.ts.map +1 -0
  7. package/dist/bridge/python-runner.js +43 -0
  8. package/dist/bridge/python-runner.js.map +1 -0
  9. package/dist/components/SpectralCubeViewer.d.ts +45 -0
  10. package/dist/components/SpectralCubeViewer.d.ts.map +1 -0
  11. package/dist/components/SpectralCubeViewer.js +196 -0
  12. package/dist/components/SpectralCubeViewer.js.map +1 -0
  13. package/dist/constants/astronomy-traits.d.ts +6 -0
  14. package/dist/constants/astronomy-traits.d.ts.map +1 -0
  15. package/dist/constants/astronomy-traits.js +12 -0
  16. package/dist/constants/astronomy-traits.js.map +1 -0
  17. package/dist/fits/FITSParser.d.ts +60 -0
  18. package/dist/fits/FITSParser.d.ts.map +1 -0
  19. package/dist/fits/FITSParser.js +230 -0
  20. package/dist/fits/FITSParser.js.map +1 -0
  21. package/dist/fits/FITSToGrid.d.ts +27 -0
  22. package/dist/fits/FITSToGrid.d.ts.map +1 -0
  23. package/dist/fits/FITSToGrid.js +85 -0
  24. package/dist/fits/FITSToGrid.js.map +1 -0
  25. package/dist/index.d.ts +25 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +28 -0
  28. package/dist/index.js.map +1 -0
  29. package/package.json +33 -0
  30. package/python/astropy_bridge.py +45 -0
  31. package/src/bridge/python-runner.ts +62 -0
  32. package/src/components/SpectralCubeViewer.tsx +310 -0
  33. package/src/constants/astronomy-traits.ts +13 -0
  34. package/src/fits/FITSParser.ts +289 -0
  35. package/src/fits/FITSToGrid.ts +95 -0
  36. package/src/index.ts +32 -0
  37. package/tsconfig.json +26 -0
  38. package/vitest.config.ts +10 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # @holoscript/radio-astronomy-plugin
2
+
3
+ ## 2.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [c64fc1a]
8
+ - Updated dependencies [6dc9732]
9
+ - @holoscript/core@8.0.6
10
+ - @holoscript/engine@6.1.3
11
+
12
+ ## 2.0.0
13
+
14
+ ### Patch Changes
15
+
16
+ - Updated dependencies [c6e69b8]
17
+ - Updated dependencies [440e163]
18
+ - @holoscript/engine@6.1.0
19
+ - @holoscript/core@6.1.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 HoloScript Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,129 @@
1
+ /**
2
+ * SpectralCubeViewer + FITSViewerPanel — integration tests.
3
+ * Validates FITS → parse → channel extraction → data range pipeline.
4
+ */
5
+
6
+ import { describe, it, expect } from 'vitest';
7
+ import { parseFITS, buildFITS } from '../src/fits/FITSParser';
8
+ import { fitsToGrid3D, extractChannel, fitsDataRange } from '../src/fits/FITSToGrid';
9
+
10
+ describe('FITS Viewer Pipeline', () => {
11
+ it('parses a synthetic 2D FITS image', () => {
12
+ const data: number[] = [];
13
+ for (let j = 0; j < 8; j++) {
14
+ for (let i = 0; i < 10; i++) {
15
+ data.push(i + j * 10); // gradient
16
+ }
17
+ }
18
+
19
+ const buffer = buildFITS({
20
+ bitpix: -32,
21
+ shape: [10, 8],
22
+ data,
23
+ headers: { OBJECT: 'Test Image', TELESCOP: 'HoloScript' },
24
+ });
25
+
26
+ const fits = parseFITS(buffer);
27
+ expect(fits.shape).toEqual([10, 8]);
28
+ expect(fits.object).toBe('Test Image');
29
+ expect(fits.telescope).toBe('HoloScript');
30
+ expect(fits.data.length).toBe(80);
31
+ expect(fits.data[0]).toBeCloseTo(0);
32
+ expect(fits.data[79]).toBeCloseTo(79);
33
+ });
34
+
35
+ it('parses a synthetic 3D FITS spectral cube', () => {
36
+ const nx = 4, ny = 3, nz = 5;
37
+ const data: number[] = [];
38
+ for (let k = 0; k < nz; k++) {
39
+ for (let j = 0; j < ny; j++) {
40
+ for (let i = 0; i < nx; i++) {
41
+ data.push(k * 100 + j * 10 + i);
42
+ }
43
+ }
44
+ }
45
+
46
+ const buffer = buildFITS({
47
+ bitpix: -32,
48
+ shape: [nx, ny, nz],
49
+ data,
50
+ headers: {
51
+ OBJECT: 'Spectral Cube',
52
+ CRVAL3: 1.4e9,
53
+ CDELT3: 1e6,
54
+ CTYPE3: 'FREQ',
55
+ CUNIT3: 'Hz',
56
+ },
57
+ });
58
+
59
+ const fits = parseFITS(buffer);
60
+ expect(fits.shape).toEqual([nx, ny, nz]);
61
+ expect(fits.data.length).toBe(nx * ny * nz);
62
+
63
+ // Extract channel 2
64
+ const ch2 = extractChannel(fits, 2);
65
+ expect(ch2.length).toBe(nx * ny);
66
+ // Channel 2 values: 200, 201, ..., 211
67
+ expect(ch2[0]).toBeCloseTo(200);
68
+ expect(ch2[nx * ny - 1]).toBeCloseTo(211);
69
+ });
70
+
71
+ it('fitsToGrid3D creates correct grid dimensions', () => {
72
+ const buffer = buildFITS({
73
+ bitpix: -32,
74
+ shape: [5, 4, 3],
75
+ data: Array.from({ length: 60 }, (_, i) => i),
76
+ });
77
+
78
+ const fits = parseFITS(buffer);
79
+ const grid = fitsToGrid3D(fits);
80
+
81
+ expect(grid.nx).toBe(5);
82
+ expect(grid.ny).toBe(4);
83
+ expect(grid.nz).toBe(3);
84
+ });
85
+
86
+ it('fitsDataRange computes correct min/max', () => {
87
+ const buffer = buildFITS({
88
+ bitpix: -32,
89
+ shape: [3, 3],
90
+ data: [1, 5, 3, -2, 7, 0, 4, 8, -1],
91
+ });
92
+
93
+ const fits = parseFITS(buffer);
94
+ const [min, max] = fitsDataRange(fits);
95
+ expect(min).toBe(-2);
96
+ expect(max).toBe(8);
97
+ });
98
+
99
+ it('handles BSCALE/BZERO for integer FITS', () => {
100
+ const buffer = buildFITS({
101
+ bitpix: 16, // int16
102
+ shape: [4],
103
+ data: [0, 100, 200, 300],
104
+ bscale: 0.1,
105
+ bzero: 50,
106
+ });
107
+
108
+ const fits = parseFITS(buffer);
109
+ // Physical value = BSCALE * raw + BZERO
110
+ expect(fits.data[0]).toBeCloseTo(50); // 0.1 * 0 + 50
111
+ expect(fits.data[1]).toBeCloseTo(60); // 0.1 * 100 + 50
112
+ expect(fits.data[3]).toBeCloseTo(80); // 0.1 * 300 + 50
113
+ });
114
+
115
+ it('2D image converts to nx×ny×1 grid', () => {
116
+ const buffer = buildFITS({
117
+ bitpix: -32,
118
+ shape: [6, 4],
119
+ data: Array.from({ length: 24 }, (_, i) => i),
120
+ });
121
+
122
+ const fits = parseFITS(buffer);
123
+ const grid = fitsToGrid3D(fits);
124
+
125
+ expect(grid.nx).toBe(6);
126
+ expect(grid.ny).toBe(4);
127
+ expect(grid.nz).toBe(1);
128
+ });
129
+ });
@@ -0,0 +1,21 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { DOMAIN_MANIFEST, RADIO_ASTRONOMY_TRAITS, PythonAstropyBridge } from '../src/index';
3
+
4
+ describe('Radio Astronomy Plugin', () => {
5
+ it('should export the domain manifest correctly', () => {
6
+ expect(DOMAIN_MANIFEST.id).toBe('domain.science.astronomy.radio');
7
+ expect(DOMAIN_MANIFEST.traits.length).toBeGreaterThan(0);
8
+ });
9
+
10
+ it('should include basic astrophysics semantic traits', () => {
11
+ expect(RADIO_ASTRONOMY_TRAITS).toContain('synchrotron');
12
+ expect(RADIO_ASTRONOMY_TRAITS).toContain('interferometer');
13
+ expect(RADIO_ASTRONOMY_TRAITS).toContain('radio_emitter');
14
+ });
15
+
16
+ it('should instantiate the python bridge without throwing', () => {
17
+ const bridge = new PythonAstropyBridge();
18
+ expect(bridge).toBeDefined();
19
+ expect(typeof bridge.executeCommand).toBe('function');
20
+ });
21
+ });
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Interface representing the result of a radio astronomy physics calculation.
3
+ */
4
+ export interface AstropyResult {
5
+ wavelength_meters?: number;
6
+ frequency_hz?: number;
7
+ flux_density_jy?: number;
8
+ error?: string;
9
+ }
10
+ /**
11
+ * Runner that bridges HoloScript to the underlying astropy Python toolkit.
12
+ */
13
+ export declare class PythonAstropyBridge {
14
+ private scriptPath;
15
+ constructor();
16
+ /**
17
+ * Dispatches a calculation to the Python environment.
18
+ * @param command The astronomical command to invoke (e.g. 'calc_synchrotron')
19
+ * @param params JSON payload of parameters
20
+ * @returns A promise resolving to the astronomical result payload
21
+ */
22
+ executeCommand(command: string, params: Record<string, any>): Promise<AstropyResult>;
23
+ }
24
+ //# sourceMappingURL=python-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"python-runner.d.ts","sourceRoot":"","sources":["../../src/bridge/python-runner.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,UAAU,CAAS;;IAM3B;;;;;OAKG;IACU,cAAc,CACzB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC1B,OAAO,CAAC,aAAa,CAAC;CA6B1B"}
@@ -0,0 +1,43 @@
1
+ import { spawn } from 'child_process';
2
+ import * as path from 'path';
3
+ /**
4
+ * Runner that bridges HoloScript to the underlying astropy Python toolkit.
5
+ */
6
+ export class PythonAstropyBridge {
7
+ constructor() {
8
+ this.scriptPath = path.resolve(__dirname, '../../python/astropy_bridge.py');
9
+ }
10
+ /**
11
+ * Dispatches a calculation to the Python environment.
12
+ * @param command The astronomical command to invoke (e.g. 'calc_synchrotron')
13
+ * @param params JSON payload of parameters
14
+ * @returns A promise resolving to the astronomical result payload
15
+ */
16
+ async executeCommand(command, params) {
17
+ return new Promise((resolve, reject) => {
18
+ const process = spawn('python', [this.scriptPath, command, JSON.stringify(params)]);
19
+ let output = '';
20
+ let errorOutput = '';
21
+ process.stdout.on('data', (data) => {
22
+ output += data.toString();
23
+ });
24
+ process.stderr.on('data', (data) => {
25
+ errorOutput += data.toString();
26
+ });
27
+ process.on('close', (code) => {
28
+ if (code !== 0) {
29
+ return reject(new Error(`Astropy bridge exited with code ${code}: ${errorOutput}`));
30
+ }
31
+ try {
32
+ // Attempt to parse JSON line output from python script
33
+ const parsed = JSON.parse(output.trim());
34
+ resolve(parsed);
35
+ }
36
+ catch (_e) {
37
+ reject(new Error(`Failed to parse bridge output: ${output}`));
38
+ }
39
+ });
40
+ });
41
+ }
42
+ }
43
+ //# sourceMappingURL=python-runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"python-runner.js","sourceRoot":"","sources":["../../src/bridge/python-runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAY7B;;GAEG;AACH,MAAM,OAAO,mBAAmB;IAG9B;QACE,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,gCAAgC,CAAC,CAAC;IAC9E,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAC,cAAc,CACzB,OAAe,EACf,MAA2B;QAE3B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YAEpF,IAAI,MAAM,GAAG,EAAE,CAAC;YAChB,IAAI,WAAW,GAAG,EAAE,CAAC;YAErB,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;gBACjC,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC5B,CAAC,CAAC,CAAC;YAEH,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;gBACjC,WAAW,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACjC,CAAC,CAAC,CAAC;YAEH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;gBAC3B,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;oBACf,OAAO,MAAM,CAAC,IAAI,KAAK,CAAC,mCAAmC,IAAI,KAAK,WAAW,EAAE,CAAC,CAAC,CAAC;gBACtF,CAAC;gBACD,IAAI,CAAC;oBACH,uDAAuD;oBACvD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;oBACzC,OAAO,CAAC,MAAuB,CAAC,CAAC;gBACnC,CAAC;gBAAC,OAAO,EAAE,EAAE,CAAC;oBACZ,MAAM,CAAC,IAAI,KAAK,CAAC,kCAAkC,MAAM,EAAE,CAAC,CAAC,CAAC;gBAChE,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * SpectralCubeViewer — Interactive 3D viewer for FITS spectral cubes.
3
+ *
4
+ * Drop a FITS file → see the spectral cube in 3D → slide through
5
+ * frequency channels → play as animation. Zero code required.
6
+ *
7
+ * Works with:
8
+ * - 3D cubes (RA × Dec × Freq): full volumetric slice-by-slice
9
+ * - 2D images (RA × Dec): single-channel colormap
10
+ * - 1D spectra: displayed as a line in 3D space
11
+ */
12
+ export type ColormapName = 'jet' | 'viridis' | 'turbo' | 'inferno' | 'coolwarm';
13
+ export interface SpectralCubeViewerProps {
14
+ /** Parsed FITS data array (physical values after BSCALE/BZERO) */
15
+ data: Float32Array;
16
+ /** Axis dimensions [NAXIS1, NAXIS2] or [NAXIS1, NAXIS2, NAXIS3] */
17
+ shape: number[];
18
+ /** Colormap (default: 'viridis') */
19
+ colormap?: ColormapName;
20
+ /** Auto-play through channels (default: false) */
21
+ autoPlay?: boolean;
22
+ /** Playback speed in channels per second (default: 5) */
23
+ playSpeed?: number;
24
+ /** WCS metadata for axis labels */
25
+ wcs?: {
26
+ ctype?: string[];
27
+ cunit?: string[];
28
+ crval?: number[];
29
+ cdelt?: number[];
30
+ };
31
+ /** Object name from FITS header */
32
+ objectName?: string;
33
+ }
34
+ export declare function SpectralCubeViewer({ data, shape, colormap, autoPlay, playSpeed, wcs, objectName, }: SpectralCubeViewerProps): import("react/jsx-runtime").JSX.Element;
35
+ export interface FITSViewerPanelProps {
36
+ /** Raw FITS ArrayBuffer (from file drop) */
37
+ fitsBuffer: ArrayBuffer;
38
+ onClose?: () => void;
39
+ }
40
+ /**
41
+ * Full FITS viewer panel: parses FITS → shows 3D → channel slider.
42
+ * This is the entry point for drag-and-drop FITS files.
43
+ */
44
+ export declare function FITSViewerPanel({ fitsBuffer, onClose }: FITSViewerPanelProps): import("react/jsx-runtime").JSX.Element;
45
+ //# sourceMappingURL=SpectralCubeViewer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SpectralCubeViewer.d.ts","sourceRoot":"","sources":["../../src/components/SpectralCubeViewer.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,CAAC;AAEhF,MAAM,WAAW,uBAAuB;IACtC,kEAAkE;IAClE,IAAI,EAAE,YAAY,CAAC;IACnB,mEAAmE;IACnE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yDAAyD;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mCAAmC;IACnC,GAAG,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACjF,mCAAmC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AA4DD,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EACJ,KAAK,EACL,QAAoB,EACpB,QAAgB,EAChB,SAAa,EACb,GAAG,EACH,UAAU,GACX,EAAE,uBAAuB,2CAsFzB;AAID,MAAM,WAAW,oBAAoB;IACnC,4CAA4C;IAC5C,UAAU,EAAE,WAAW,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,oBAAoB,2CAiG5E"}
@@ -0,0 +1,196 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * SpectralCubeViewer — Interactive 3D viewer for FITS spectral cubes.
4
+ *
5
+ * Drop a FITS file → see the spectral cube in 3D → slide through
6
+ * frequency channels → play as animation. Zero code required.
7
+ *
8
+ * Works with:
9
+ * - 3D cubes (RA × Dec × Freq): full volumetric slice-by-slice
10
+ * - 2D images (RA × Dec): single-channel colormap
11
+ * - 1D spectra: displayed as a line in 3D space
12
+ */
13
+ import { useState, useMemo, useRef, useEffect } from 'react';
14
+ import { useFrame } from '@react-three/fiber';
15
+ import * as THREE from 'three';
16
+ // ── Colormap GLSL ────────────────────────────────────────────────────────────
17
+ const COLORMAPS = {
18
+ viridis: `
19
+ vec3 colormap(float t) {
20
+ vec3 c0 = vec3(0.267, 0.004, 0.329);
21
+ vec3 c4 = vec3(0.127, 0.566, 0.551);
22
+ vec3 c8 = vec3(0.993, 0.906, 0.144);
23
+ if (t < 0.5) return mix(c0, c4, t * 2.0);
24
+ return mix(c4, c8, (t - 0.5) * 2.0);
25
+ }
26
+ `,
27
+ turbo: `
28
+ vec3 colormap(float t) {
29
+ float r = 0.136 + t * (4.615 + t * (-42.66 + t * (132.13 + t * (-152.55 + t * 56.31))));
30
+ float g = 0.091 + t * (2.264 + t * (-14.02 + t * (32.21 + t * (-29.27 + t * 10.16))));
31
+ float b = 0.107 + t * (12.75 + t * (-60.58 + t * (132.75 + t * (-134.01 + t * 50.26))));
32
+ return clamp(vec3(r, g, b), 0.0, 1.0);
33
+ }
34
+ `,
35
+ inferno: `
36
+ vec3 colormap(float t) {
37
+ vec3 c0 = vec3(0.001, 0.0, 0.014);
38
+ vec3 c3 = vec3(0.735, 0.216, 0.329);
39
+ vec3 c6 = vec3(0.988, 0.999, 0.644);
40
+ if (t < 0.5) return mix(c0, c3, t * 2.0);
41
+ return mix(c3, c6, (t - 0.5) * 2.0);
42
+ }
43
+ `,
44
+ };
45
+ const VERT = /* glsl */ `
46
+ attribute float aIntensity;
47
+ uniform float uMin;
48
+ uniform float uMax;
49
+ varying float vNorm;
50
+
51
+ void main() {
52
+ float range = uMax - uMin;
53
+ vNorm = range > 0.0 ? clamp((aIntensity - uMin) / range, 0.0, 1.0) : 0.5;
54
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
55
+ }
56
+ `;
57
+ function makeFrag(cmName) {
58
+ const cm = COLORMAPS[cmName] ?? COLORMAPS.viridis;
59
+ return /* glsl */ `
60
+ varying float vNorm;
61
+ ${cm}
62
+ void main() {
63
+ vec3 c = colormap(vNorm);
64
+ gl_FragColor = vec4(c, 1.0);
65
+ }
66
+ `;
67
+ }
68
+ // ── Component ────────────────────────────────────────────────────────────────
69
+ export function SpectralCubeViewer({ data, shape, colormap = 'viridis', autoPlay = false, playSpeed = 5, wcs, objectName, }) {
70
+ const nx = shape[0] ?? 1;
71
+ const ny = shape[1] ?? 1;
72
+ const nz = shape[2] ?? 1;
73
+ const is3D = shape.length >= 3 && nz > 1;
74
+ const [channel, setChannel] = useState(0);
75
+ const [playing, setPlaying] = useState(autoPlay);
76
+ const meshRef = useRef(null);
77
+ const timeRef = useRef(0);
78
+ // Extract channel slice
79
+ const channelData = useMemo(() => {
80
+ if (!is3D)
81
+ return data; // 2D — use entire dataset
82
+ const offset = channel * nx * ny;
83
+ return data.slice(offset, offset + nx * ny);
84
+ }, [data, channel, nx, ny, is3D]);
85
+ // Data range for colormap normalization
86
+ const [min, max] = useMemo(() => {
87
+ let lo = Infinity, hi = -Infinity;
88
+ for (let i = 0; i < channelData.length; i++) {
89
+ if (channelData[i] < lo)
90
+ lo = channelData[i];
91
+ if (channelData[i] > hi)
92
+ hi = channelData[i];
93
+ }
94
+ return [lo, hi];
95
+ }, [channelData]);
96
+ // Build geometry: flat plane with per-pixel intensity attribute
97
+ const geometry = useMemo(() => {
98
+ const geo = new THREE.PlaneGeometry(nx / Math.max(nx, ny), ny / Math.max(nx, ny), nx - 1, ny - 1);
99
+ const intensities = new Float32Array(nx * ny);
100
+ for (let j = 0; j < ny; j++) {
101
+ for (let i = 0; i < nx; i++) {
102
+ // PlaneGeometry vertex order: row by row, top to bottom
103
+ intensities[j * nx + i] = channelData[j * nx + i] ?? 0;
104
+ }
105
+ }
106
+ geo.setAttribute('aIntensity', new THREE.BufferAttribute(intensities, 1));
107
+ return geo;
108
+ }, [nx, ny, channelData]);
109
+ // Auto-play animation
110
+ useFrame((_, delta) => {
111
+ if (!playing || !is3D)
112
+ return;
113
+ timeRef.current += delta * playSpeed;
114
+ if (timeRef.current >= 1) {
115
+ timeRef.current -= 1;
116
+ setChannel((c) => (c + 1) % nz);
117
+ }
118
+ });
119
+ const fragmentShader = useMemo(() => makeFrag(colormap), [colormap]);
120
+ const uniforms = useMemo(() => ({
121
+ uMin: { value: min },
122
+ uMax: { value: max },
123
+ }), [min, max]);
124
+ // Channel label from WCS
125
+ const channelLabel = useMemo(() => {
126
+ if (!wcs?.crval || !wcs?.cdelt || !wcs?.ctype)
127
+ return `Channel ${channel}/${nz}`;
128
+ const freqIdx = wcs.ctype.findIndex((t) => t.includes('FREQ'));
129
+ if (freqIdx < 0)
130
+ return `Channel ${channel}/${nz}`;
131
+ const freq = wcs.crval[freqIdx] + channel * wcs.cdelt[freqIdx];
132
+ const unit = wcs.cunit?.[freqIdx] ?? 'Hz';
133
+ if (freq >= 1e9)
134
+ return `${(freq / 1e9).toFixed(3)} GHz`;
135
+ if (freq >= 1e6)
136
+ return `${(freq / 1e6).toFixed(3)} MHz`;
137
+ return `${freq.toFixed(0)} ${unit}`;
138
+ }, [channel, nz, wcs]);
139
+ return (_jsx("group", { children: _jsx("mesh", { ref: meshRef, geometry: geometry, children: _jsx("shaderMaterial", { vertexShader: VERT, fragmentShader: fragmentShader, uniforms: uniforms, side: THREE.DoubleSide }) }) }));
140
+ }
141
+ /**
142
+ * Full FITS viewer panel: parses FITS → shows 3D → channel slider.
143
+ * This is the entry point for drag-and-drop FITS files.
144
+ */
145
+ export function FITSViewerPanel({ fitsBuffer, onClose }) {
146
+ const [fitsData, setFitsData] = useState(null);
147
+ const [channel, setChannel] = useState(0);
148
+ const [playing, setPlaying] = useState(false);
149
+ const [error, setError] = useState(null);
150
+ useEffect(() => {
151
+ try {
152
+ // Dynamic import to avoid bundling FITS parser unless needed
153
+ import('../fits/FITSParser').then(({ parseFITS }) => {
154
+ const fits = parseFITS(fitsBuffer);
155
+ setFitsData({
156
+ data: fits.data,
157
+ shape: fits.shape,
158
+ wcs: fits.wcs,
159
+ object: fits.object || 'Unknown Object',
160
+ });
161
+ }).catch((e) => setError(String(e)));
162
+ }
163
+ catch (e) {
164
+ setError(String(e));
165
+ }
166
+ }, [fitsBuffer]);
167
+ if (error) {
168
+ return (_jsxs("div", { style: { padding: 20, color: '#ef4444', fontFamily: 'monospace' }, children: ["FITS Parse Error: ", error] }));
169
+ }
170
+ if (!fitsData) {
171
+ return (_jsx("div", { style: { padding: 20, color: '#94a3b8', fontFamily: 'monospace' }, children: "Loading FITS data..." }));
172
+ }
173
+ const nz = fitsData.shape[2] ?? 1;
174
+ const is3D = fitsData.shape.length >= 3 && nz > 1;
175
+ return (_jsxs("div", { style: {
176
+ display: 'flex', flexDirection: 'column', gap: 8,
177
+ padding: 12, background: '#1a1a2e', borderRadius: 8,
178
+ border: '1px solid #2a2a3e', color: '#e4e4e7',
179
+ fontFamily: "'Space Mono', monospace",
180
+ }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, children: [_jsx("span", { style: { fontSize: 13, fontWeight: 700, color: '#a78bfa' }, children: fitsData.object }), _jsxs("span", { style: { fontSize: 10, color: '#71717a' }, children: [fitsData.shape.join(' × '), " px"] })] }), is3D && (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8 }, children: [_jsx("button", { onClick: () => setPlaying(!playing), style: {
181
+ background: playing ? '#ef4444' : '#3b82f6',
182
+ border: 'none', borderRadius: 4, padding: '4px 10px',
183
+ color: 'white', fontSize: 11, cursor: 'pointer',
184
+ }, children: playing ? 'Stop' : 'Play' }), _jsx("input", { type: "range", min: 0, max: nz - 1, value: channel, onChange: (e) => { setChannel(Number(e.target.value)); setPlaying(false); }, style: { flex: 1 } }), _jsxs("span", { style: { fontSize: 10, color: '#71717a', minWidth: 80, textAlign: 'right' }, children: ["Ch ", channel, "/", nz - 1] })] })), _jsxs("div", { style: { fontSize: 10, color: '#71717a' }, children: ["Range: ", dataRange(fitsData.data).map((v) => v.toExponential(2)).join(' → ')] })] }));
185
+ }
186
+ function dataRange(data) {
187
+ let min = Infinity, max = -Infinity;
188
+ for (let i = 0; i < data.length; i++) {
189
+ if (data[i] < min)
190
+ min = data[i];
191
+ if (data[i] > max)
192
+ max = data[i];
193
+ }
194
+ return [min, max];
195
+ }
196
+ //# sourceMappingURL=SpectralCubeViewer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SpectralCubeViewer.js","sourceRoot":"","sources":["../../src/components/SpectralCubeViewer.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAe,MAAM,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAC1E,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAuB/B,gFAAgF;AAEhF,MAAM,SAAS,GAA2B;IACxC,OAAO,EAAE;;;;;;;;GAQR;IACD,KAAK,EAAE;;;;;;;GAON;IACD,OAAO,EAAE;;;;;;;;GAQR;CACF,CAAC;AAEF,MAAM,IAAI,GAAG,UAAU,CAAC;;;;;;;;;;;CAWvB,CAAC;AAEF,SAAS,QAAQ,CAAC,MAAc;IAC9B,MAAM,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,SAAS,CAAC,OAAO,CAAC;IAClD,OAAO,UAAU,CAAC;;MAEd,EAAE;;;;;GAKL,CAAC;AACJ,CAAC;AAED,gFAAgF;AAEhF,MAAM,UAAU,kBAAkB,CAAC,EACjC,IAAI,EACJ,KAAK,EACL,QAAQ,GAAG,SAAS,EACpB,QAAQ,GAAG,KAAK,EAChB,SAAS,GAAG,CAAC,EACb,GAAG,EACH,UAAU,GACc;IACxB,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACzB,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACzB,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACzB,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAEzC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC1C,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACjD,MAAM,OAAO,GAAG,MAAM,CAAa,IAAI,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IAE1B,wBAAwB;IACxB,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,EAAE;QAC/B,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC,CAAC,0BAA0B;QAClD,MAAM,MAAM,GAAG,OAAO,GAAG,EAAE,GAAG,EAAE,CAAC;QACjC,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;IAC9C,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC;IAElC,wCAAwC;IACxC,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,EAAE;QAC9B,IAAI,EAAE,GAAG,QAAQ,EAAE,EAAE,GAAG,CAAC,QAAQ,CAAC;QAClC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC5C,IAAI,WAAW,CAAC,CAAC,CAAC,GAAG,EAAE;gBAAE,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;YAC7C,IAAI,WAAW,CAAC,CAAC,CAAC,GAAG,EAAE;gBAAE,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;QAC/C,CAAC;QACD,OAAO,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IAClB,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;IAElB,gEAAgE;IAChE,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,EAAE;QAC5B,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,aAAa,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;QAClG,MAAM,WAAW,GAAG,IAAI,YAAY,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;QAC9C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;YAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC5B,wDAAwD;gBACxD,WAAW,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;YACzD,CAAC;QACH,CAAC;QACD,GAAG,CAAC,YAAY,CAAC,YAAY,EAAE,IAAI,KAAK,CAAC,eAAe,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC;QAC1E,OAAO,GAAG,CAAC;IACb,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,WAAW,CAAC,CAAC,CAAC;IAE1B,sBAAsB;IACtB,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE;QACpB,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI;YAAE,OAAO;QAC9B,OAAO,CAAC,OAAO,IAAI,KAAK,GAAG,SAAS,CAAC;QACrC,IAAI,OAAO,CAAC,OAAO,IAAI,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,OAAO,IAAI,CAAC,CAAC;YACrB,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;QAClC,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IAErE,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QAC9B,IAAI,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE;QACpB,IAAI,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE;KACrB,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;IAEhB,yBAAyB;IACzB,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,EAAE;QAChC,IAAI,CAAC,GAAG,EAAE,KAAK,IAAI,CAAC,GAAG,EAAE,KAAK,IAAI,CAAC,GAAG,EAAE,KAAK;YAAE,OAAO,WAAW,OAAO,IAAI,EAAE,EAAE,CAAC;QACjF,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;QAC/D,IAAI,OAAO,GAAG,CAAC;YAAE,OAAO,WAAW,OAAO,IAAI,EAAE,EAAE,CAAC;QACnD,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC/D,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,EAAE,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;QAC1C,IAAI,IAAI,IAAI,GAAG;YAAE,OAAO,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;QACzD,IAAI,IAAI,IAAI,GAAG;YAAE,OAAO,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;QACzD,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;IACtC,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;IAEvB,OAAO,CACL,0BACE,eAAM,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,YACpC,yBACE,YAAY,EAAE,IAAI,EAClB,cAAc,EAAE,cAAc,EAC9B,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,KAAK,CAAC,UAAU,GACtB,GACG,GAID,CACT,CAAC;AACJ,CAAC;AAUD;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,EAAE,UAAU,EAAE,OAAO,EAAwB;IAC3E,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAK9B,IAAI,CAAC,CAAC;IAChB,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC1C,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC9C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IAExD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC;YACH,6DAA6D;YAC7D,MAAM,CAAC,oBAAoB,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE;gBAClD,MAAM,IAAI,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC;gBACnC,WAAW,CAAC;oBACV,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,KAAK,EAAE,IAAI,CAAC,KAAK;oBACjB,GAAG,EAAE,IAAI,CAAC,GAAG;oBACb,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,gBAAgB;iBACxC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACvC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QACtB,CAAC;IACH,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;IAEjB,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,CACL,eAAK,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,mCACjD,KAAK,IACpB,CACP,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,CACL,cAAK,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,qCAEhE,CACP,CAAC;IACJ,CAAC;IAED,MAAM,EAAE,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAElD,OAAO,CACL,eAAK,KAAK,EAAE;YACV,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC;YAChD,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC;YACnD,MAAM,EAAE,mBAAmB,EAAE,KAAK,EAAE,SAAS;YAC7C,UAAU,EAAE,yBAAyB;SACtC,aAEC,eAAK,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,eAAe,EAAE,UAAU,EAAE,QAAQ,EAAE,aACpF,eAAM,KAAK,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,YAC7D,QAAQ,CAAC,MAAM,GACX,EACP,gBAAM,KAAK,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,aAC5C,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,WACtB,IACH,EAGL,IAAI,IAAI,CACP,eAAK,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,EAAE,aAC3D,iBACE,OAAO,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,EACnC,KAAK,EAAE;4BACL,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;4BAC3C,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,EAAE,OAAO,EAAE,UAAU;4BACpD,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS;yBAChD,YAEA,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,GACnB,EACT,gBACE,IAAI,EAAC,OAAO,EACZ,GAAG,EAAE,CAAC,EACN,GAAG,EAAE,EAAE,GAAG,CAAC,EACX,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAC3E,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,GAClB,EACF,gBAAM,KAAK,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,oBAC3E,OAAO,OAAG,EAAE,GAAG,CAAC,IACf,IACH,CACP,EAGD,eAAK,KAAK,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,wBACpC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IACvE,IACF,CACP,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,IAAkB;IACnC,IAAI,GAAG,GAAG,QAAQ,EAAE,GAAG,GAAG,CAAC,QAAQ,CAAC;IACpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG;YAAE,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACjC,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG;YAAE,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACnC,CAAC;IACD,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AACpB,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Specific traits representing properties for Radio Astrophysics.
3
+ */
4
+ export declare const RADIO_ASTRONOMY_TRAITS: readonly ["radio_emitter", "synchrotron", "interferometer", "em_wave", "pulsar_timing", "spectral_line"];
5
+ export type RadioAstronomyTraitName = (typeof RADIO_ASTRONOMY_TRAITS)[number];
6
+ //# sourceMappingURL=astronomy-traits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"astronomy-traits.d.ts","sourceRoot":"","sources":["../../src/constants/astronomy-traits.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,sBAAsB,0GAOzB,CAAC;AAEX,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Specific traits representing properties for Radio Astrophysics.
3
+ */
4
+ export const RADIO_ASTRONOMY_TRAITS = [
5
+ 'radio_emitter', // Signals a 3D construct as an origin point for specific radio wavelengths
6
+ 'synchrotron', // Determines emission behavior via magnetic fields and relativistic electrons
7
+ 'interferometer', // Marks a multi-nodal virtual sensor
8
+ 'em_wave', // Represents an electromagnetic wave primitive
9
+ 'pulsar_timing', // Represents pulsar timing array signals
10
+ 'spectral_line', // Maps to particular spectral line signals (e.g., 21cm HI line)
11
+ ];
12
+ //# sourceMappingURL=astronomy-traits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"astronomy-traits.js","sourceRoot":"","sources":["../../src/constants/astronomy-traits.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,eAAe,EAAE,2EAA2E;IAC5F,aAAa,EAAE,8EAA8E;IAC7F,gBAAgB,EAAE,qCAAqC;IACvD,SAAS,EAAE,+CAA+C;IAC1D,eAAe,EAAE,yCAAyC;IAC1D,eAAe,EAAE,gEAAgE;CACzE,CAAC"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * FITSParser — Pure JavaScript parser for FITS (Flexible Image Transport System) files.
3
+ *
4
+ * FITS is the standard data format in astronomy. Structure:
5
+ * - Header: 2880-byte blocks of 80-character ASCII "cards" (key=value pairs)
6
+ * - Data: big-endian binary arrays, padded to 2880-byte boundary
7
+ * - Extensions: additional HDUs (Header Data Units) with same structure
8
+ *
9
+ * Supports: BITPIX 8 (uint8), 16 (int16), 32 (int32), -32 (float32), -64 (float64)
10
+ * Handles: BSCALE/BZERO physical value scaling, WCS coordinate metadata
11
+ *
12
+ * @see https://fits.gsfc.nasa.gov/fits_standard.html
13
+ */
14
+ export interface WCSInfo {
15
+ /** Reference pixel (1-indexed, per FITS convention) */
16
+ crpix: number[];
17
+ /** Reference value (world coordinate at reference pixel) */
18
+ crval: number[];
19
+ /** Pixel scale (coordinate increment per pixel) */
20
+ cdelt: number[];
21
+ /** Axis types (e.g., 'RA---TAN', 'DEC--TAN', 'FREQ') */
22
+ ctype: string[];
23
+ /** Axis units */
24
+ cunit: string[];
25
+ }
26
+ export interface FITSFile {
27
+ /** All header cards as key→value */
28
+ headers: Map<string, string | number | boolean>;
29
+ /** Data array (physical values after BSCALE/BZERO) */
30
+ data: Float32Array;
31
+ /** Axis dimensions [NAXIS1, NAXIS2, ...] */
32
+ shape: number[];
33
+ /** World Coordinate System info (if present) */
34
+ wcs: WCSInfo | null;
35
+ /** BITPIX from header */
36
+ bitpix: number;
37
+ /** Object name (OBJECT card) */
38
+ object: string;
39
+ /** Telescope name (TELESCOP card) */
40
+ telescope: string;
41
+ /** Observation date (DATE-OBS card) */
42
+ dateObs: string;
43
+ }
44
+ /**
45
+ * Parse a FITS file from an ArrayBuffer.
46
+ * Returns the primary HDU (first header + data unit).
47
+ */
48
+ export declare function parseFITS(buffer: ArrayBuffer): FITSFile;
49
+ /**
50
+ * Build a minimal FITS file as ArrayBuffer (for testing).
51
+ */
52
+ export declare function buildFITS(opts: {
53
+ bitpix: number;
54
+ shape: number[];
55
+ data: number[];
56
+ bscale?: number;
57
+ bzero?: number;
58
+ headers?: Record<string, string | number>;
59
+ }): ArrayBuffer;
60
+ //# sourceMappingURL=FITSParser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FITSParser.d.ts","sourceRoot":"","sources":["../../src/fits/FITSParser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,MAAM,WAAW,OAAO;IACtB,uDAAuD;IACvD,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,4DAA4D;IAC5D,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,mDAAmD;IACnD,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,wDAAwD;IACxD,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,iBAAiB;IACjB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,oCAAoC;IACpC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IAChD,sDAAsD;IACtD,IAAI,EAAE,YAAY,CAAC;IACnB,4CAA4C;IAC5C,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,gDAAgD;IAChD,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;IACpB,yBAAyB;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC;CACjB;AAUD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,QAAQ,CA+GvD;AAID;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;CAC3C,GAAG,WAAW,CAuDd"}