Skip to content

Usage

Constructing a SpectroCube

Single spectrum

Pass a 1D intensity array. Dimensions are inferred as ("wavelength",).

import numpy as np
from spectrocube import SpectroCube

wl = np.linspace(400.0, 700.0, 3000)
spectrum = np.ones(3000) * 100.0

sc = SpectroCube.from_arrays(
    wavelength=wl,
    intensity=spectrum,
    instrument_id="my_spectrometer",
    calibration_type="counts",
    intensity_units="counts",
    wavelength_medium="air",
)
print(sc)
# SpectroCube(instrument='my_spectrometer', dims=(wavelength=3000), ...)

Multi-frame sequence

Pass a 2D array (n_frames, n_wavelengths). Dimensions are inferred as ("frame", "wavelength").

intensity = np.random.default_rng(0).random((10, 3000)) * 1000

sc = SpectroCube.from_arrays(
    wavelength=wl,
    intensity=intensity,
    instrument_id="my_spectrometer",
    calibration_type="counts",
    intensity_units="counts",
    wavelength_medium="air",
    exposure_s=0.1,
    spectrometer="Ocean HR4000",
    detector="CCD-1024",
)
print(sc.dims)   # ('frame', 'wavelength')
print(sc.sizes)  # {'frame': 10, 'wavelength': 3000}

Custom dimensions

Supply dims explicitly for non-default names or for arrays with 3+ dimensions.

# 2D: rename the leading axis
sc = SpectroCube.from_arrays(
    wavelength=wl,
    intensity=np.ones((50, 3000)),
    dims=("time", "wavelength"),
    instrument_id="my_spectrometer",
    calibration_type="relative",
    intensity_units="a.u.",
    wavelength_medium="air",
)

# 3D: multi-chord time-resolved
sc = SpectroCube.from_arrays(
    wavelength=wl,
    intensity=np.ones((20, 50, 3000)),
    dims=("chord", "time", "wavelength"),
    instrument_id="tomographic_spectrometer",
    calibration_type="relative",
    intensity_units="a.u.",
    wavelength_medium="air",
)
print(sc.dims)   # ('chord', 'time', 'wavelength')

Providing coordinate arrays

Pass a coords dict to label non-wavelength dimensions with real values.

time_axis = np.arange(50) * 0.02  # seconds

sc = SpectroCube.from_arrays(
    wavelength=wl,
    intensity=np.ones((50, 3000)),
    dims=("time", "wavelength"),
    coords={"time": time_axis},
    instrument_id="my_spectrometer",
    calibration_type="counts",
    intensity_units="counts",
    wavelength_medium="air",
)

Absolute calibration

Set calibration_type="absolute" and provide physical units and a calibration source.

sc_abs = SpectroCube.from_arrays(
    wavelength=wl,
    intensity=np.ones((5, 3000)) * 1e14,
    instrument_id="calibrated_spectrometer",
    calibration_type="absolute",
    intensity_units="ph/s/nm/sr",
    wavelength_medium="air",
    calibration_source="NIST-traceable tungsten lamp, 2024-03-15",
    exposure_s=1.0,
)

Validation

report = sc.validate()

if report.ok:
    print("Valid:", report)
else:
    print("Errors found:")
    for e in report.errors:
        print(" ", e)

for w in report.warnings:
    print("Warning:", w)

validate() always returns a ValidationReport; it never raises. save() raises ValueError if the report has errors.


Save and load

sc.save("output.nc")               # validates before writing by default
sc.save("output.nc", validate=False)  # skip validation

sc2 = SpectroCube.load("output.nc")

Accessing the underlying Dataset

All xarray operations are available directly through .ds.

# Select a wavelength slice using xarray
ha_region = sc.ds["intensity"].sel(wavelength=slice(654, 659))

# Select a single frame
frame_0 = sc.ds["intensity"].isel(frame=0)

# Check all attributes
for key, val in sc.ds.attrs.items():
    print(f"{key}: {val}")

Wrapping an existing Dataset

If you already have an xr.Dataset with the right structure, use from_dataset().

import xarray as xr

ds = xr.load_dataset("some_file.nc")
sc = SpectroCube.from_dataset(ds)
report = sc.validate()

Complete example

import numpy as np
from spectrocube import SpectroCube

rng = np.random.default_rng(42)
wl = np.linspace(400.0, 700.0, 3000)

def gaussian(x, center, width, amp):
    return amp * np.exp(-0.5 * ((x - center) / width) ** 2)

continuum = 50.0 + 0.01 * (wl - 400)
spectrum = (
    continuum
    + gaussian(wl, 656.28, 0.3, 5000)  # Hα
    + gaussian(wl, 486.13, 0.3, 1500)  # Hβ
    + rng.normal(0, 5, len(wl))
)
intensity = np.stack([spectrum * (1.0 + 0.02 * i) for i in range(3)])

sc = SpectroCube.from_arrays(
    wavelength=wl,
    intensity=intensity,
    instrument_id="lab_spectrometer",
    calibration_type="relative",
    intensity_units="a.u.",
    wavelength_medium="air",
    wavelength_accuracy_nm=0.05,
    exposure_s=0.5,
    spectrometer="Echelle ESA-3",
    detector="Andor iStar 334T",
    shot_number="run_001",
    notes="Synthetic Balmer series, 3 frames",
)

print(sc)
print(sc.validate())

sc.save("/tmp/example.nc")
sc2 = SpectroCube.load("/tmp/example.nc")
print(sc2)