@holoscript/radio-astronomy-plugin 2.0.3 → 2.0.4
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/LICENSE +21 -0
- package/README.md +14 -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 +212 -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 +234 -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 +24 -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 +17 -7
- package/plugin.manifest.json +9 -0
- package/CHANGELOG.md +0 -19
- package/__tests__/SpectralCubeViewer.test.ts +0 -131
- package/__tests__/plugin.test.ts +0 -21
- package/python/astropy_bridge.py +0 -45
- package/src/bridge/python-runner.ts +0 -62
- package/src/components/SpectralCubeViewer.tsx +0 -335
- package/src/constants/astronomy-traits.ts +0 -13
- package/src/fits/FITSParser.ts +0 -312
- package/src/fits/FITSToGrid.ts +0 -96
- package/src/index.ts +0 -37
- package/tsconfig.json +0 -26
- package/vitest.config.ts +0 -10
|
@@ -0,0 +1,234 @@
|
|
|
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: [],
|
|
99
|
+
crval: [],
|
|
100
|
+
cdelt: [],
|
|
101
|
+
ctype: [],
|
|
102
|
+
cunit: [],
|
|
103
|
+
};
|
|
104
|
+
for (let i = 1; i <= naxis; i++) {
|
|
105
|
+
wcs.crpix.push(getNumOr(headers, `CRPIX${i}`, 1));
|
|
106
|
+
wcs.crval.push(getNumOr(headers, `CRVAL${i}`, 0));
|
|
107
|
+
wcs.cdelt.push(getNumOr(headers, `CDELT${i}`, 1));
|
|
108
|
+
wcs.ctype.push(getStrOr(headers, `CTYPE${i}`, ''));
|
|
109
|
+
wcs.cunit.push(getStrOr(headers, `CUNIT${i}`, ''));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
headers,
|
|
114
|
+
data,
|
|
115
|
+
shape,
|
|
116
|
+
wcs,
|
|
117
|
+
bitpix,
|
|
118
|
+
object: getStrOr(headers, 'OBJECT', ''),
|
|
119
|
+
telescope: getStrOr(headers, 'TELESCOP', ''),
|
|
120
|
+
dateObs: getStrOr(headers, 'DATE-OBS', ''),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
// ── FITS Builder (for tests) ─────────────────────────────────────────────────
|
|
124
|
+
/**
|
|
125
|
+
* Build a minimal FITS file as ArrayBuffer (for testing).
|
|
126
|
+
*/
|
|
127
|
+
export function buildFITS(opts) {
|
|
128
|
+
const cards = [];
|
|
129
|
+
cards.push(fmtCard('SIMPLE', true));
|
|
130
|
+
cards.push(fmtCard('BITPIX', opts.bitpix));
|
|
131
|
+
cards.push(fmtCard('NAXIS', opts.shape.length));
|
|
132
|
+
for (let i = 0; i < opts.shape.length; i++) {
|
|
133
|
+
cards.push(fmtCard(`NAXIS${i + 1}`, opts.shape[i]));
|
|
134
|
+
}
|
|
135
|
+
if (opts.bscale !== undefined)
|
|
136
|
+
cards.push(fmtCard('BSCALE', opts.bscale));
|
|
137
|
+
if (opts.bzero !== undefined)
|
|
138
|
+
cards.push(fmtCard('BZERO', opts.bzero));
|
|
139
|
+
if (opts.headers) {
|
|
140
|
+
for (const [key, val] of Object.entries(opts.headers)) {
|
|
141
|
+
cards.push(fmtCard(key, val));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
cards.push('END'.padEnd(CARD_SIZE));
|
|
145
|
+
// Pad header to block boundary
|
|
146
|
+
while (cards.length % CARDS_PER_BLOCK !== 0) {
|
|
147
|
+
cards.push(' '.repeat(CARD_SIZE));
|
|
148
|
+
}
|
|
149
|
+
const headerBytes = new Uint8Array(cards.length * CARD_SIZE);
|
|
150
|
+
for (let i = 0; i < cards.length; i++) {
|
|
151
|
+
for (let j = 0; j < CARD_SIZE; j++) {
|
|
152
|
+
headerBytes[i * CARD_SIZE + j] = cards[i].charCodeAt(j);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Write data
|
|
156
|
+
const bytesPerPixel = Math.abs(opts.bitpix) / 8;
|
|
157
|
+
const dataSize = opts.data.length * bytesPerPixel;
|
|
158
|
+
const paddedDataSize = Math.ceil(dataSize / BLOCK_SIZE) * BLOCK_SIZE;
|
|
159
|
+
const dataBytes = new ArrayBuffer(paddedDataSize);
|
|
160
|
+
const dataView = new DataView(dataBytes);
|
|
161
|
+
for (let i = 0; i < opts.data.length; i++) {
|
|
162
|
+
const off = i * bytesPerPixel;
|
|
163
|
+
switch (opts.bitpix) {
|
|
164
|
+
case 8:
|
|
165
|
+
new Uint8Array(dataBytes)[off] = opts.data[i];
|
|
166
|
+
break;
|
|
167
|
+
case 16:
|
|
168
|
+
dataView.setInt16(off, opts.data[i], false);
|
|
169
|
+
break;
|
|
170
|
+
case 32:
|
|
171
|
+
dataView.setInt32(off, opts.data[i], false);
|
|
172
|
+
break;
|
|
173
|
+
case -32:
|
|
174
|
+
dataView.setFloat32(off, opts.data[i], false);
|
|
175
|
+
break;
|
|
176
|
+
case -64:
|
|
177
|
+
dataView.setFloat64(off, opts.data[i], false);
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Combine
|
|
182
|
+
const result = new Uint8Array(headerBytes.length + paddedDataSize);
|
|
183
|
+
result.set(headerBytes);
|
|
184
|
+
result.set(new Uint8Array(dataBytes), headerBytes.length);
|
|
185
|
+
return result.buffer;
|
|
186
|
+
}
|
|
187
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
188
|
+
function decodeASCII(bytes, offset, length) {
|
|
189
|
+
let s = '';
|
|
190
|
+
for (let i = 0; i < length; i++) {
|
|
191
|
+
s += String.fromCharCode(bytes[offset + i]);
|
|
192
|
+
}
|
|
193
|
+
return s;
|
|
194
|
+
}
|
|
195
|
+
function parseCardValue(s) {
|
|
196
|
+
if (s === 'T')
|
|
197
|
+
return true;
|
|
198
|
+
if (s === 'F')
|
|
199
|
+
return false;
|
|
200
|
+
if (s.startsWith("'"))
|
|
201
|
+
return s.replace(/^'|'$/g, '').trim();
|
|
202
|
+
const n = Number(s);
|
|
203
|
+
return Number.isNaN(n) ? s : n;
|
|
204
|
+
}
|
|
205
|
+
function getNum(headers, key) {
|
|
206
|
+
const v = headers.get(key);
|
|
207
|
+
if (typeof v !== 'number')
|
|
208
|
+
throw new Error(`FITS: Missing or non-numeric header ${key}`);
|
|
209
|
+
return v;
|
|
210
|
+
}
|
|
211
|
+
function getNumOr(headers, key, def) {
|
|
212
|
+
const v = headers.get(key);
|
|
213
|
+
return typeof v === 'number' ? v : def;
|
|
214
|
+
}
|
|
215
|
+
function getStrOr(headers, key, def) {
|
|
216
|
+
const v = headers.get(key);
|
|
217
|
+
return typeof v === 'string' ? v : def;
|
|
218
|
+
}
|
|
219
|
+
function fmtCard(keyword, value) {
|
|
220
|
+
const kw = keyword.padEnd(8);
|
|
221
|
+
let valStr;
|
|
222
|
+
if (typeof value === 'boolean') {
|
|
223
|
+
valStr = value ? 'T' : 'F';
|
|
224
|
+
valStr = valStr.padStart(20);
|
|
225
|
+
}
|
|
226
|
+
else if (typeof value === 'number') {
|
|
227
|
+
valStr = String(value).padStart(20);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
valStr = `'${value}'`.padEnd(20);
|
|
231
|
+
}
|
|
232
|
+
return `${kw}= ${valStr}`.padEnd(CARD_SIZE);
|
|
233
|
+
}
|
|
234
|
+
//# 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.js';
|
|
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,iBAAiB,CAAC;AAEhD;;;;;;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,CAS9D"}
|
|
@@ -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":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAG9D;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,IAAc;IACzC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IAEzB,IAAI,EAAU,EAAE,EAAU,EAAE,EAAU,CAAC;IAEvC,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QACtB,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACd,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACd,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IAChB,CAAC;SAAM,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACd,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACd,EAAE,GAAG,CAAC,CAAC;IACT,CAAC;SAAM,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACd,EAAE,GAAG,CAAC,CAAC;QACP,EAAE,GAAG,CAAC,CAAC;IACT,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,KAAK,CAAC,wBAAwB,KAAK,CAAC,MAAM,gBAAgB,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,aAAa,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;IAE3D,0EAA0E;IAC1E,oEAAoE;IACpE,oEAAoE;IACpE,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACvB,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC;IAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAEnD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7B,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACxB,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,IAAc,EAAE,OAAe;IAC5D,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,oCAAoC;QACpC,OAAO,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC;IAED,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACzB,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACzB,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAEzB,IAAI,OAAO,GAAG,CAAC,IAAI,OAAO,IAAI,EAAE,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,WAAW,OAAO,qBAAqB,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,OAAO,GAAG,EAAE,GAAG,EAAE,CAAC;IAEjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;QACjC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACnC,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,IAAc;IAC1C,IAAI,GAAG,GAAG,QAAQ,EAChB,GAAG,GAAG,CAAC,QAAQ,CAAC;IAClB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1C,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACvB,IAAI,CAAC,GAAG,GAAG;YAAE,GAAG,GAAG,CAAC,CAAC;QACrB,IAAI,CAAC,GAAG,GAAG;YAAE,GAAG,GAAG,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AACpB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { RADIO_ASTRONOMY_TRAITS, type RadioAstronomyTraitName } from './constants/astronomy-traits.js';
|
|
2
|
+
import { PythonAstropyBridge, type AstropyResult } from './bridge/python-runner.js';
|
|
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.js';
|
|
12
|
+
export { fitsToGrid3D, extractChannel, fitsDataRange } from './fits/FITSToGrid.js';
|
|
13
|
+
/**
|
|
14
|
+
* Metadata exposing domain capabilities to the Studio / Schema Mapper.
|
|
15
|
+
*/
|
|
16
|
+
export declare const DOMAIN_MANIFEST: {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
version: string;
|
|
20
|
+
description: string;
|
|
21
|
+
keywords: string[];
|
|
22
|
+
traits: readonly ["radio_emitter", "synchrotron", "interferometer", "em_wave", "pulsar_timing", "spectral_line"];
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,KAAK,uBAAuB,EAC7B,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,mBAAmB,EAAE,KAAK,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAEpF;;;;;GAKG;AAGH,OAAO,EAAE,sBAAsB,EAAE,KAAK,uBAAuB,EAAE,CAAC;AAGhE,OAAO,EAAE,mBAAmB,EAAE,KAAK,aAAa,EAAE,CAAC;AAInD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAE,KAAK,OAAO,EAAE,MAAM,sBAAsB,CAAC;AACzF,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAEnF;;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.js';
|
|
2
|
+
import { PythonAstropyBridge } from './bridge/python-runner.js';
|
|
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. The React/R3F viewer is intentionally not a root export:
|
|
14
|
+
// cold consumers should not need browser UI peers to load the plugin vocabulary.
|
|
15
|
+
export { parseFITS, buildFITS } from './fits/FITSParser.js';
|
|
16
|
+
export { fitsToGrid3D, extractChannel, fitsDataRange } from './fits/FITSToGrid.js';
|
|
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,EACL,sBAAsB,GAEvB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,mBAAmB,EAAsB,MAAM,2BAA2B,CAAC;AAEpF;;;;;GAKG;AAEH,oBAAoB;AACpB,OAAO,EAAE,sBAAsB,EAAgC,CAAC;AAEhE,iBAAiB;AACjB,OAAO,EAAE,mBAAmB,EAAsB,CAAC;AAEnD,gFAAgF;AAChF,iFAAiF;AACjF,OAAO,EAAE,SAAS,EAAE,SAAS,EAA+B,MAAM,sBAAsB,CAAC;AACzF,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAEnF;;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
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holoscript/radio-astronomy-plugin",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"description": "Radio Astrophysics plugin for HoloScript spatial environments. Adds primitives for interferometers, pulsar analysis, and synchrotron emissions.",
|
|
5
|
-
"
|
|
6
|
-
"
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"LICENSE",
|
|
11
|
+
"README.md",
|
|
12
|
+
"plugin.manifest.json"
|
|
13
|
+
],
|
|
7
14
|
"scripts": {
|
|
8
15
|
"build": "tsc",
|
|
9
16
|
"test": "vitest run",
|
|
@@ -20,11 +27,14 @@
|
|
|
20
27
|
],
|
|
21
28
|
"author": "brianonbased-dev",
|
|
22
29
|
"license": "MIT",
|
|
23
|
-
"dependencies": {
|
|
24
|
-
"@holoscript/engine": "^6.1.3"
|
|
25
|
-
},
|
|
26
30
|
"peerDependencies": {
|
|
27
|
-
"@holoscript/core": ">=8.0.0"
|
|
31
|
+
"@holoscript/core": ">=8.0.0",
|
|
32
|
+
"@holoscript/engine": ">=6.1.4"
|
|
33
|
+
},
|
|
34
|
+
"peerDependenciesMeta": {
|
|
35
|
+
"@holoscript/engine": {
|
|
36
|
+
"optional": true
|
|
37
|
+
}
|
|
28
38
|
},
|
|
29
39
|
"devDependencies": {
|
|
30
40
|
"typescript": "^5.0.0",
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "radio-astronomy-plugin",
|
|
3
|
+
"version": "2.0.3",
|
|
4
|
+
"name": "Radio Astrophysics",
|
|
5
|
+
"description": "Radio Astrophysics plugin for HoloScript spatial environments. Adds primitives for interferometers, pulsar analysis, and synchrotron emissions.",
|
|
6
|
+
"traits": [],
|
|
7
|
+
"autoRegister": false,
|
|
8
|
+
"coreVersion": ">=8.0.0"
|
|
9
|
+
}
|
package/CHANGELOG.md
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
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
|
|
@@ -1,131 +0,0 @@
|
|
|
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,
|
|
37
|
-
ny = 3,
|
|
38
|
-
nz = 5;
|
|
39
|
-
const data: number[] = [];
|
|
40
|
-
for (let k = 0; k < nz; k++) {
|
|
41
|
-
for (let j = 0; j < ny; j++) {
|
|
42
|
-
for (let i = 0; i < nx; i++) {
|
|
43
|
-
data.push(k * 100 + j * 10 + i);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const buffer = buildFITS({
|
|
49
|
-
bitpix: -32,
|
|
50
|
-
shape: [nx, ny, nz],
|
|
51
|
-
data,
|
|
52
|
-
headers: {
|
|
53
|
-
OBJECT: 'Spectral Cube',
|
|
54
|
-
CRVAL3: 1.4e9,
|
|
55
|
-
CDELT3: 1e6,
|
|
56
|
-
CTYPE3: 'FREQ',
|
|
57
|
-
CUNIT3: 'Hz',
|
|
58
|
-
},
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
const fits = parseFITS(buffer);
|
|
62
|
-
expect(fits.shape).toEqual([nx, ny, nz]);
|
|
63
|
-
expect(fits.data.length).toBe(nx * ny * nz);
|
|
64
|
-
|
|
65
|
-
// Extract channel 2
|
|
66
|
-
const ch2 = extractChannel(fits, 2);
|
|
67
|
-
expect(ch2.length).toBe(nx * ny);
|
|
68
|
-
// Channel 2 values: 200, 201, ..., 211
|
|
69
|
-
expect(ch2[0]).toBeCloseTo(200);
|
|
70
|
-
expect(ch2[nx * ny - 1]).toBeCloseTo(211);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('fitsToGrid3D creates correct grid dimensions', () => {
|
|
74
|
-
const buffer = buildFITS({
|
|
75
|
-
bitpix: -32,
|
|
76
|
-
shape: [5, 4, 3],
|
|
77
|
-
data: Array.from({ length: 60 }, (_, i) => i),
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
const fits = parseFITS(buffer);
|
|
81
|
-
const grid = fitsToGrid3D(fits);
|
|
82
|
-
|
|
83
|
-
expect(grid.nx).toBe(5);
|
|
84
|
-
expect(grid.ny).toBe(4);
|
|
85
|
-
expect(grid.nz).toBe(3);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('fitsDataRange computes correct min/max', () => {
|
|
89
|
-
const buffer = buildFITS({
|
|
90
|
-
bitpix: -32,
|
|
91
|
-
shape: [3, 3],
|
|
92
|
-
data: [1, 5, 3, -2, 7, 0, 4, 8, -1],
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
const fits = parseFITS(buffer);
|
|
96
|
-
const [min, max] = fitsDataRange(fits);
|
|
97
|
-
expect(min).toBe(-2);
|
|
98
|
-
expect(max).toBe(8);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('handles BSCALE/BZERO for integer FITS', () => {
|
|
102
|
-
const buffer = buildFITS({
|
|
103
|
-
bitpix: 16, // int16
|
|
104
|
-
shape: [4],
|
|
105
|
-
data: [0, 100, 200, 300],
|
|
106
|
-
bscale: 0.1,
|
|
107
|
-
bzero: 50,
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
const fits = parseFITS(buffer);
|
|
111
|
-
// Physical value = BSCALE * raw + BZERO
|
|
112
|
-
expect(fits.data[0]).toBeCloseTo(50); // 0.1 * 0 + 50
|
|
113
|
-
expect(fits.data[1]).toBeCloseTo(60); // 0.1 * 100 + 50
|
|
114
|
-
expect(fits.data[3]).toBeCloseTo(80); // 0.1 * 300 + 50
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('2D image converts to nx×ny×1 grid', () => {
|
|
118
|
-
const buffer = buildFITS({
|
|
119
|
-
bitpix: -32,
|
|
120
|
-
shape: [6, 4],
|
|
121
|
-
data: Array.from({ length: 24 }, (_, i) => i),
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
const fits = parseFITS(buffer);
|
|
125
|
-
const grid = fitsToGrid3D(fits);
|
|
126
|
-
|
|
127
|
-
expect(grid.nx).toBe(6);
|
|
128
|
-
expect(grid.ny).toBe(4);
|
|
129
|
-
expect(grid.nz).toBe(1);
|
|
130
|
-
});
|
|
131
|
-
});
|
package/__tests__/plugin.test.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
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
|
-
});
|