fitsjs-ng 1.0.0 → 1.0.1
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/README.md +242 -28
- package/dist/index.cjs +18 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +716 -10
- package/dist/index.d.ts +716 -10
- package/dist/index.js +18 -2
- package/dist/index.js.map +1 -1
- package/package.json +22 -15
package/README.md
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
# fitsjs-ng
|
|
2
2
|
|
|
3
|
-
Modern TypeScript library for reading [FITS](https://fits.gsfc.nasa.gov/) (
|
|
3
|
+
Modern TypeScript library for reading and writing [FITS](https://fits.gsfc.nasa.gov/), [SER](https://grischa-hahn.hier-im-netz.de/astro/ser/), and [XISF](https://pixinsight.com/xisf/) astronomical files. A complete rewrite of [astrojs/fitsjs](https://github.com/astrojs/fitsjs) with Promise-based APIs, full type safety, and Node.js/browser dual support.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **FITS Image Reading** — BITPIX 8, 16, 32, -32, -64 with BZERO/BSCALE scaling
|
|
7
|
+
- **FITS Image Reading** — BITPIX 8, 16, 32, 64, -32, -64 with BZERO/BSCALE scaling
|
|
8
|
+
- **FITS Image Writing** — build FITS HDUs and export complete FITS buffers
|
|
9
|
+
- **SER Read/Write** — full SER v3 parsing/writing, timestamps, Bayer/CMY + RGB/BGR support
|
|
10
|
+
- **XISF Read/Write** — monolithic (`.xisf`) and distributed (`.xish` + `.xisb`) workflows
|
|
11
|
+
- **XISF Signature Verification** — XML-DSig `SignedInfo`/digest/signature verification with policy control
|
|
12
|
+
- **XISF↔FITS Conversion** — strict conversion with metadata preservation
|
|
13
|
+
- **XISF↔HiPS Conversion** — direct conversion APIs via standards-preserving FITS bridge
|
|
14
|
+
- **SER↔FITS / SER↔XISF Conversion** — reversible metadata/time-stamp aware conversion pipelines
|
|
15
|
+
- **HiPS Image + HiPS3D** — read/write HiPS properties, tiles, Allsky, and lint checks
|
|
16
|
+
- **FITS↔HiPS Conversion** — build HiPS directories and export tile/map/cutout FITS
|
|
8
17
|
- **Data Cubes** — Frame-by-frame reading of 3D+ image data
|
|
9
18
|
- **ASCII Tables** — Fixed-width text table parsing (A/I/F/E/D format codes)
|
|
10
19
|
- **Binary Tables** — All standard types (L/B/I/J/K/A/E/D/C/M/X), bit arrays, heap access
|
|
@@ -24,29 +33,112 @@ pnpm add fitsjs-ng
|
|
|
24
33
|
## Quick Start
|
|
25
34
|
|
|
26
35
|
```ts
|
|
27
|
-
import {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
import {
|
|
37
|
+
FITS,
|
|
38
|
+
SER,
|
|
39
|
+
XISF,
|
|
40
|
+
XISFWriter,
|
|
41
|
+
parseSERBuffer,
|
|
42
|
+
parseSERBlob,
|
|
43
|
+
convertFitsToXisf,
|
|
44
|
+
convertXisfToFits,
|
|
45
|
+
convertSerToFits,
|
|
46
|
+
convertFitsToSer,
|
|
47
|
+
convertSerToXisf,
|
|
48
|
+
convertXisfToSer,
|
|
49
|
+
convertXisfToHiPS,
|
|
50
|
+
convertHiPSToXisf,
|
|
51
|
+
NodeFSTarget,
|
|
52
|
+
Image,
|
|
53
|
+
} from 'fitsjs-ng'
|
|
54
|
+
import fs from 'node:fs'
|
|
55
|
+
|
|
56
|
+
// FITS from ArrayBuffer / Blob / Node buffer-like / URL
|
|
57
|
+
const fits = FITS.fromArrayBuffer(
|
|
58
|
+
await fs.promises
|
|
59
|
+
.readFile('image.fits')
|
|
60
|
+
.then((b) => b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength)),
|
|
61
|
+
)
|
|
62
|
+
const fitsFromBlob = await FITS.fromBlob(new Blob([await fs.promises.readFile('image.fits')]))
|
|
63
|
+
const fitsFromNodeBuffer = FITS.fromNodeBuffer(await fs.promises.readFile('image.fits'))
|
|
64
|
+
const fitsFromUrl = await FITS.fromURL('https://example.com/image.fits')
|
|
65
|
+
|
|
66
|
+
// Access header + image
|
|
42
67
|
const header = fits.getHeader()
|
|
43
|
-
console.log(header
|
|
44
|
-
console.log(header.get('NAXIS1')) // e.g. 1024
|
|
45
|
-
|
|
46
|
-
// Read image pixels
|
|
68
|
+
console.log(header?.get('BITPIX'))
|
|
47
69
|
const image = fits.getDataUnit() as Image
|
|
48
70
|
const pixels = await image.getFrame(0)
|
|
49
71
|
const [min, max] = image.getExtent(pixels)
|
|
72
|
+
|
|
73
|
+
// FITS <-> XISF
|
|
74
|
+
const xisfBytes = await convertFitsToXisf(
|
|
75
|
+
await fs.promises
|
|
76
|
+
.readFile('image.fits')
|
|
77
|
+
.then((b) => b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength)),
|
|
78
|
+
)
|
|
79
|
+
const xisf = await XISF.fromArrayBuffer(xisfBytes as ArrayBuffer)
|
|
80
|
+
const fitsBytes = await convertXisfToFits(xisf)
|
|
81
|
+
|
|
82
|
+
// SER parse + conversions
|
|
83
|
+
const serBytes = await fs.promises
|
|
84
|
+
.readFile('capture.ser')
|
|
85
|
+
.then((b) => b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength))
|
|
86
|
+
const ser = SER.fromArrayBuffer(serBytes)
|
|
87
|
+
const parsedSer = parseSERBuffer(serBytes)
|
|
88
|
+
const parsedSerBlob = await parseSERBlob(new Blob([serBytes]))
|
|
89
|
+
const fitsFromSer = await convertSerToFits(serBytes, { layout: 'cube' })
|
|
90
|
+
const serFromFits = await convertFitsToSer(fitsFromSer, { sourceLayout: 'auto' })
|
|
91
|
+
const xisfFromSer = await convertSerToXisf(serBytes)
|
|
92
|
+
const serFromXisf = await convertXisfToSer(xisfFromSer as ArrayBuffer, { imageIndex: 0 })
|
|
93
|
+
|
|
94
|
+
// XISF <-> HiPS (offline/local target)
|
|
95
|
+
const hipsTarget = new NodeFSTarget('./demo/.out/readme-quickstart-hips')
|
|
96
|
+
await convertXisfToHiPS(xisfBytes as ArrayBuffer, {
|
|
97
|
+
output: hipsTarget,
|
|
98
|
+
title: 'XISF Survey',
|
|
99
|
+
creatorDid: 'ivo://example/xisf',
|
|
100
|
+
hipsOrder: 4,
|
|
101
|
+
minOrder: 1,
|
|
102
|
+
tileWidth: 128,
|
|
103
|
+
formats: ['fits', 'png'],
|
|
104
|
+
})
|
|
105
|
+
const xisfCutout = await convertHiPSToXisf(hipsTarget, {
|
|
106
|
+
cutout: { width: 512, height: 512, ra: 83.63, dec: 22.01, fov: 1.2 },
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
// XISF writer outputs
|
|
110
|
+
const monolithic = await XISFWriter.toMonolithic(xisf.unit, { compression: 'zlib' })
|
|
111
|
+
const distributed = await XISFWriter.toDistributed(xisf.unit, { compression: 'zlib' })
|
|
112
|
+
// distributed.header => .xish bytes, distributed.blocks['blocks.xisb'] => .xisb bytes
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### HiPS Quick Start
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { NodeFSTarget, convertFitsToHiPS, convertHiPSToFITS, HiPS, lintHiPS } from 'fitsjs-ng'
|
|
119
|
+
|
|
120
|
+
const target = new NodeFSTarget('./out/my-hips')
|
|
121
|
+
await convertFitsToHiPS(fitsArrayBuffer, {
|
|
122
|
+
output: target,
|
|
123
|
+
title: 'My Survey',
|
|
124
|
+
creatorDid: 'ivo://example/my-survey',
|
|
125
|
+
hipsOrder: 6,
|
|
126
|
+
tileWidth: 512,
|
|
127
|
+
formats: ['fits', 'png'],
|
|
128
|
+
interpolation: 'bilinear',
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
const hips = await HiPS.open('./out/my-hips')
|
|
132
|
+
const tile = await hips.readTile({ order: 6, ipix: 12345, format: 'fits' })
|
|
133
|
+
|
|
134
|
+
const cutoutFits = await convertHiPSToFITS('./out/my-hips', {
|
|
135
|
+
cutout: { width: 1024, height: 1024, ra: 83.63, dec: 22.01, fov: 1.2 },
|
|
136
|
+
backend: 'auto', // local first, fallback to hips2fits if hipsId is set
|
|
137
|
+
hipsId: 'CDS/P/2MASS/K',
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
const lint = await lintHiPS('./out/my-hips')
|
|
141
|
+
console.log(lint.ok, lint.issues)
|
|
50
142
|
```
|
|
51
143
|
|
|
52
144
|
## API Reference
|
|
@@ -62,6 +154,80 @@ Static factory methods:
|
|
|
62
154
|
| `FITS.fromURL(url)` | Fetch and parse remote file (async) |
|
|
63
155
|
| `FITS.fromNodeBuffer(buffer)` | Parse from Node.js `Buffer` (sync) |
|
|
64
156
|
|
|
157
|
+
### `XISF`
|
|
158
|
+
|
|
159
|
+
Static factory methods:
|
|
160
|
+
|
|
161
|
+
| Method | Description |
|
|
162
|
+
| ------------------------------ | ---------------------------------------- |
|
|
163
|
+
| `XISF.fromArrayBuffer(buffer)` | Parse from `ArrayBuffer` |
|
|
164
|
+
| `XISF.fromBlob(blob)` | Parse from `Blob`/`File` |
|
|
165
|
+
| `XISF.fromURL(url)` | Fetch and parse remote `.xisf`/`.xish` |
|
|
166
|
+
| `XISF.fromNodeBuffer(buffer)` | Parse from Node.js `Buffer`-like payload |
|
|
167
|
+
|
|
168
|
+
### `SER`
|
|
169
|
+
|
|
170
|
+
Static factory methods:
|
|
171
|
+
|
|
172
|
+
| Method | Description |
|
|
173
|
+
| ----------------------------- | --------------------------------------------------- |
|
|
174
|
+
| `SER.fromArrayBuffer(buffer)` | Parse SER from `ArrayBuffer` |
|
|
175
|
+
| `SER.fromBlob(blob)` | Parse SER from `Blob`/`File` |
|
|
176
|
+
| `SER.fromURL(url)` | Fetch and parse remote `.ser` |
|
|
177
|
+
| `SER.fromNodeBuffer(buffer)` | Parse SER from Node.js `Buffer`-like payload |
|
|
178
|
+
| `parseSERBuffer(buffer)` | Parse SER buffer and return structured parse result |
|
|
179
|
+
| `parseSERBlob(blob)` | Parse SER blob and return structured parse result |
|
|
180
|
+
| `writeSER(input)` | Serialize SER header + frames (+ optional trailer) |
|
|
181
|
+
|
|
182
|
+
Instance helpers:
|
|
183
|
+
|
|
184
|
+
| Method | Description |
|
|
185
|
+
| -------------------------- | ---------------------------------------------- |
|
|
186
|
+
| `ser.getFrameCount()` | Total frame count |
|
|
187
|
+
| `ser.getFrameRGB(i)` | RGB helper decode for mono/Bayer/CMY/RGB/BGR |
|
|
188
|
+
| `ser.getDurationTicks()` | Duration from trailer timestamps (100ns ticks) |
|
|
189
|
+
| `ser.getDurationSeconds()` | Duration in seconds from trailer timestamps |
|
|
190
|
+
| `ser.getEstimatedFPS()` | Estimated FPS from timestamp spacing |
|
|
191
|
+
|
|
192
|
+
### `XISFWriter`
|
|
193
|
+
|
|
194
|
+
| Method | Description |
|
|
195
|
+
| ---------------------------- | ------------------------------------------------ |
|
|
196
|
+
| `XISFWriter.toMonolithic()` | Serialize to monolithic `.xisf` bytes |
|
|
197
|
+
| `XISFWriter.toDistributed()` | Serialize to distributed `.xish` + `.xisb` bytes |
|
|
198
|
+
|
|
199
|
+
### Conversion
|
|
200
|
+
|
|
201
|
+
| Method | Description |
|
|
202
|
+
| ----------------------------------- | -------------------------------------------------- |
|
|
203
|
+
| `convertXisfToFits(input)` | Convert XISF to FITS bytes |
|
|
204
|
+
| `convertFitsToXisf(input)` | Convert FITS to XISF bytes (or distributed object) |
|
|
205
|
+
| `convertSerToFits(input)` | Convert SER to FITS bytes |
|
|
206
|
+
| `convertFitsToSer(input)` | Convert FITS to SER bytes |
|
|
207
|
+
| `convertSerToXisf(input)` | Convert SER to XISF bytes |
|
|
208
|
+
| `convertXisfToSer(input)` | Convert XISF to SER bytes |
|
|
209
|
+
| `convertFitsToHiPS(input, options)` | Convert FITS to HiPS directory |
|
|
210
|
+
| `convertHiPSToFITS(input, options)` | Export HiPS to FITS tile/map/cutout |
|
|
211
|
+
|
|
212
|
+
SER conversion options:
|
|
213
|
+
|
|
214
|
+
- `convertSerToFits(input, { layout: 'cube' | 'multi-hdu' })` (default: `'cube'`)
|
|
215
|
+
- `convertFitsToSer(input, { sourceLayout: 'auto' | 'cube' | 'multi-hdu' })` (default: `'auto'`)
|
|
216
|
+
- `convertXisfToSer(input, { imageIndex })` for multi-image XISF units
|
|
217
|
+
|
|
218
|
+
### HiPS
|
|
219
|
+
|
|
220
|
+
| Method / Class | Description |
|
|
221
|
+
| ---------------------------------------- | ------------------------------------------------- |
|
|
222
|
+
| `HiPS.open(source)` | Open HiPS from local path, URL, or storage target |
|
|
223
|
+
| `HiPS.getProperties()` | Load and parse `properties` |
|
|
224
|
+
| `HiPS.readTile({ order, ipix, format })` | Read/decode one tile |
|
|
225
|
+
| `NodeFSTarget` | Node filesystem output target |
|
|
226
|
+
| `BrowserZipTarget` | Browser ZIP output target |
|
|
227
|
+
| `BrowserOPFSTarget` | Browser OPFS output target |
|
|
228
|
+
| `HiPSProperties` | Parse/serialize/validate HiPS properties |
|
|
229
|
+
| `lintHiPS(source)` | Validate metadata and structure |
|
|
230
|
+
|
|
65
231
|
Instance methods:
|
|
66
232
|
|
|
67
233
|
| Method | Description |
|
|
@@ -84,13 +250,40 @@ Instance methods:
|
|
|
84
250
|
|
|
85
251
|
### `Image`
|
|
86
252
|
|
|
87
|
-
| Method
|
|
88
|
-
|
|
|
89
|
-
| `getFrame(frame?)`
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
| `
|
|
93
|
-
| `
|
|
253
|
+
| Method | Description |
|
|
254
|
+
| -------------------------- | --------------------------------------------------------- |
|
|
255
|
+
| `getFrame(frame?)` | Read a single frame (async) |
|
|
256
|
+
| `getFrameAsNumber(frame?)` | Read frame as `Float64Array` (explicitly lossy for int64) |
|
|
257
|
+
| `getFrames(start, count)` | Read multiple frames (async) |
|
|
258
|
+
| `getExtent(pixels)` | Compute `[min, max]` (`number`/`bigint`) |
|
|
259
|
+
| `getPixel(pixels, x, y)` | Get pixel at (x, y) (`number`/`bigint`) |
|
|
260
|
+
| `isDataCube()` | Whether NAXIS > 2 |
|
|
261
|
+
|
|
262
|
+
`BITPIX=64` reads use lossless `BigInt64Array` on the primary path when linear scaling is exact (`BSCALE=1`, safe-integer `BZERO`). Use `getFrameAsNumber()` only when you intentionally accept precision loss.
|
|
263
|
+
|
|
264
|
+
### XISF Signature Policy
|
|
265
|
+
|
|
266
|
+
`XISF.fromArrayBuffer()` accepts:
|
|
267
|
+
|
|
268
|
+
- `signaturePolicy: 'require' | 'warn' | 'ignore'` (default: `'require'`)
|
|
269
|
+
- `verifySignatures` (default: `true`)
|
|
270
|
+
|
|
271
|
+
Behavior:
|
|
272
|
+
|
|
273
|
+
- **`require`**: signed documents must verify; failures throw `XISFSignatureError`
|
|
274
|
+
- **`warn`**: signature failures are reported through warnings and `unit.signature`
|
|
275
|
+
- **`ignore`**: signature verification is skipped
|
|
276
|
+
|
|
277
|
+
When a detached signature is present and verification is enabled, checksum verification is forced for attachment/path/url data blocks.
|
|
278
|
+
|
|
279
|
+
### FITS↔XISF Preservation Scope
|
|
280
|
+
|
|
281
|
+
`convertFitsToXisf()` / `convertXisfToFits()` preserve:
|
|
282
|
+
|
|
283
|
+
- FITS keyword values **and comments** (`Header.getCards()` based mapping)
|
|
284
|
+
- non-image HDUs through `FITS:PreservedHDULayout` metadata (reversible card+payload container)
|
|
285
|
+
|
|
286
|
+
For `BITPIX=64`, canonical unsigned encoding (`BSCALE=1`, `BZERO=9223372036854775808`) is detected with strict raw-card parsing (no tolerance heuristics).
|
|
94
287
|
|
|
95
288
|
### `Table` (ASCII)
|
|
96
289
|
|
|
@@ -169,8 +362,29 @@ pnpm test # Run tests
|
|
|
169
362
|
pnpm build # Build library
|
|
170
363
|
pnpm typecheck # Type check
|
|
171
364
|
pnpm lint # Lint
|
|
365
|
+
pnpm demo:all # Run all Node demos in sequence
|
|
366
|
+
pnpm demo # FITS/XISF CLI demo
|
|
367
|
+
pnpm demo:hips # HiPS Node demo (FITS->HiPS->FITS)
|
|
368
|
+
pnpm demo:xisf # XISF Node demo (FITS<->XISF, monolithic/distributed)
|
|
369
|
+
pnpm demo:ser # SER Node demo (SER<->FITS<->XISF)
|
|
370
|
+
pnpm demo:web # Serve web demos (open /demo/web/index.html, /demo/web/hips.html, /demo/web/xisf.html)
|
|
172
371
|
```
|
|
173
372
|
|
|
373
|
+
Node demo artifacts are written under `demo/.out/*`.
|
|
374
|
+
|
|
375
|
+
## Standards & Compatibility
|
|
376
|
+
|
|
377
|
+
- HiPS metadata and directory naming follow HiPS 1.0 conventions (`Norder*/Dir*/Npix*`, `Norder3/Allsky.*`, `properties`, `Moc.fits`).
|
|
378
|
+
- FITS writing follows FITS 4.0 card/block alignment rules (80-char cards, 2880-byte blocks).
|
|
379
|
+
- Output `properties` defaults to `hips_version=1.4` and also writes legacy compatibility fields (`coordsys`, `maxOrder`, `format`).
|
|
380
|
+
- XISF default codec provider supports `zlib`, `lz4`, and `lz4hc` for read/write and `zstd` for read; custom providers can extend encoding support.
|
|
381
|
+
|
|
382
|
+
## Remote Backend Behavior
|
|
383
|
+
|
|
384
|
+
- `backend: 'local'`: all conversion is performed locally.
|
|
385
|
+
- `backend: 'remote'`: cutout export uses CDS hips2fits endpoint directly.
|
|
386
|
+
- `backend: 'auto'`: try local cutout first, then fallback to hips2fits when `hipsId` is provided.
|
|
387
|
+
|
|
174
388
|
## Credits
|
|
175
389
|
|
|
176
390
|
Based on [astrojs/fitsjs](https://github.com/astrojs/fitsjs) by Amit Kapadia / Zooniverse.
|