@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.
- package/CHANGELOG.md +19 -0
- package/LICENSE +21 -0
- package/__tests__/SpectralCubeViewer.test.ts +129 -0
- package/__tests__/plugin.test.ts +21 -0
- package/dist/bridge/python-runner.d.ts +24 -0
- package/dist/bridge/python-runner.d.ts.map +1 -0
- package/dist/bridge/python-runner.js +43 -0
- package/dist/bridge/python-runner.js.map +1 -0
- package/dist/components/SpectralCubeViewer.d.ts +45 -0
- package/dist/components/SpectralCubeViewer.d.ts.map +1 -0
- package/dist/components/SpectralCubeViewer.js +196 -0
- package/dist/components/SpectralCubeViewer.js.map +1 -0
- package/dist/constants/astronomy-traits.d.ts +6 -0
- package/dist/constants/astronomy-traits.d.ts.map +1 -0
- package/dist/constants/astronomy-traits.js +12 -0
- package/dist/constants/astronomy-traits.js.map +1 -0
- package/dist/fits/FITSParser.d.ts +60 -0
- package/dist/fits/FITSParser.d.ts.map +1 -0
- package/dist/fits/FITSParser.js +230 -0
- package/dist/fits/FITSParser.js.map +1 -0
- package/dist/fits/FITSToGrid.d.ts +27 -0
- package/dist/fits/FITSToGrid.d.ts.map +1 -0
- package/dist/fits/FITSToGrid.js +85 -0
- package/dist/fits/FITSToGrid.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/package.json +33 -0
- package/python/astropy_bridge.py +45 -0
- package/src/bridge/python-runner.ts +62 -0
- package/src/components/SpectralCubeViewer.tsx +310 -0
- package/src/constants/astronomy-traits.ts +13 -0
- package/src/fits/FITSParser.ts +289 -0
- package/src/fits/FITSToGrid.ts +95 -0
- package/src/index.ts +32 -0
- package/tsconfig.json +26 -0
- package/vitest.config.ts +10 -0
|
@@ -0,0 +1,230 @@
|
|
|
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
|
+
// ── Constants ────────────────────────────────────────────────────────────────
|
|
15
|
+
const BLOCK_SIZE = 2880;
|
|
16
|
+
const CARD_SIZE = 80;
|
|
17
|
+
const CARDS_PER_BLOCK = BLOCK_SIZE / CARD_SIZE; // 36
|
|
18
|
+
// ── Parser ───────────────────────────────────────────────────────────────────
|
|
19
|
+
/**
|
|
20
|
+
* Parse a FITS file from an ArrayBuffer.
|
|
21
|
+
* Returns the primary HDU (first header + data unit).
|
|
22
|
+
*/
|
|
23
|
+
export function parseFITS(buffer) {
|
|
24
|
+
const bytes = new Uint8Array(buffer);
|
|
25
|
+
const view = new DataView(buffer);
|
|
26
|
+
// ── Parse Header ─────────────────────────────────────────────────
|
|
27
|
+
const headers = new Map();
|
|
28
|
+
let headerEnd = 0;
|
|
29
|
+
outer: for (let block = 0; block * BLOCK_SIZE < buffer.byteLength; block++) {
|
|
30
|
+
for (let card = 0; card < CARDS_PER_BLOCK; card++) {
|
|
31
|
+
const offset = block * BLOCK_SIZE + card * CARD_SIZE;
|
|
32
|
+
if (offset + CARD_SIZE > buffer.byteLength)
|
|
33
|
+
break outer;
|
|
34
|
+
const cardStr = decodeASCII(bytes, offset, CARD_SIZE);
|
|
35
|
+
const keyword = cardStr.substring(0, 8).trim();
|
|
36
|
+
if (keyword === 'END') {
|
|
37
|
+
headerEnd = (block + 1) * BLOCK_SIZE; // data starts at next block boundary
|
|
38
|
+
break outer;
|
|
39
|
+
}
|
|
40
|
+
if (cardStr[8] === '=' && cardStr[9] === ' ') {
|
|
41
|
+
const valueStr = cardStr.substring(10).split('/')[0].trim();
|
|
42
|
+
headers.set(keyword, parseCardValue(valueStr));
|
|
43
|
+
}
|
|
44
|
+
else if (keyword === 'COMMENT' || keyword === 'HISTORY') {
|
|
45
|
+
// Skip comment/history cards
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (headerEnd === 0) {
|
|
50
|
+
throw new Error('FITS: No END card found in header');
|
|
51
|
+
}
|
|
52
|
+
// ── Extract Critical Keywords ────────────────────────────────────
|
|
53
|
+
const bitpix = getNum(headers, 'BITPIX');
|
|
54
|
+
const naxis = getNum(headers, 'NAXIS');
|
|
55
|
+
const shape = [];
|
|
56
|
+
for (let i = 1; i <= naxis; i++) {
|
|
57
|
+
shape.push(getNum(headers, `NAXIS${i}`));
|
|
58
|
+
}
|
|
59
|
+
const bscale = getNumOr(headers, 'BSCALE', 1.0);
|
|
60
|
+
const bzero = getNumOr(headers, 'BZERO', 0.0);
|
|
61
|
+
// ── Parse Data ───────────────────────────────────────────────────
|
|
62
|
+
const totalPixels = shape.reduce((a, b) => a * b, 1);
|
|
63
|
+
const bytesPerPixel = Math.abs(bitpix) / 8;
|
|
64
|
+
const dataOffset = headerEnd;
|
|
65
|
+
if (dataOffset + totalPixels * bytesPerPixel > buffer.byteLength) {
|
|
66
|
+
throw new Error(`FITS: Data section extends beyond buffer (need ${dataOffset + totalPixels * bytesPerPixel}, have ${buffer.byteLength})`);
|
|
67
|
+
}
|
|
68
|
+
const data = new Float32Array(totalPixels);
|
|
69
|
+
for (let i = 0; i < totalPixels; i++) {
|
|
70
|
+
const off = dataOffset + i * bytesPerPixel;
|
|
71
|
+
let raw;
|
|
72
|
+
switch (bitpix) {
|
|
73
|
+
case 8:
|
|
74
|
+
raw = bytes[off];
|
|
75
|
+
break;
|
|
76
|
+
case 16:
|
|
77
|
+
raw = view.getInt16(off, false); // big-endian
|
|
78
|
+
break;
|
|
79
|
+
case 32:
|
|
80
|
+
raw = view.getInt32(off, false);
|
|
81
|
+
break;
|
|
82
|
+
case -32:
|
|
83
|
+
raw = view.getFloat32(off, false);
|
|
84
|
+
break;
|
|
85
|
+
case -64:
|
|
86
|
+
raw = view.getFloat64(off, false);
|
|
87
|
+
break;
|
|
88
|
+
default:
|
|
89
|
+
throw new Error(`FITS: Unsupported BITPIX ${bitpix}`);
|
|
90
|
+
}
|
|
91
|
+
// Apply physical value transformation
|
|
92
|
+
data[i] = bscale * raw + bzero;
|
|
93
|
+
}
|
|
94
|
+
// ── Extract WCS ──────────────────────────────────────────────────
|
|
95
|
+
let wcs = null;
|
|
96
|
+
if (headers.has('CRVAL1')) {
|
|
97
|
+
wcs = {
|
|
98
|
+
crpix: [], crval: [], cdelt: [], ctype: [], cunit: [],
|
|
99
|
+
};
|
|
100
|
+
for (let i = 1; i <= naxis; i++) {
|
|
101
|
+
wcs.crpix.push(getNumOr(headers, `CRPIX${i}`, 1));
|
|
102
|
+
wcs.crval.push(getNumOr(headers, `CRVAL${i}`, 0));
|
|
103
|
+
wcs.cdelt.push(getNumOr(headers, `CDELT${i}`, 1));
|
|
104
|
+
wcs.ctype.push(getStrOr(headers, `CTYPE${i}`, ''));
|
|
105
|
+
wcs.cunit.push(getStrOr(headers, `CUNIT${i}`, ''));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
headers,
|
|
110
|
+
data,
|
|
111
|
+
shape,
|
|
112
|
+
wcs,
|
|
113
|
+
bitpix,
|
|
114
|
+
object: getStrOr(headers, 'OBJECT', ''),
|
|
115
|
+
telescope: getStrOr(headers, 'TELESCOP', ''),
|
|
116
|
+
dateObs: getStrOr(headers, 'DATE-OBS', ''),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
// ── FITS Builder (for tests) ─────────────────────────────────────────────────
|
|
120
|
+
/**
|
|
121
|
+
* Build a minimal FITS file as ArrayBuffer (for testing).
|
|
122
|
+
*/
|
|
123
|
+
export function buildFITS(opts) {
|
|
124
|
+
const cards = [];
|
|
125
|
+
cards.push(fmtCard('SIMPLE', true));
|
|
126
|
+
cards.push(fmtCard('BITPIX', opts.bitpix));
|
|
127
|
+
cards.push(fmtCard('NAXIS', opts.shape.length));
|
|
128
|
+
for (let i = 0; i < opts.shape.length; i++) {
|
|
129
|
+
cards.push(fmtCard(`NAXIS${i + 1}`, opts.shape[i]));
|
|
130
|
+
}
|
|
131
|
+
if (opts.bscale !== undefined)
|
|
132
|
+
cards.push(fmtCard('BSCALE', opts.bscale));
|
|
133
|
+
if (opts.bzero !== undefined)
|
|
134
|
+
cards.push(fmtCard('BZERO', opts.bzero));
|
|
135
|
+
if (opts.headers) {
|
|
136
|
+
for (const [key, val] of Object.entries(opts.headers)) {
|
|
137
|
+
cards.push(fmtCard(key, val));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
cards.push('END'.padEnd(CARD_SIZE));
|
|
141
|
+
// Pad header to block boundary
|
|
142
|
+
while (cards.length % CARDS_PER_BLOCK !== 0) {
|
|
143
|
+
cards.push(' '.repeat(CARD_SIZE));
|
|
144
|
+
}
|
|
145
|
+
const headerBytes = new Uint8Array(cards.length * CARD_SIZE);
|
|
146
|
+
for (let i = 0; i < cards.length; i++) {
|
|
147
|
+
for (let j = 0; j < CARD_SIZE; j++) {
|
|
148
|
+
headerBytes[i * CARD_SIZE + j] = cards[i].charCodeAt(j);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Write data
|
|
152
|
+
const bytesPerPixel = Math.abs(opts.bitpix) / 8;
|
|
153
|
+
const dataSize = opts.data.length * bytesPerPixel;
|
|
154
|
+
const paddedDataSize = Math.ceil(dataSize / BLOCK_SIZE) * BLOCK_SIZE;
|
|
155
|
+
const dataBytes = new ArrayBuffer(paddedDataSize);
|
|
156
|
+
const dataView = new DataView(dataBytes);
|
|
157
|
+
for (let i = 0; i < opts.data.length; i++) {
|
|
158
|
+
const off = i * bytesPerPixel;
|
|
159
|
+
switch (opts.bitpix) {
|
|
160
|
+
case 8:
|
|
161
|
+
new Uint8Array(dataBytes)[off] = opts.data[i];
|
|
162
|
+
break;
|
|
163
|
+
case 16:
|
|
164
|
+
dataView.setInt16(off, opts.data[i], false);
|
|
165
|
+
break;
|
|
166
|
+
case 32:
|
|
167
|
+
dataView.setInt32(off, opts.data[i], false);
|
|
168
|
+
break;
|
|
169
|
+
case -32:
|
|
170
|
+
dataView.setFloat32(off, opts.data[i], false);
|
|
171
|
+
break;
|
|
172
|
+
case -64:
|
|
173
|
+
dataView.setFloat64(off, opts.data[i], false);
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Combine
|
|
178
|
+
const result = new Uint8Array(headerBytes.length + paddedDataSize);
|
|
179
|
+
result.set(headerBytes);
|
|
180
|
+
result.set(new Uint8Array(dataBytes), headerBytes.length);
|
|
181
|
+
return result.buffer;
|
|
182
|
+
}
|
|
183
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
184
|
+
function decodeASCII(bytes, offset, length) {
|
|
185
|
+
let s = '';
|
|
186
|
+
for (let i = 0; i < length; i++) {
|
|
187
|
+
s += String.fromCharCode(bytes[offset + i]);
|
|
188
|
+
}
|
|
189
|
+
return s;
|
|
190
|
+
}
|
|
191
|
+
function parseCardValue(s) {
|
|
192
|
+
if (s === 'T')
|
|
193
|
+
return true;
|
|
194
|
+
if (s === 'F')
|
|
195
|
+
return false;
|
|
196
|
+
if (s.startsWith("'"))
|
|
197
|
+
return s.replace(/^'|'$/g, '').trim();
|
|
198
|
+
const n = Number(s);
|
|
199
|
+
return Number.isNaN(n) ? s : n;
|
|
200
|
+
}
|
|
201
|
+
function getNum(headers, key) {
|
|
202
|
+
const v = headers.get(key);
|
|
203
|
+
if (typeof v !== 'number')
|
|
204
|
+
throw new Error(`FITS: Missing or non-numeric header ${key}`);
|
|
205
|
+
return v;
|
|
206
|
+
}
|
|
207
|
+
function getNumOr(headers, key, def) {
|
|
208
|
+
const v = headers.get(key);
|
|
209
|
+
return typeof v === 'number' ? v : def;
|
|
210
|
+
}
|
|
211
|
+
function getStrOr(headers, key, def) {
|
|
212
|
+
const v = headers.get(key);
|
|
213
|
+
return typeof v === 'string' ? v : def;
|
|
214
|
+
}
|
|
215
|
+
function fmtCard(keyword, value) {
|
|
216
|
+
const kw = keyword.padEnd(8);
|
|
217
|
+
let valStr;
|
|
218
|
+
if (typeof value === 'boolean') {
|
|
219
|
+
valStr = value ? 'T' : 'F';
|
|
220
|
+
valStr = valStr.padStart(20);
|
|
221
|
+
}
|
|
222
|
+
else if (typeof value === 'number') {
|
|
223
|
+
valStr = String(value).padStart(20);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
valStr = `'${value}'`.padEnd(20);
|
|
227
|
+
}
|
|
228
|
+
return `${kw}= ${valStr}`.padEnd(CARD_SIZE);
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=FITSParser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FITSParser.js","sourceRoot":"","sources":["../../src/fits/FITSParser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FITSToGrid — Convert parsed FITS data to HoloScript grid structures.
|
|
3
|
+
*
|
|
4
|
+
* Maps FITS spectral cubes (RA × Dec × Freq) and 2D images to
|
|
5
|
+
* RegularGrid3D for visualization via ScalarFieldOverlay or SimResultsMesh.
|
|
6
|
+
*/
|
|
7
|
+
import { RegularGrid3D } from '@holoscript/engine/simulation';
|
|
8
|
+
import type { FITSFile } from './FITSParser';
|
|
9
|
+
/**
|
|
10
|
+
* Convert a parsed FITS file to a RegularGrid3D.
|
|
11
|
+
*
|
|
12
|
+
* - 3D cubes (NAXIS=3): maps directly to grid
|
|
13
|
+
* - 2D images (NAXIS=2): creates a 1-deep grid (nx × ny × 1)
|
|
14
|
+
* - 1D spectra (NAXIS=1): creates a 1×1×n grid
|
|
15
|
+
*/
|
|
16
|
+
export declare function fitsToGrid3D(fits: FITSFile): RegularGrid3D;
|
|
17
|
+
/**
|
|
18
|
+
* Extract a single frequency channel (z-slice) from a 3D FITS cube.
|
|
19
|
+
* Returns a 2D Float32Array (nx × ny) for use as a ScalarFieldOverlay.
|
|
20
|
+
*/
|
|
21
|
+
export declare function extractChannel(fits: FITSFile, channel: number): Float32Array;
|
|
22
|
+
/**
|
|
23
|
+
* Get data range (min/max) for the FITS data.
|
|
24
|
+
* Useful for colormap normalization.
|
|
25
|
+
*/
|
|
26
|
+
export declare function fitsDataRange(fits: FITSFile): [number, number];
|
|
27
|
+
//# sourceMappingURL=FITSToGrid.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FITSToGrid.d.ts","sourceRoot":"","sources":["../../src/fits/FITSToGrid.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE7C;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,aAAa,CAmC1D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,YAAY,CAsB5E;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAQ9D"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FITSToGrid — Convert parsed FITS data to HoloScript grid structures.
|
|
3
|
+
*
|
|
4
|
+
* Maps FITS spectral cubes (RA × Dec × Freq) and 2D images to
|
|
5
|
+
* RegularGrid3D for visualization via ScalarFieldOverlay or SimResultsMesh.
|
|
6
|
+
*/
|
|
7
|
+
import { RegularGrid3D } from '@holoscript/engine/simulation';
|
|
8
|
+
/**
|
|
9
|
+
* Convert a parsed FITS file to a RegularGrid3D.
|
|
10
|
+
*
|
|
11
|
+
* - 3D cubes (NAXIS=3): maps directly to grid
|
|
12
|
+
* - 2D images (NAXIS=2): creates a 1-deep grid (nx × ny × 1)
|
|
13
|
+
* - 1D spectra (NAXIS=1): creates a 1×1×n grid
|
|
14
|
+
*/
|
|
15
|
+
export function fitsToGrid3D(fits) {
|
|
16
|
+
const shape = fits.shape;
|
|
17
|
+
let nx, ny, nz;
|
|
18
|
+
if (shape.length >= 3) {
|
|
19
|
+
nx = shape[0];
|
|
20
|
+
ny = shape[1];
|
|
21
|
+
nz = shape[2];
|
|
22
|
+
}
|
|
23
|
+
else if (shape.length === 2) {
|
|
24
|
+
nx = shape[0];
|
|
25
|
+
ny = shape[1];
|
|
26
|
+
nz = 1;
|
|
27
|
+
}
|
|
28
|
+
else if (shape.length === 1) {
|
|
29
|
+
nx = shape[0];
|
|
30
|
+
ny = 1;
|
|
31
|
+
nz = 1;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
throw new Error(`FITS: Cannot convert ${shape.length}D data to grid`);
|
|
35
|
+
}
|
|
36
|
+
const grid = new RegularGrid3D([nx, ny, nz], [nx, ny, nz]);
|
|
37
|
+
// FITS stores data in FORTRAN order (column-major: NAXIS1 varies fastest)
|
|
38
|
+
// RegularGrid3D uses row-major (x varies fastest in our convention)
|
|
39
|
+
// Since NAXIS1 = x and it varies fastest in both, direct copy works
|
|
40
|
+
const data = fits.data;
|
|
41
|
+
const gridData = grid.data;
|
|
42
|
+
const len = Math.min(data.length, gridData.length);
|
|
43
|
+
for (let i = 0; i < len; i++) {
|
|
44
|
+
gridData[i] = data[i];
|
|
45
|
+
}
|
|
46
|
+
return grid;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Extract a single frequency channel (z-slice) from a 3D FITS cube.
|
|
50
|
+
* Returns a 2D Float32Array (nx × ny) for use as a ScalarFieldOverlay.
|
|
51
|
+
*/
|
|
52
|
+
export function extractChannel(fits, channel) {
|
|
53
|
+
if (fits.shape.length < 3) {
|
|
54
|
+
// 2D image — return the whole thing
|
|
55
|
+
return new Float32Array(fits.data);
|
|
56
|
+
}
|
|
57
|
+
const nx = fits.shape[0];
|
|
58
|
+
const ny = fits.shape[1];
|
|
59
|
+
const nz = fits.shape[2];
|
|
60
|
+
if (channel < 0 || channel >= nz) {
|
|
61
|
+
throw new Error(`Channel ${channel} out of range [0, ${nz - 1}]`);
|
|
62
|
+
}
|
|
63
|
+
const slice = new Float32Array(nx * ny);
|
|
64
|
+
const offset = channel * nx * ny;
|
|
65
|
+
for (let i = 0; i < nx * ny; i++) {
|
|
66
|
+
slice[i] = fits.data[offset + i];
|
|
67
|
+
}
|
|
68
|
+
return slice;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Get data range (min/max) for the FITS data.
|
|
72
|
+
* Useful for colormap normalization.
|
|
73
|
+
*/
|
|
74
|
+
export function fitsDataRange(fits) {
|
|
75
|
+
let min = Infinity, max = -Infinity;
|
|
76
|
+
for (let i = 0; i < fits.data.length; i++) {
|
|
77
|
+
const v = fits.data[i];
|
|
78
|
+
if (v < min)
|
|
79
|
+
min = v;
|
|
80
|
+
if (v > max)
|
|
81
|
+
max = v;
|
|
82
|
+
}
|
|
83
|
+
return [min, max];
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=FITSToGrid.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FITSToGrid.js","sourceRoot":"","sources":["../../src/fits/FITSToGrid.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { RADIO_ASTRONOMY_TRAITS, RadioAstronomyTraitName } from './constants/astronomy-traits';
|
|
2
|
+
import { PythonAstropyBridge, AstropyResult } from './bridge/python-runner';
|
|
3
|
+
/**
|
|
4
|
+
* @holoscript/radio-astronomy-plugin
|
|
5
|
+
*
|
|
6
|
+
* Domain plugin bridging Radio Astrophysics simulation concepts into the HoloScript Universal pipeline.
|
|
7
|
+
* Extends standard traits without bloating core. Provides an astropy python bridge for logic evaluation.
|
|
8
|
+
*/
|
|
9
|
+
export { RADIO_ASTRONOMY_TRAITS, type RadioAstronomyTraitName };
|
|
10
|
+
export { PythonAstropyBridge, type AstropyResult };
|
|
11
|
+
export { parseFITS, buildFITS, type FITSFile, type WCSInfo } from './fits/FITSParser';
|
|
12
|
+
export { fitsToGrid3D, extractChannel, fitsDataRange } from './fits/FITSToGrid';
|
|
13
|
+
export { SpectralCubeViewer, FITSViewerPanel, type SpectralCubeViewerProps, type FITSViewerPanelProps } from './components/SpectralCubeViewer';
|
|
14
|
+
/**
|
|
15
|
+
* Metadata exposing domain capabilities to the Studio / Schema Mapper.
|
|
16
|
+
*/
|
|
17
|
+
export declare const DOMAIN_MANIFEST: {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
version: string;
|
|
21
|
+
description: string;
|
|
22
|
+
keywords: string[];
|
|
23
|
+
traits: readonly ["radio_emitter", "synchrotron", "interferometer", "em_wave", "pulsar_timing", "spectral_line"];
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAC/F,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAE5E;;;;;GAKG;AAGH,OAAO,EAAE,sBAAsB,EAAE,KAAK,uBAAuB,EAAE,CAAC;AAGhE,OAAO,EAAE,mBAAmB,EAAE,KAAK,aAAa,EAAE,CAAC;AAGnD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAE,KAAK,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACtF,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,KAAK,uBAAuB,EAAE,KAAK,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAE/I;;GAEG;AACH,eAAO,MAAM,eAAe;;;;;;;CAO3B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { RADIO_ASTRONOMY_TRAITS } from './constants/astronomy-traits';
|
|
2
|
+
import { PythonAstropyBridge } from './bridge/python-runner';
|
|
3
|
+
/**
|
|
4
|
+
* @holoscript/radio-astronomy-plugin
|
|
5
|
+
*
|
|
6
|
+
* Domain plugin bridging Radio Astrophysics simulation concepts into the HoloScript Universal pipeline.
|
|
7
|
+
* Extends standard traits without bloating core. Provides an astropy python bridge for logic evaluation.
|
|
8
|
+
*/
|
|
9
|
+
// Export vocabulary
|
|
10
|
+
export { RADIO_ASTRONOMY_TRAITS };
|
|
11
|
+
// Export Bridges
|
|
12
|
+
export { PythonAstropyBridge };
|
|
13
|
+
// Export FITS parsing and visualization
|
|
14
|
+
export { parseFITS, buildFITS } from './fits/FITSParser';
|
|
15
|
+
export { fitsToGrid3D, extractChannel, fitsDataRange } from './fits/FITSToGrid';
|
|
16
|
+
export { SpectralCubeViewer, FITSViewerPanel } from './components/SpectralCubeViewer';
|
|
17
|
+
/**
|
|
18
|
+
* Metadata exposing domain capabilities to the Studio / Schema Mapper.
|
|
19
|
+
*/
|
|
20
|
+
export const DOMAIN_MANIFEST = {
|
|
21
|
+
id: 'domain.science.astronomy.radio',
|
|
22
|
+
name: 'Radio Astronomy Plugin',
|
|
23
|
+
version: '1.0.0',
|
|
24
|
+
description: 'Extends HoloScript spatial environments with radio astrophysics primitives.',
|
|
25
|
+
keywords: ['interferometer', 'radio emitting', 'synchrotron radiation', 'pulsar'],
|
|
26
|
+
traits: RADIO_ASTRONOMY_TRAITS,
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAA2B,MAAM,8BAA8B,CAAC;AAC/F,OAAO,EAAE,mBAAmB,EAAiB,MAAM,wBAAwB,CAAC;AAE5E;;;;;GAKG;AAEH,oBAAoB;AACpB,OAAO,EAAE,sBAAsB,EAAgC,CAAC;AAEhE,iBAAiB;AACjB,OAAO,EAAE,mBAAmB,EAAsB,CAAC;AAEnD,wCAAwC;AACxC,OAAO,EAAE,SAAS,EAAE,SAAS,EAA+B,MAAM,mBAAmB,CAAC;AACtF,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAA2D,MAAM,iCAAiC,CAAC;AAE/I;;GAEG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,EAAE,EAAE,gCAAgC;IACpC,IAAI,EAAE,wBAAwB;IAC9B,OAAO,EAAE,OAAO;IAChB,WAAW,EAAE,6EAA6E;IAC1F,QAAQ,EAAE,CAAC,gBAAgB,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,QAAQ,CAAC;IACjF,MAAM,EAAE,sBAAsB;CAC/B,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@holoscript/radio-astronomy-plugin",
|
|
3
|
+
"version": "2.0.2",
|
|
4
|
+
"description": "Radio Astrophysics plugin for HoloScript spatial environments. Adds primitives for interferometers, pulsar analysis, and synchrotron emissions.",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"types": "src/index.ts",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"holoscript",
|
|
9
|
+
"plugin",
|
|
10
|
+
"astrophysics",
|
|
11
|
+
"radio-astronomy",
|
|
12
|
+
"science"
|
|
13
|
+
],
|
|
14
|
+
"author": "brianonbased-dev",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@holoscript/engine": "^6.1.3"
|
|
18
|
+
},
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"@holoscript/core": "8.0.6"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"typescript": "^5.0.0",
|
|
24
|
+
"vitest": "^4.1.5"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"lint": "eslint src --ext .ts",
|
|
30
|
+
"clean": "rimraf dist",
|
|
31
|
+
"test:coverage": "vitest run --coverage"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import json
|
|
3
|
+
import math
|
|
4
|
+
|
|
5
|
+
def calculate_synchrotron(params):
|
|
6
|
+
"""
|
|
7
|
+
Mock calculation for synchrotron radiation flux based on magnetic field strength.
|
|
8
|
+
In a fully operational deployment, this would use `astropy.units` and `astropy.constants`.
|
|
9
|
+
"""
|
|
10
|
+
b_field = params.get('magnetic_field_gauss', 1e-4) # default interstellar field
|
|
11
|
+
frequency = params.get('frequency_hz', 1.4e9) # default 1.4 GHz (HI line proximity)
|
|
12
|
+
|
|
13
|
+
# Placeholder formula for demonstration (proportionality mock)
|
|
14
|
+
# Spectral index alpha ~ 0.75
|
|
15
|
+
flux = (b_field ** 2) * (frequency ** -0.75) * 1e12
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
"flux_density_jy": flux,
|
|
19
|
+
"frequency_hz": frequency,
|
|
20
|
+
"wavelength_meters": 3e8 / frequency
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
def main():
|
|
24
|
+
if len(sys.argv) < 3:
|
|
25
|
+
print(json.dumps({"error": "Insufficient arguments. Usage: script.py <command> <json_params>"}))
|
|
26
|
+
sys.exit(1)
|
|
27
|
+
|
|
28
|
+
command = sys.argv[1]
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
params = json.loads(sys.argv[2])
|
|
32
|
+
except Exception as e:
|
|
33
|
+
print(json.dumps({"error": f"Invalid JSON payload: {str(e)}"}))
|
|
34
|
+
sys.exit(1)
|
|
35
|
+
|
|
36
|
+
if command == "calc_synchrotron":
|
|
37
|
+
result = calculate_synchrotron(params)
|
|
38
|
+
else:
|
|
39
|
+
result = {"error": f"Unknown command: {command}"}
|
|
40
|
+
|
|
41
|
+
# Print pure JSON to stdout for the JS bridge to parse
|
|
42
|
+
print(json.dumps(result))
|
|
43
|
+
|
|
44
|
+
if __name__ == "__main__":
|
|
45
|
+
main()
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Interface representing the result of a radio astronomy physics calculation.
|
|
6
|
+
*/
|
|
7
|
+
export interface AstropyResult {
|
|
8
|
+
wavelength_meters?: number;
|
|
9
|
+
frequency_hz?: number;
|
|
10
|
+
flux_density_jy?: number;
|
|
11
|
+
error?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Runner that bridges HoloScript to the underlying astropy Python toolkit.
|
|
16
|
+
*/
|
|
17
|
+
export class PythonAstropyBridge {
|
|
18
|
+
private scriptPath: string;
|
|
19
|
+
|
|
20
|
+
constructor() {
|
|
21
|
+
this.scriptPath = path.resolve(__dirname, '../../python/astropy_bridge.py');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Dispatches a calculation to the Python environment.
|
|
26
|
+
* @param command The astronomical command to invoke (e.g. 'calc_synchrotron')
|
|
27
|
+
* @param params JSON payload of parameters
|
|
28
|
+
* @returns A promise resolving to the astronomical result payload
|
|
29
|
+
*/
|
|
30
|
+
public async executeCommand(
|
|
31
|
+
command: string,
|
|
32
|
+
params: Record<string, any>
|
|
33
|
+
): Promise<AstropyResult> {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const process = spawn('python', [this.scriptPath, command, JSON.stringify(params)]);
|
|
36
|
+
|
|
37
|
+
let output = '';
|
|
38
|
+
let errorOutput = '';
|
|
39
|
+
|
|
40
|
+
process.stdout.on('data', (data) => {
|
|
41
|
+
output += data.toString();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
process.stderr.on('data', (data) => {
|
|
45
|
+
errorOutput += data.toString();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
process.on('close', (code) => {
|
|
49
|
+
if (code !== 0) {
|
|
50
|
+
return reject(new Error(`Astropy bridge exited with code ${code}: ${errorOutput}`));
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
// Attempt to parse JSON line output from python script
|
|
54
|
+
const parsed = JSON.parse(output.trim());
|
|
55
|
+
resolve(parsed as AstropyResult);
|
|
56
|
+
} catch (_e) {
|
|
57
|
+
reject(new Error(`Failed to parse bridge output: ${output}`));
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|