Pixel-by-pixel fitting

Spectuner provides functionality for efficient pixel-by-pixel fitting using a neural network. In this tutorial, we will learn how to use this functionality step-by-step.

Preprocessing

Let’s assume that we want to perform pixel-by-pixel fitting for the following FITS files:

XXX_spw1_line.fits
XXX_spw1_continuum.fits
XXX_spw2_line.fits
XXX_spw2_continuum.fits

The first step is to perform preprocessing using the following code:

import spectuner

file_list = [
    spectuner.CubeItem(
        line="XXX_spw1_line.fits",
        continuum="XXX_spw1_continuum.fits",
    ),
    spectuner.CubeItem(
        line="XXX_spw2_line.fits",
        continuum="XXX_spw2_continuum.fits",
    )
]
pipline = spectuner.CubePipeline(
    noise_factor_local=6.,
    number_cut=3,
    v_LSR=0.
)
pipline.run(file_list, "XXX.h5")

This converts the FITS files into a compact HDF file. CubePipeline excludes noisy pixels and estimates the RMS noise for each cube, which we use in the subsequent fitting process. We can adjust the parameters noise_factor_local and number_cut to determine which pixels to exclude; if number_cut=0, no pixels are excluded. Furthermore, it is important to set the LSR velocity v_LSR correctly, as our neural network can only handle velocity offsets within the range of -12 km/s to +12 km/s.

Performing the fitting

The code below demonstrates how to perform pixel-by-pixel fitting. We can employ a neural network to provide initial guesses, which significantly improves the fitting process. To use the neural network, please download the weights file from Hugging Face and install PyTorch. We highly recommend using a GPU for inference; otherwise, please set device="cpu".

There are two types of loss functions available for pixel-by-pixel fitting:

  1. "chi2": The \(\chi^2\) loss function, which works well if line blending is not significant.

  2. "pm": The peak matching loss function, which should be used if line blending is significant.

The example below fits the spectra of CH3OH. Update species to fit different species. Multiple species are supported.

import spectuner

# Configuration
config = spectuner.load_default_config()

# Set the path to the spectroscopic database
fname_db = "path/to/the/cdms/database"
config.set_fname_db(fname_db)

# Set information for inference
config.set_inference_model(
    ckpt="path/to/the/network/weights/file"
    device="cuda:0",
    batch_size=64
)

# Set the species to fit
species = ["CH3OH;v=0;"]
config.set_pixel_by_pixel_fitting(
    species=species,
    loss_fn="chi2",
    need_spectra=True
)

# Run
fname_cube = "XXX.h5" # HDF file created in preprocessing
save_name = "XXX_results.h5" # Path to save the results
spectuner.fit_pixel_by_pixel(config, fname_cube, save_name)

We can use spectuner.print_h5_structure to check the structure of the output file.

spectuner.print_h5_structure(save_name)

This may give the following output:

CH3OH;v=0;/
N_tot (shape: (XXX,), type: float32)
T_ex (shape: (XXX,), type: float32)
T_pred/
    0 (shape: (XXX, XXXX), type: float32)
    1 (shape: (XXX, XXXX), type: float32)
delta_v (shape: (XXX,), type: float32)
theta (shape: (XXX,), type: float32)
v_offset (shape: (XXX,), type: float32)
score/
fun (shape: (XXX,), type: float32)
nfev (shape: (XXX,), type: int32)

We can access the fitting results using HDFCubeManager. Note that the initialization of HDFCubeManager requires the HDF file of the observed data.

import spectuner

# HDF file created in preprocessing
fname_cube = "XXX.h5"

# Path to the saved results
fname_result = "XXX_result.h5"

cube_mgr = spectuner.HDFCubeManager(fname_cube)
# Load the predicted excitation temperature
T_ex = cube_mgr.load_pred_data(fname, "CH3OH;v=0;/T_ex")
# Load the predicted spectra of the first spectral window
T_pred = cube_mgr.load_pred_data(fname, "CH3OH;v=0;/T_pred/0")

Converting the fitting results to FITS files

This step is optional. We may use HDFCubeManager to convert the fitting results to FITS files, which can then be visulaized using CARTA. Note that the LSR velocity is corrected for the predicted spectra. By setting add_v_LSR=True, we add the LSR velocity to the predicted spectra so that the results are comparable with the observation.

# Directory to save the FITS files
save_dir = "fits/"

# Convert
cube_mgr.pred_data_to_fits(fname_result, save_dir, add_v_LSR=True, overwrite=True)