multicube
The example describes the possibilities of the SubCube class - a wrapper class which inherits most of its routines from pyspeckit's Cube class (see the docs here). This notebook demonstrates the usage of the internal methods defined in SubCube, mainly dealing with flexible initial guess selection and SNR estimates.
This notebook:
In [1]:
import numpy as np
%matplotlib inline
import matplotlib.pylab as plt
import pyspeckit
from multicube.subcube import SubCube
from multicube.astro_toolbox import make_test_cube, get_ncores
from IPython.utils import io
import warnings
warnings.filterwarnings('ignore')
Let's first make a test FITS file, containing a mixture of synthetic signal with some noise put on top of it. The created spectral cube will be 10x10 pixels wide in the plain of sky and 300 pixels "long" along its spectral axis.
In [2]:
make_test_cube((300,10,10), outfile='foo.fits', sigma=(10,5))
sc = SubCube('foo.fits')
To make things interesting, let's introduce a radial velocity gradient in our cube along with a second, weaker component.
In [3]:
# TODO: move this to astro_toolbox.py
# as a general synthetic cube generator routine
def tinker_ppv(arr):
scale_roll = 15
rel_shift = 30
rel_str = 5
shifted_component = np.roll(arr, rel_shift) / rel_str
for y,x in np.ndindex(arr.shape[1:]):
roll = np.sqrt((x-5)**2 + (y-5)**2) * scale_roll
arr[:,y,x] = np.roll(arr[:,y,x], int(roll))
return arr + shifted_component
sc.cube = tinker_ppv(sc.cube)
This is how a sample spectrum looks like at x,y = (3,7):
In [4]:
sc.plot_spectrum(3,7)
multicube
can take minimal and maximal values for spectral model parameters and permute them to generate a grid in parameter space with given spacing (finesse
). This works for an arbirtary number of parameters and with custom resolution for individual parameters (e.g., setting finesse = [3, 20, 5]
will also work in this case).
(for Gaussian model in pyspeckit
, the parameter order is [amplitude
, centroid
, sigma
])
In [5]:
sc.update_model('gaussian')
minpars = [0.1, sc.xarr.min().value, 0.1]
maxpars = [2.0, sc.xarr.max().value, 2.0]
finesse = 10
sc.make_guess_grid(minpars, maxpars, finesse)
Out[5]:
This grid, stored under sc.guess_grid
, can be used to generate a number of spectral models with pyspeckit
, and the guesses that have the least residual rms
can be selected for the whole cube:
sc.best_map
stored the map between x,y pixel numbers and the numbers of corresponding best modelssc.best_model
is the number of the model suited best for the pixel with the highest S/N ratio.
In [6]:
sc.generate_model()
sc.get_snr_map()
sc.best_guess()
Here's the best model selected for the spectrum above:
In [7]:
sc.plot_spectrum(3,7)
sc.plotter.axis.plot(sc.xarr.value, sc.model_grid[sc._best_map[3,7]])
# TODO: show the best five guesses or so for this pixel to demonstrate the grid size in the parameter space
Out[7]:
In [8]:
sc1, sc2 = sc, sc.copy()
with io.capture_output() as captured: # suppresses output, normally should not be used
sc1.fiteach(fittype = sc1.fittype,
guesses = sc1.best_snr_guess, # best for the highest SNR pixel
multicore = get_ncores(),
errmap = sc1._rms_map,
verbose = 0,
**sc1.fiteach_args)
# let's plot the velocity field:
sc1.show_fit_param(1, cmap='coolwarm')
clb = sc1.mapplot.FITSFigure.colorbar
clb.set_axis_label_text(sc1.xarr.unit.to_string('latex_inline'))
sc1.mapplot.FITSFigure.set_title("fiteach() for one guess only:")
Because the same guess was used across the cube with varying velocity centroid, it isn't surprising that the fit failed to converge outside the central spot. Normally, a combination of start_from_point=(x,y)
and use_neighbor_as_guess=True
arguments can be passed to pyspeckit.Cube.fiteach
to gradually spread from (x,y) and avoid divergence in this case, but this approach
multicore
is set to a relatively high number.Alternatively, moment analysis is commonly used get the best initial guess for the nonlinear regression. However, the method is restricted to a singular Gaussian component. In the following code block, an alternative is shown, with initial guesses for each pixel selected as the best generated model for individual spectrum:
In [9]:
with io.capture_output() as captured: # suppresses output, normally should not be used
sc2.fiteach(fittype = sc2.fittype,
guesses = sc2.best_guesses,
multicore = get_ncores(),
errmap = sc2._rms_map,
verbose = 0,
**sc2.fiteach_args);
#sc2.show_fit_param(1, cmap='coolwarm')
sc2.show_fit_param(1, cmap='coolwarm')
clb = sc2.mapplot.FITSFigure.colorbar
clb.set_axis_label_text(sc2.xarr.unit.to_string('latex_inline'))
sc2.mapplot.FITSFigure.set_title("fiteach() for a map of best guesses:")
Voilà! All the pixels are fit.
In [10]:
sc.plot_spectrum(3,7, plot_fit=True)