def collapse_gaussfit(cube,axis=2): std_coll = cube.std(axis=axis) mean_std = median(std_coll.ravel()) if axis > 0: cube = cube.swapaxes(0,axis) width_arr = zeros(cube.shape[1:]) amp_arr = zeros(cube.shape[1:]) chi2_arr = zeros(cube.shape[1:]) offset_arr = zeros(cube.shape[1:]) starttime = time.time() print(cube.shape) print("Fitting a total of %i spectra with peak signal above %f" % ((cube.max(axis=0) > mean_std).sum(),mean_std)) for i in xrange(cube.shape[1]): t0 = time.time() nspec = (cube[:,i,:].max(axis=0) > mean_std).sum() print("Working on row %d with %d spectra to fit" % (i,nspec), end=' ') for j in xrange(cube.shape[2]): if cube[:,i,j].max() > mean_std: pars = return_param(cube[:,i,j]) width_arr[i,j] = pars[1] chi2_arr[i,j] = sum(( gerr(cube[:,i,j])(pars) )**2) offset_arr[i,j] = pars[0] amp_arr[i,j] = pars[2] else: width_arr[i,j] = numpy.nan chi2_arr[i,j] = numpy.nan offset_arr[i,j] = numpy.nan amp_arr[i,j] = numpy.nan dt = time.time()-t0 if nspec > 0: print("in %f seconds (average: %f)" % (dt,dt/float(nspec))) print("Total time %f seconds" % (time.time()-starttime)) return width_arr,offset_arr,amp_arr,chi2_arr
def n_lorentzian(pars=None,a=None,dx=None,width=None): """ Returns a function that sums over N lorentzians, where N is the length of a,dx,sigma *OR* N = len(pars) / 3 The background "height" is assumed to be zero (you must "baseline" your spectrum before fitting) pars - a list with len(pars) = 3n, assuming a,dx,sigma repeated dx - offset (velocity center) values width - line widths (Lorentzian FWHM) a - amplitudes """ if len(pars) % 3 == 0: a = [pars[ii] for ii in xrange(0,len(pars),3)] dx = [pars[ii] for ii in xrange(1,len(pars),3)] width = [pars[ii] for ii in xrange(2,len(pars),3)] elif not(len(dx) == len(width) == len(a)): raise ValueError("Wrong array lengths! dx: %i width %i a: %i" % (len(dx),len(width),len(a))) def L(x): v = numpy.zeros(len(x)) for i in range(len(dx)): v += a[i] / (2*pi) * w / ((x-dx)**2 + (w/2.0)**2) return v return L
def n_gaussian(self, pars=None,a=None,dx=None,sigma=None): """ Returns a function that sums over N gaussians, where N is the length of a,dx,sigma *OR* N = len(pars) / 3 The background "height" is assumed to be zero (you must "baseline" your spectrum before fitting) pars - a list with len(pars) = 3n, assuming a,dx,sigma repeated dx - offset (velocity center) values sigma - line widths a - amplitudes """ if len(pars) % 3 == 0: a = [pars[ii] for ii in xrange(0,len(pars),3)] dx = [pars[ii] for ii in xrange(1,len(pars),3)] sigma = [pars[ii] for ii in xrange(2,len(pars),3)] elif not(len(dx) == len(sigma) == len(a)): raise ValueError("Wrong array lengths! dx: %i sigma: %i a: %i" % (len(dx),len(sigma),len(a))) def g(x): v = numpy.zeros(len(x)) for ii in range(len(pars)/3): v += a[ii] * numpy.exp( - ( x - dx[ii] )**2 / (2.0*sigma[ii]**2) ) return v return g
def n_gaussian(self, pars=None, a=None, dx=None, sigma=None): """ Returns a function that sums over N gaussians, where N is the length of a,dx,sigma *OR* N = len(pars) / 3 The background "height" is assumed to be zero (you must "baseline" your spectrum before fitting) pars - a list with len(pars) = 3n, assuming a,dx,sigma repeated dx - offset (velocity center) values sigma - line widths a - amplitudes """ if len(pars) % 3 == 0: a = [pars[ii] for ii in xrange(0, len(pars), 3)] dx = [pars[ii] for ii in xrange(1, len(pars), 3)] sigma = [pars[ii] for ii in xrange(2, len(pars), 3)] elif not (len(dx) == len(sigma) == len(a)): raise ValueError("Wrong array lengths! dx: %i sigma: %i a: %i" % (len(dx), len(sigma), len(a))) def g(x): v = numpy.zeros(len(x)) for ii in range(len(pars) / 3): v += a[ii] * numpy.exp(-(x - dx[ii])**2 / (2.0 * sigma[ii]**2)) return v return g
def collapse_gaussfit(cube, axis=2): std_coll = cube.std(axis=axis) mean_std = median(std_coll.ravel()) if axis > 0: cube = cube.swapaxes(0, axis) width_arr = zeros(cube.shape[1:]) amp_arr = zeros(cube.shape[1:]) chi2_arr = zeros(cube.shape[1:]) offset_arr = zeros(cube.shape[1:]) starttime = time.time() print cube.shape print "Fitting a total of %i spectra with peak signal above %f" % ( (cube.max(axis=0) > mean_std).sum(), mean_std) for i in xrange(cube.shape[1]): t0 = time.time() nspec = (cube[:, i, :].max(axis=0) > mean_std).sum() print "Working on row %d with %d spectra to fit" % (i, nspec), for j in xrange(cube.shape[2]): if cube[:, i, j].max() > mean_std: pars = return_param(cube[:, i, j]) width_arr[i, j] = pars[1] chi2_arr[i, j] = sum((gerr(cube[:, i, j])(pars))**2) offset_arr[i, j] = pars[0] amp_arr[i, j] = pars[2] else: width_arr[i, j] = numpy.nan chi2_arr[i, j] = numpy.nan offset_arr[i, j] = numpy.nan amp_arr[i, j] = numpy.nan dt = time.time() - t0 if nspec > 0: print "in %f seconds (average: %f)" % (dt, dt / float(nspec)) print "Total time %f seconds" % (time.time() - starttime) return width_arr, offset_arr, amp_arr, chi2_arr
def n_lorentzian(pars=None, a=None, dx=None, width=None): """ Returns a function that sums over N lorentzians, where N is the length of a,dx,sigma *OR* N = len(pars) / 3 The background "height" is assumed to be zero (you must "baseline" your spectrum before fitting) pars - a list with len(pars) = 3n, assuming a,dx,sigma repeated dx - offset (velocity center) values width - line widths (Lorentzian FWHM) a - amplitudes """ if len(pars) % 3 == 0: a = [pars[ii] for ii in xrange(0, len(pars), 3)] dx = [pars[ii] for ii in xrange(1, len(pars), 3)] width = [pars[ii] for ii in xrange(2, len(pars), 3)] elif not (len(dx) == len(width) == len(a)): raise ValueError("Wrong array lengths! dx: %i width %i a: %i" % (len(dx), len(width), len(a))) def L(x): v = numpy.zeros(len(x)) for i in range(len(dx)): v += a[i] / (2 * pi) * w / ((x - dx)**2 + (w / 2.0)**2) return v return L
def poly_fitter(order=1): """ Generator for polynomial fitter class """ myclass = model.SpectralModel(polymodel, order+1, parnames=['coeff%i' % ii for ii in xrange(order+1)], parlimited=[(False,False) for ii in xrange(order+1)], parlimits=[(0,0) for ii in xrange(order+1)], shortvarnames=['C%i' % ii for ii in xrange(order+1)] ) myclass.__name__ = "polymodel" return myclass
def poly_fitter(order=1): """ Generator for polynomial fitter class """ myclass = model.SpectralModel( polymodel, order + 1, parnames=['coeff%i' % ii for ii in xrange(order + 1)], parlimited=[(False, False) for ii in xrange(order + 1)], parlimits=[(0, 0) for ii in xrange(order + 1)], shortvarnames=['C%i' % ii for ii in xrange(order + 1)]) myclass.__name__ = "polymodel" return myclass
def get_chunks(num_items, chunk): ''' Parameters ---------- num_items : int Number of total items. chunk : int Size of chunks Returns ------- chunks : list of np.ndarray List of channels in chunks of the given size. ''' items = np.arange(num_items) if num_items == chunk: return [items] chunks = \ np.array_split(items, [chunk * i for i in xrange(int(num_items / chunk))]) if chunks[-1].size == 0: # Last one is empty chunks = chunks[:-1] if chunks[0].size == 0: # First one is empty chunks = chunks[1:] return chunks
def tableprint(self, item_length=15, numbered=True): """ Print data in table-friendly format Parameters ---------- item_length : int Number of characters per item printed numbered : bool Are the parameters numbered? In pyspeckit, they will always be, but this was included for compatibility with generic fitters """ stripped_names = list( set([par.parname.strip("0123456789") for par in self])) nlines = len(self.n) / len(stripped_names) strformat = "%" + str(item_length) + "s" fltformat = "%" + str(item_length) + "g" print(" ".join([strformat % name for name in stripped_names])) if numbered: for ii in xrange(nlines): print(" ".join([ fltformat % (self[name + "%i" % ii].value) for name in stripped_names ])) else: print(" ".join( [fltformat % (self[name].value) for name in stripped_names]))
def tableprint(self, item_length=15, numbered=True): """ Print data in table-friendly format Parameters ---------- item_length : int Number of characters per item printed numbered : bool Are the parameters numbered? In pyspeckit, they will always be, but this was included for compatibility with generic fitters """ stripped_names = list(set([par.parname.strip("0123456789") for par in self])) nlines = len(self.n) / len(stripped_names) strformat = "%" + str(item_length) + "s" fltformat = "%" + str(item_length) + "g" print(" ".join([strformat % name for name in stripped_names])) if numbered: for ii in xrange(nlines): print(" ".join([fltformat % (self[name+"%i" % ii].value) for name in stripped_names])) else: print(" ".join([fltformat % (self[name].value) for name in stripped_names]))
def plane_smooth(cube,cubedim=0,parallel=True,numcores=None,**kwargs): """ parallel-map the smooth function Parameters ---------- parallel: bool defaults True. Set to false if you want serial (for debug purposes?) numcores: int pass to parallel_map (None = use all available) """ if not smoothOK: return if cubedim != 0: cube = cube.swapaxes(0,cubedim) cubelist = [cube[ii,:,:] for ii in xrange(cube.shape[0])] Psmooth = lambda C: smooth(C,**kwargs) if parallel: smoothcube = array(parallel_map(Psmooth,cubelist,numcores=numcores)) else: smoothcube = array(map(Psmooth,cubelist)) if cubedim != 0: smoothcube = smoothcube.swapaxes(0,cubedim) return smoothcube
def plane_smooth(cube, cubedim=0, parallel=True, numcores=None, **kwargs): """ parallel-map the smooth function Parameters ---------- parallel: bool defaults True. Set to false if you want serial (for debug purposes?) numcores: int pass to parallel_map (None = use all available) """ if not smoothOK: return if cubedim != 0: cube = cube.swapaxes(0, cubedim) cubelist = [cube[ii, :, :] for ii in xrange(cube.shape[0])] Psmooth = lambda C: smooth(C, **kwargs) if parallel: smoothcube = array(parallel_map(Psmooth, cubelist, numcores=numcores)) else: smoothcube = array(map(Psmooth, cubelist)) if cubedim != 0: smoothcube = smoothcube.swapaxes(0, cubedim) return smoothcube
def clear(self, nbytes): """ Write nbytes of zeros. """ blank_data = b'\0' * self.block_size for i in xrange(0, nbytes, self.block_size): length = min(nbytes - i, self.block_size) self.write(blank_data[:length])
def _array_fromfile(fd, size): chunk_size = 1024 ** 3 if size < chunk_size: return np.fromfile(fd, dtype=np.uint8, count=size) else: array = np.empty(size, dtype=np.uint8) for beg in xrange(0, size, chunk_size): end = min(size, beg + chunk_size) array[beg:end] = np.fromfile(fd, dtype=np.uint8, count=end - beg) return array
def firstclick_guess(self): """ Initialize self.guesses """ self.Spectrum.plotter.axis.set_autoscale_on(False) if self.guesses is None: self.guesses = [] elif len(self.guesses) > 0: for ii in xrange(len(self.guesses)): self.guesses.pop()
def compute_luminosity(self, pars): """ Determine luminosity of line (need distance and flux units). """ lum = 0 niter = (len(pars) / 3) for i in xrange(int(niter)): lum += self.compute_flux(pars) * 4. * np.pi * self.d**2 return lum
def compute_amplitude(self, pars): """ Calculate amplitude of emission line. Should be easy - add multiple components if they exist. Currently assumes multiple components have the same centroid. """ amp = 0 niter = (len(pars) / 3) for i in xrange(int(niter)): amp += pars[3 * i] return amp * self.fluxnorm
def formaldehyde_pyradex(xarr, density=4, column=13, temperature=20, xoff_v=0.0, opr=1.0, width=1.0, tbackground=2.73, grid_vwidth=1.0, debug=False, verbose=False, **kwargs): """ Use a grid of RADEX-computed models to make a model line spectrum The RADEX models have to be available somewhere. OR they can be passed as arrays. If as arrays, the form should be: texgrid = ((minfreq1,maxfreq1,texgrid1),(minfreq2,maxfreq2,texgrid2)) xarr must be a SpectroscopicAxis instance xoff_v, width are both in km/s grid_vwidth is the velocity assumed when computing the grid in km/s this is important because tau = modeltau / width (see, e.g., Draine 2011 textbook pgs 219-230) """ raise NotImplementedError("Not done yet.") import pyradex # Convert X-units to frequency in GHz xarr = xarr.as_unit('Hz', quiet=True) tb_nu_cumul = np.zeros(len(xarr)) R = pyradex.Radex( molecule='oh2co-h2', column=column, temperature=temperature, density=10**density, tbackground=tbackground, ) spec = np.sum( [(formaldehyde_vtau(xarr, Tex=float(tex[ii]), tau=float(tau[ii]), xoff_v=xoff_v, width=width, **kwargs) * (xarr.as_unit('GHz') > minfreq[ii]) * (xarr.as_unit('GHz') < maxfreq[ii])) for ii in xrange(len(tex))], axis=0) return spec
def read_blocks(self, size): """ Read ``size`` bytes of data from the file, one block at a time. The result is a generator where each value is a bytes object. """ i = 0 for i in xrange(0, size - self._blksize, self._blksize): yield self.read(self._blksize) if i < size: yield self.read(size - i)
def compute_flux(self, pars): """ Calculate integrated flux of emission line. Works for multi-component fits too. Unnormalized. """ flux = 0 niter = (len(pars) / 3) assert niter == int(niter) for i in xrange(int(niter)): flux += np.sqrt(2. * np.pi) * pars[3 * i] * abs(pars[2 + 3 * i]) return flux * self.fluxnorm
def n_formaldehyde(self, pars=None, fittau=False, **kwargs): """ Returns a function that sums over N ammonia line profiles, where N is the length of tkin,tex,Ntot,width,xoff_v,fortho *OR* N = len(pars) / 6 The background "height" is assumed to be zero (you must "baseline" your spectrum before fitting) pars - a list with len(pars) = 6n, assuming tkin,tex,Ntot,width,xoff_v,fortho repeated """ if len(pars) % 6 == 0: tkin = [pars[ii] for ii in xrange(0,len(pars),6)] tex = [pars[ii] for ii in xrange(1,len(pars),6)] Ntot = [pars[ii] for ii in xrange(2,len(pars),6)] width = [pars[ii] for ii in xrange(3,len(pars),6)] xoff_v = [pars[ii] for ii in xrange(4,len(pars),6)] fortho = [pars[ii] for ii in xrange(5,len(pars),6)] elif not(len(tkin) == len(tex) == len(Ntot) == len(xoff_v) == len(width) == len(fortho)): raise ValueError("Wrong array lengths!") modelkwargs = kwargs.copy() def L(x): v = np.zeros(len(x)) for i in range(len(tkin)): modelkwargs.update({'tkin':tkin[i], 'tex':tex[i], 'width':width[i], 'xoff_v':xoff_v[i], 'fortho':fortho[i]}) if fittau: modelkwargs.update({'tau11':Ntot[i]}) else: modelkwargs.update({'Ntot':Ntot[i]}) v += self.ammonia(x,**modelkwargs) return v return L
def formaldehyde_mm_despotic(xarr, temperature=25, column=13, density=4, xoff_v=0.0, width=1.0, grid_vwidth=1.0, h2co_303_202=None, h2co_322_221=None, h2co_321_220=None, debug=False, verbose=False, **kwargs): """ Fitter to p-H2CO using despotic grids. Requires building grids and passing in functions for interpolating the h2co transition optical depth and excitation temperatures. """ Tex303_202, tau303_202 = h2co_303_202(logdensity=density, logcolumn=column, temperature=temperature, sigmav=width) Tex322_221, tau322_221 = h2co_322_221(logdensity=density, logcolumn=column, temperature=temperature, sigmav=width) Tex321_220, tau321_220 = h2co_321_220(logdensity=density, logcolumn=column, temperature=temperature, sigmav=width) tex = [Tex303_202, Tex322_221, Tex321_220] tau = [tau303_202, tau322_221, tau321_220] minfreq = [218.15, 218.40, 218.7] maxfreq = [218.25, 218.55, 218.8] spec = np.sum([(formaldehyde_mm_vtau(xarr, Tex=float(tex[ii]), tau=float(tau[ii]), xoff_v=xoff_v, width=width, **kwargs) * (xarr.as_unit('GHz').value > minfreq[ii]) * (xarr.as_unit('GHz').value < maxfreq[ii])) for ii in xrange(len(tex))], axis=0) return spec
def update_information(self, **kwargs): self.information = [ ("Baseline Range","(%g,%g)" % (self.baseline.xmin,self.baseline.xmax)), ("Baseline Order","%i" % (self.baseline.order)), ("Baseline Subtracted?","%s" % (self.baseline.subtracted)), ("Fitter Range","(%g,%g)" % (self.specfit.xmin,self.specfit.xmax)), ("Fitter Type","%s" % (self.specfit.fittype)), ] for ii in xrange(self.specfit.npeaks): guesses = tuple(self.specfit.guesses[ii:ii+3]) if len(guesses) == 3: self.information += [("Fitter guesses%i:" % ii , "p: %g c: %g w: %g" % guesses) ] else: break self.show_labels(**kwargs)
def read_galex(fitsfilename, orderselection='obj'): """ Read in a GALEX xg-gsp.fits file Definition of the file is here: `<http://www.galex.caltech.edu/DATA/gr1_docs/GR1_Column_descriptions_for_-xg-gsp.fits.htm>`_ Parameters ---------- fitsfilename: string The file name (must be a valid FITS file) orderselection: {'obj','objs'} 'obj' selects the NUV 1st and FUV 2nd orders 'objs' selects the NUV 2nd and FUV 3rd orders (higher resolution, lower S/N) Example ------- # from http://galex.stsci.edu/GR6/?page=downloadlist&tilenum=5697&type=coaddS $ wget http://galex.stsci.edu/data/GR6/pipe/01-vsn/05697-VAR_J000836p545700/g/01-main/0001-img/07-try/VAR_J000836p545700-xg-gsp.fits.gz $ gunzip VAR_J000836p545700-xg-gsp.fits.gz >>> spblock = read_galex('VAR_J000836p545700-xg-gsp.fits') >>> spavg = spblock.average() # average over all spectra of the source (naive) >>> spblock.plotter() >>> spavg.plotter() """ from .. import classes ff = pyfits.open(fitsfilename) bintable = ff[1].data # wavelength in angstroms wavelength = bintable.zero + np.arange(len(bintable.disp)) * bintable.disp xaxis = units.SpectroscopicAxis(wavelength, units='angstroms') splist = [ Spectrum(data=bintable[orderselection][:, ii], error=bintable[orderselection + "err"][:, ii], xarr=xaxis, header=ff[1].header) for ii in xrange(bintable[orderselection].shape[1]) ] galexblock = ObsBlock(splist) return galexblock
def _remove_cut_lines(self): """ Removes any cutlines that may already have existed in the input polygons. This is so any cutlines in the final result will be optimized to be as short as possible and won't intersect each other. This works by finding coincident edges that are reverse to each other, and then splicing around them. """ # As this proceeds, edges are removed from the graph. It # iterates over a static list of all edges that exist at the # start, so each time one is selected, we need to ensure it # still exists as part of the graph. # This transforms the following (where = is the cut line) # # \ / # A' + + B' # | | # A +====================+ B # # D +====================+ C # | | # D' + + C' # / \ # # to this: # # \ / # A' + + B' # | | # A + + C # | | # D' + + C' # / \ # edges = list(self._edges) for i in xrange(len(edges)): AB = edges[i] if AB not in self._edges: continue A, B = AB._nodes if len(A._edges) == 3 and len(B._edges) == 3: self._remove_edge(AB)
def formaldehyde_mm_despotic(xarr, temperature=25, column=13, density=4, xoff_v=0.0, width=1.0, grid_vwidth=1.0, h2co_303_202=None, h2co_322_221=None, h2co_321_220=None, debug=False, verbose=False, **kwargs): """ Fitter to p-H2CO using despotic grids. Requires building grids and passing in functions for interpolating the h2co transition optical depth and excitation temperatures. """ Tex303_202, tau303_202 = h2co_303_202(logdensity=density, logcolumn=column, temperature=temperature, sigmav=width) Tex322_221, tau322_221 = h2co_322_221(logdensity=density, logcolumn=column, temperature=temperature, sigmav=width) Tex321_220, tau321_220 = h2co_321_220(logdensity=density, logcolumn=column, temperature=temperature, sigmav=width) tex = [Tex303_202, Tex322_221, Tex321_220] tau = [tau303_202, tau322_221, tau321_220] minfreq = [218.15, 218.40, 218.7] maxfreq = [218.25, 218.55, 218.8] spec = np.sum([ (formaldehyde_mm_vtau(xarr, Tex=float(tex[ii]), tau=float(tau[ii]), xoff_v=xoff_v, width=width, **kwargs) * (xarr.as_unit('GHz').value>minfreq[ii]) * (xarr.as_unit('GHz').value<maxfreq[ii])) for ii in xrange(len(tex))], axis=0) return spec
def update_information(self, **kwargs): self.information = [ ("Baseline Range", "(%g,%g)" % (self.baseline.xmin, self.baseline.xmax)), ("Baseline Order", "%i" % (self.baseline.order)), ("Baseline Subtracted?", "%s" % (self.baseline.subtracted)), ("Fitter Range", "(%g,%g)" % (self.specfit.xmin, self.specfit.xmax)), ("Fitter Type", "%s" % (self.specfit.fittype)), ] for ii in xrange(self.specfit.npeaks): guesses = tuple(self.specfit.guesses[ii:ii + 3]) if len(guesses) == 3: self.information += [("Fitter guesses%i:" % ii, "p: %g c: %g w: %g" % guesses)] else: break self.show_labels(**kwargs)
def load_IRAF_multispec(fitsfilename): """ Load an IRAF multispec file as a list of spectra (a list of spectra can be passed to `pyspeckit.ObsBlock` if they are all of the same wavelength regime, but different objects or different spectra of the same object. They can be passed to `pyspeckit.Spectra` if, e.g., you have an Echelle spectrum """ fitsfile = pyfits.open(fitsfilename) data, err, xax, header = read_echelle(fitsfile[0]) speclist = [Spectrum(data=data[ii,:], error=err[ii,:], xarr=xax[ii,:], header=header) for ii in xrange(data.shape[0])] return speclist
def read_galex(fitsfilename, orderselection='obj'): """ Read in a GALEX xg-gsp.fits file Definition of the file is here: `<http://www.galex.caltech.edu/DATA/gr1_docs/GR1_Column_descriptions_for_-xg-gsp.fits.htm>`_ Parameters ---------- fitsfilename: string The file name (must be a valid FITS file) orderselection: {'obj','objs'} 'obj' selects the NUV 1st and FUV 2nd orders 'objs' selects the NUV 2nd and FUV 3rd orders (higher resolution, lower S/N) Example ------- # from http://galex.stsci.edu/GR6/?page=downloadlist&tilenum=5697&type=coaddS $ wget http://galex.stsci.edu/data/GR6/pipe/01-vsn/05697-VAR_J000836p545700/g/01-main/0001-img/07-try/VAR_J000836p545700-xg-gsp.fits.gz $ gunzip VAR_J000836p545700-xg-gsp.fits.gz >>> spblock = read_galex('VAR_J000836p545700-xg-gsp.fits') >>> spavg = spblock.average() # average over all spectra of the source (naive) >>> spblock.plotter() >>> spavg.plotter() """ from .. import classes ff = pyfits.open(fitsfilename) bintable = ff[1].data # wavelength in angstroms wavelength = bintable.zero + np.arange(len(bintable.disp)) * bintable.disp xaxis = units.SpectroscopicAxis(wavelength,units='angstroms') splist = [Spectrum(data=bintable[orderselection][:,ii], error=bintable[orderselection+"err"][:,ii], xarr=xaxis, header=ff[1].header) for ii in xrange(bintable[orderselection].shape[1])] galexblock = ObsBlock(splist) return galexblock
def n_formaldehyde(self, pars=None, fittau=False, **kwargs): """ Returns a function that sums over N ammonia line profiles, where N is the length of tkin,tex,Ntot,width,xoff_v,fortho *OR* N = len(pars) / 6 The background "height" is assumed to be zero (you must "baseline" your spectrum before fitting) pars - a list with len(pars) = 6n, assuming tkin,tex,Ntot,width,xoff_v,fortho repeated """ if len(pars) % 6 == 0: tkin = [pars[ii] for ii in xrange(0, len(pars), 6)] tex = [pars[ii] for ii in xrange(1, len(pars), 6)] Ntot = [pars[ii] for ii in xrange(2, len(pars), 6)] width = [pars[ii] for ii in xrange(3, len(pars), 6)] xoff_v = [pars[ii] for ii in xrange(4, len(pars), 6)] fortho = [pars[ii] for ii in xrange(5, len(pars), 6)] elif not (len(tkin) == len(tex) == len(Ntot) == len(xoff_v) == len(width) == len(fortho)): raise ValueError("Wrong array lengths!") modelkwargs = kwargs.copy() def L(x): v = np.zeros(len(x)) for i in range(len(tkin)): modelkwargs.update({ 'tkin': tkin[i], 'tex': tex[i], 'width': width[i], 'xoff_v': xoff_v[i], 'fortho': fortho[i] }) if fittau: modelkwargs.update({'tau11': Ntot[i]}) else: modelkwargs.update({'Ntot': Ntot[i]}) v += self.ammonia(x, **modelkwargs) return v return L
def fitnh3tkin(input_dict, dobaseline=True, baselinekwargs={}, crop=False, guessline='twotwo', tex=15, tkin=20, column=15.0, fortho=0.66, tau=None, thin=False, quiet=False, doplot=True, fignum=1, guessfignum=2, smooth=False, scale_keyword=None, rebase=False, npeaks=1, guesses=None, **kwargs): """ Given a dictionary of filenames and lines, fit them together e.g. {'oneone':'G000.000+00.000_nh3_11.fits'} """ spdict = dict([(linename, Spectrum(value, scale_keyword=scale_keyword)) if type(value) is str else (linename, value) for linename, value in iteritems(input_dict)]) splist = spdict.values() for sp in splist: # required for plotting, cropping sp.xarr.convert_to_unit('km/s') if crop and len(crop) == 2: for sp in splist: sp.crop(*crop) if dobaseline: for sp in splist: sp.baseline(**baselinekwargs) if smooth and type(smooth) is int: for sp in splist: sp.smooth(smooth) spdict[guessline].specfit(fittype='gaussian', negamp=False, vheight=False, guesses='moments') ampguess, vguess, widthguess = spdict[guessline].specfit.modelpars if widthguess < 0: raise ValueError("Width guess was < 0. This is impossible.") print("RMS guess (errspec): ", spdict[guessline].specfit.errspec.mean()) print("RMS guess (residuals): ", spdict[guessline].specfit.residuals.std()) errguess = spdict[guessline].specfit.residuals.std() if rebase: # redo baseline subtraction excluding the centroid +/- about 20 km/s vlow = spdict[guessline].specfit.modelpars[1] - ( 19.8 + spdict[guessline].specfit.modelpars[2] * 2.35) vhigh = spdict[guessline].specfit.modelpars[1] + ( 19.8 + spdict[guessline].specfit.modelpars[2] * 2.35) for sp in splist: sp.baseline(exclude=[vlow, vhigh], **baselinekwargs) for sp in splist: sp.error[:] = errguess if doplot: spdict[guessline].plotter(figure=guessfignum) spdict[guessline].specfit.plot_fit() spectra = Spectra(splist) spectra.specfit.npeaks = npeaks if tau is not None: if guesses is None: guesses = [ a for i in xrange(npeaks) for a in (tkin + random.random() * i, tex, tau + random.random() * i, widthguess + random.random() * i, vguess + random.random() * i, fortho) ] spectra.specfit(fittype='ammonia_tau', quiet=quiet, multifit=None, guesses=guesses, thin=thin, **kwargs) else: if guesses is None: guesses = [ a for i in xrange(npeaks) for a in (tkin + random.random() * i, tex, column + random.random() * i, widthguess + random.random() * i, vguess + random.random() * i, fortho) ] spectra.specfit(fittype='ammonia', quiet=quiet, multifit=None, guesses=guesses, thin=thin, **kwargs) if doplot: plot_nh3(spdict, spectra, fignum=fignum) return spdict, spectra
def test_ordering(): nrepeat = 10 A = polygon.SphericalPolygon( [[3.532808036921135653e-01, 6.351523005458726834e-01, -6.868582305351954576e-01], [3.532781068942476010e-01, 6.351564219435104075e-01, -6.868558064493115456e-01], [3.529538811375814156e-01, 6.351027504797477352e-01, -6.870720880104047579e-01], [3.533428330964511477e-01, 6.345142927049303161e-01, -6.874157800432978416e-01], [3.533486351814376647e-01, 6.345151843837375516e-01, -6.874119745843003670e-01], [3.533513056857608414e-01, 6.345111416839894769e-01, -6.874143334620310686e-01], [3.536740696809928530e-01, 6.345607036635456666e-01, -6.872025653337667794e-01], [3.536713200704008631e-01, 6.345649108795897719e-01, -6.872000954889618818e-01], [3.536761865498951884e-01, 6.345656515431040701e-01, -6.871969069700470945e-01], [3.536788213460497765e-01, 6.345616140129455296e-01, -6.871992792142280759e-01], [3.540056257094351122e-01, 6.346113105009757449e-01, -6.869850810245486938e-01], [3.536200722272911379e-01, 6.352081961257413090e-01, -6.866319189293832448e-01], [3.536142814048366390e-01, 6.352072452054380314e-01, -6.866357809093986964e-01], [3.536116196666648781e-01, 6.352113634102898310e-01, -6.866333419163089813e-01], [3.532833767830895755e-01, 6.351574192193063517e-01, -6.868521736876195272e-01], [3.532861440234288386e-01, 6.351531838825796861e-01, -6.868546669018701367e-01], [3.532808036921135653e-01, 6.351523005458726834e-01, -6.868582305351954576e-01]], [3.536414047913637448e-01, 6.348851549491377755e-01, -6.869196436573932196e-01]) B = polygon.SphericalPolygon( [[3.529249199274748783e-01, 6.356925960489819838e-01, -6.865412764158403958e-01], [3.533126219535084322e-01, 6.351003877952851040e-01, -6.868898664200949744e-01], [3.533173735956686712e-01, 6.351012981906917210e-01, -6.868865805589428053e-01], [3.529301898742857047e-01, 6.356935934402119237e-01, -6.865376437853726310e-01], [3.532584388080926563e-01, 6.357475490961038700e-01, -6.863188247667159070e-01], [3.536441982306618437e-01, 6.351510082118909661e-01, -6.866723948326530769e-01], [3.533173735956686712e-01, 6.351012981906917210e-01, -6.868865805589428053e-01], [3.533126219535084322e-01, 6.351003877952851040e-01, -6.868898664200949744e-01], [3.529898380712340189e-01, 6.350508125724935171e-01, -6.871016225198859351e-01], [3.526006883384300017e-01, 6.356389133339014341e-01, -6.867575456003104373e-01], [3.529249199274748783e-01, 6.356925960489819838e-01, -6.865412764158403958e-01]], [3.532883212044564125e-01, 6.354215160430938258e-01, -6.866053153377369433e-01]) areas = [] for i in xrange(nrepeat): C = A.intersection(B) areas.append(C.area()) areas = np.array(areas) assert_array_almost_equal(areas[:-1], areas[1:]) def roll_polygon(P, i): points = P.points points = np.roll(points[:-1], i, 0) points = np.append(points, [points[0]], 0) return polygon.SphericalPolygon(points, P.inside) Aareas = [] Bareas = [] Careas = [] for i in xrange(nrepeat): AS = roll_polygon(A, i) BS = roll_polygon(B, i) C = AS.intersection(BS) Aareas.append(A.area()) Bareas.append(B.area()) Careas.append(C.area()) for j in xrange(nrepeat): CS = roll_polygon(C, j) Careas.append(CS.area()) Aareas = np.array(Aareas) Bareas = np.array(Bareas) Careas = np.array(Careas) assert_array_almost_equal(Aareas[:-1], Aareas[1:]) assert_array_almost_equal(Bareas[:-1], Bareas[1:]) assert_array_almost_equal(Careas[:-1], Careas[1:])
def formaldehyde_radex_orthopara_temp(xarr, density=4, column=13, orthopara=1.0, temperature=15.0, xoff_v=0.0, width=1.0, Tbackground1=2.73, Tbackground2=2.73, grid_vwidth=1.0, grid_vwidth_scale=False, texgrid=None, taugrid=None, hdr=None, path_to_texgrid='', path_to_taugrid='', debug=False, verbose=False, getpars=False, **kwargs): """ Use a grid of RADEX-computed models to make a model line spectrum The RADEX models have to be available somewhere. OR they can be passed as arrays. If as arrays, the form should be: texgrid = ((minfreq1,maxfreq1,texgrid1),(minfreq2,maxfreq2,texgrid2)) xarr must be a SpectroscopicAxis instance xoff_v, width are both in km/s grid_vwidth is the velocity assumed when computing the grid in km/s this is important because tau = modeltau / width (see, e.g., Draine 2011 textbook pgs 219-230) grid_vwidth_scale is True or False: False for LVG, True for Sphere """ if texgrid is None and taugrid is None: if path_to_texgrid == '' or path_to_taugrid=='': raise IOError("Must specify model grids to use.") else: taugrid = [pyfits.getdata(path_to_taugrid)] texgrid = [pyfits.getdata(path_to_texgrid)] hdr = pyfits.getheader(path_to_taugrid) minfreq = (4.8,) maxfreq = (5.0,) elif len(taugrid)==len(texgrid) and hdr is not None: minfreq,maxfreq,texgrid = zip(*texgrid) minfreq,maxfreq,taugrid = zip(*taugrid) else: raise Exception densityarr = (np.arange(taugrid[0].shape[3])+hdr['CRPIX1']-1)*hdr['CD1_1']+hdr['CRVAL1'] # log density columnarr = (np.arange(taugrid[0].shape[2])+hdr['CRPIX2']-1)*hdr['CD2_2']+hdr['CRVAL2'] # log column temparr = (np.arange(taugrid[0].shape[1])+hdr['CRPIX3']-1)*hdr['CDELT3']+hdr['CRVAL3'] # temperature oprarr = (np.arange(taugrid[0].shape[0])+hdr['CRPIX4']-1)*hdr['CDELT4']+hdr['CRVAL4'] # log ortho/para ratio gridval1 = np.interp(density, densityarr, np.arange(len(densityarr))) gridval2 = np.interp(column, columnarr, np.arange(len(columnarr))) gridval3 = np.interp(temperature, temparr, np.arange(len(temparr))) gridval4 = np.interp(orthopara, oprarr, np.arange(len(oprarr))) if np.isnan(gridval1) or np.isnan(gridval2): raise ValueError("Invalid column/density") if scipyOK: slices = [slice(int(np.floor(gv)),int(np.floor(gv)+2)) for gv in (gridval4,gridval3,gridval2,gridval1)] tau = [scipy.ndimage.map_coordinates(tg[slices], np.array([[gridval4 % 1], [gridval3 % 1], [gridval2 % 1], [gridval1 % 1]]), order=1, prefilter=False) for tg in taugrid] tex = [scipy.ndimage.map_coordinates(tg[slices], np.array([[gridval4 % 1], [gridval3 % 1], [gridval2 % 1], [gridval1 % 1]]), order=1,prefilter=False) for tg in texgrid] else: raise ImportError("Couldn't import scipy, therefore cannot interpolate") #tau = modelgrid.line_params_2D(gridval1,gridval2,densityarr,columnarr,taugrid[temperature_gridnumber,:,:]) #tex = modelgrid.line_params_2D(gridval1,gridval2,densityarr,columnarr,texgrid[temperature_gridnumber,:,:]) # there can be different background temperatures at each frequency tbg = [Tbackground1,Tbackground2] if verbose: print("density %20.12g column: %20.12g temperature: %20.12g opr: %20.12g xoff_v: %20.12g width: %20.12g" % (density, column, temperature, orthopara, xoff_v, width)) print("tau: ",tau," tex: ",tex) print("minfreq: ",minfreq," maxfreq: ",maxfreq) print("tbg: ",tbg) if debug > 1: import pdb; pdb.set_trace() if getpars: return tau,tex spec = np.sum([(formaldehyde_vtau(xarr.as_unit('Hz', quiet=True), Tex=float(tex[ii]), tau=float(tau[ii]), Tbackground=tbg[ii], xoff_v=xoff_v, width=width, **kwargs) * (xarr.as_unit('GHz')>minfreq[ii]) * (xarr.as_unit('GHz')<maxfreq[ii])) for ii in xrange(len(tex))], axis=0) return spec
def formaldehyde_pyradex(xarr, density=4, column=13, temperature=20, xoff_v=0.0, opr=1.0, width=1.0, tbackground=2.73, grid_vwidth=1.0, debug=False, verbose=False, **kwargs): """ Use a grid of RADEX-computed models to make a model line spectrum The RADEX models have to be available somewhere. OR they can be passed as arrays. If as arrays, the form should be: texgrid = ((minfreq1,maxfreq1,texgrid1),(minfreq2,maxfreq2,texgrid2)) xarr must be a SpectroscopicAxis instance xoff_v, width are both in km/s grid_vwidth is the velocity assumed when computing the grid in km/s this is important because tau = modeltau / width (see, e.g., Draine 2011 textbook pgs 219-230) """ raise NotImplementedError("Not done yet.") import pyradex # Convert X-units to frequency in GHz xarr = xarr.as_unit('Hz', quiet=True) tb_nu_cumul = np.zeros(len(xarr)) R = pyradex.Radex(molecule='oh2co-h2', column=column, temperature=temperature, density=10**density, tbackground=tbackground,) spec = np.sum([(formaldehyde_vtau(xarr,Tex=float(tex[ii]),tau=float(tau[ii]),xoff_v=xoff_v,width=width, **kwargs) * (xarr.as_unit('GHz')>minfreq[ii]) * (xarr.as_unit('GHz')<maxfreq[ii])) for ii in xrange(len(tex))], axis=0) return spec
def values(self): return [self._dict[self._dict_index[ii]] for ii in xrange(len(self))]
def keys(self): return [self._dict_index[ii] for ii in xrange(len(self))]
agpy.pymc_plotting.hist2d(spmchdf5b, 'TEMPERATURE0', 'COLUMN0', doerrellipse=False, clear=True, bins=50,fignum=5) pl.savefig("h2co_mm_tem_vs_col_tmb2.png") agpy.pymc_plotting.hist2d(spmchdf5b, 'DENSITY0', 'COLUMN0', doerrellipse=False, clear=True, bins=50,fignum=6) pl.savefig("h2co_mm_den_vs_col_tmb2.png") spmchdf5d.sample(1e5) spmchdf5d.db.close() agpy.pymc_plotting.hist2d(spmchdf5d, 'TEMPERATURE0', 'DENSITY0', doerrellipse=False, clear=True, bins=50, fignum=4) pl.savefig("h2co_mm_tem_vs_den_tmb_vel.png") agpy.pymc_plotting.hist2d(spmchdf5d, 'TEMPERATURE0', 'COLUMN0', doerrellipse=False, clear=True, bins=50,fignum=5) pl.savefig("h2co_mm_tem_vs_col_tmb_vel.png") agpy.pymc_plotting.hist2d(spmchdf5d, 'DENSITY0', 'COLUMN0', doerrellipse=False, clear=True, bins=50,fignum=6) pl.savefig("h2co_mm_den_vs_col_tmb_vel.png") pars = dict([(k,spmchdf5b.trace(k)[-50:]) for k in sp3.specfit.parinfo.keys()]) for ii in xrange(0,50): sp3.specfit.plot_model( [pars[k][ii] for k in sp3.specfit.parinfo.keys()], clear=False, composite_fit_color='r', plotkwargs={'alpha':0.01}) sp3.plotter.autorefresh=False pars = dict([(k,spmchdf5d.trace(k)[-1000:]) for k in sp3.specfit.parinfo.keys()]) for ii in xrange(0,1000): sp3.specfit.plot_model( [pars[k][ii] for k in sp3.specfit.parinfo.keys()], clear=False, composite_fit_color='r', plotkwargs={'alpha':0.01}, composite_lw=2) sp3.plotter.refresh()
def formaldehyde_radex_tau(xarr, density=4, column=13, xoff_v=0.0, width=1.0, grid_vwidth=1.0, grid_vwidth_scale=False, taugrid=None, hdr=None, path_to_taugrid='', temperature_gridnumber=3, debug=False, verbose=False, return_hyperfine_components=False, **kwargs): """ Use a grid of RADEX-computed models to make a model line spectrum * uses hyperfine components * assumes *tau* varies but *tex* does not! The RADEX models have to be available somewhere. OR they can be passed as arrays. If as arrays, the form should be: texgrid = ((minfreq1,maxfreq1,texgrid1),(minfreq2,maxfreq2,texgrid2)) xarr must be a SpectroscopicAxis instance xoff_v, width are both in km/s grid_vwidth is the velocity assumed when computing the grid in km/s this is important because tau = modeltau / width (see, e.g., Draine 2011 textbook pgs 219-230) grid_vwidth_scale is True or False: False for LVG, True for Sphere """ if verbose: print("Parameters: dens=%f, column=%f, xoff=%f, width=%f" % (density, column, xoff_v, width)) if taugrid is None: if path_to_taugrid == '': raise IOError("Must specify model grids to use.") else: taugrid = [pyfits.getdata(path_to_taugrid)] hdr = pyfits.getheader(path_to_taugrid) yinds, xinds = np.indices(taugrid[0].shape[1:]) densityarr = (xinds + hdr['CRPIX1'] - 1) * hdr['CD1_1'] + hdr['CRVAL1'] # log density columnarr = (yinds + hdr['CRPIX2'] - 1) * hdr['CD2_2'] + hdr['CRVAL2'] # log column minfreq = (4.8, ) maxfreq = (5.0, ) elif hdr is not None: minfreq, maxfreq, taugrid = zip(*taugrid) yinds, xinds = np.indices(taugrid[0].shape[1:]) densityarr = (xinds + hdr['CRPIX1'] - 1) * hdr['CD1_1'] + hdr['CRVAL1'] # log density columnarr = (yinds + hdr['CRPIX2'] - 1) * hdr['CD2_2'] + hdr['CRVAL2'] # log column else: raise Exception # Convert X-units to frequency in GHz xarr = xarr.as_unit('Hz', quiet=True) gridval1 = np.interp(density, densityarr[0, :], xinds[0, :]) gridval2 = np.interp(column, columnarr[:, 0], yinds[:, 0]) if np.isnan(gridval1) or np.isnan(gridval2): raise ValueError("Invalid column/density") if scipyOK: slices = [temperature_gridnumber] + [ slice(np.floor(gv), np.floor(gv) + 2) for gv in (gridval2, gridval1) ] tau = [ scipy.ndimage.map_coordinates(tg[slices], np.array([[gridval2 % 1], [gridval1 % 1]]), order=1) for tg in taugrid ] else: raise ImportError( "Couldn't import scipy, therefore cannot interpolate") # let the hyperfine module determine the hyperfine components, and pass all of them here spec_components = [ (formaldehyde_vtau(xarr.as_unit('Hz', quiet=True), tau=float(tau[ii]), xoff_v=xoff_v, width=width, return_tau=True, return_hyperfine_components=True, **kwargs) * (xarr.as_unit('GHz') > minfreq[ii]) * (xarr.as_unit('GHz') < maxfreq[ii])) for ii in xrange(len(tau)) ] # get an array of [n_lines, n_hyperfine, len(xarr)] if return_hyperfine_components: return np.array(spec_components).sum(axis=0) else: return np.sum(spec_components, axis=0).sum(axis=0)
def multinh3fit(self, xax, data, npeaks=1, err=None, params=[20,20,1e10,1.0,0.0,0.5], fixed=[False,False,False,False,False,False], limitedmin=[True,True,True,True,False,True], limitedmax=[False,False,False,False,False,True], minpars=[2.73,2.73,0,0,0,0], maxpars=[0,0,0,0,0,1], quiet=True, shh=True, veryverbose=False, **kwargs): """ Fit multiple nh3 profiles Inputs: xax - x axis data - y axis npeaks - How many nh3 profiles to fit? Default 1 (this could supersede onedgaussfit) err - error corresponding to data These parameters need to have length = 6*npeaks. If npeaks > 1 and length = 6, they will be replicated npeaks times, otherwise they will be reset to defaults: params - Fit parameters: [amplitude, offset, Gfwhm, Lfwhm] * npeaks If len(params) % 6 == 0, npeaks will be set to len(params) / 6 fixed - Is parameter fixed? limitedmin/minpars - set lower limits on each parameter (default: width>0, Tex and Tkin > Tcmb) limitedmax/maxpars - set upper limits on each parameter quiet - should MPFIT output each iteration? shh - output final parameters? Returns: Fit parameters Model Fit errors chi2 """ self.npars = 6 if len(params) != npeaks and (len(params) / self.npars) > npeaks: npeaks = len(params) / self.npars self.npeaks = npeaks if isinstance(params,np.ndarray): params=params.tolist() # make sure all various things are the right length; if they're not, fix them using the defaults for parlist in (params,fixed,limitedmin,limitedmax,minpars,maxpars): if len(parlist) != self.npars*self.npeaks: # if you leave the defaults, or enter something that can be multiplied by 3 to get to the # right number of gaussians, it will just replicate if len(parlist) == self.npars: parlist *= npeaks elif parlist==params: parlist[:] = [20,20,1e10,1.0,0.0,0.5] * npeaks elif parlist==fixed: parlist[:] = [False,False,False,False,False,False] * npeaks elif parlist==limitedmax: parlist[:] = [False,False,False,False,False,True] * npeaks elif parlist==limitedmin: parlist[:] = [True,True,True,True,False,True] * npeaks elif parlist==minpars: parlist[:] = [2.73,0,0,0,0,0] * npeaks elif parlist==maxpars: parlist[:] = [0,0,0,0,0,1] * npeaks def mpfitfun(x,y,err): if err is None: def f(p,fjac=None): return [0,(y-self.n_ammonia(pars=p, **kwargs)(x))] else: def f(p,fjac=None): return [0,(y-self.n_ammonia(pars=p, **kwargs)(x))/err] return f parnames = {0:"TKIN",1:"TEX",2:"NTOT",3:"WIDTH",4:"XOFF_V",5:"FORTHO"} parinfo = [ {'n':ii, 'value':params[ii], 'limits':[minpars[ii],maxpars[ii]], 'limited':[limitedmin[ii],limitedmax[ii]], 'fixed':fixed[ii], 'parname':parnames[ii%self.npars]+str(ii/self.npars), 'mpmaxstep':0,'error':ii} for ii in xrange(len(params)) ] parinfo[0]['mpmaxstep'] = 1.0 parinfo[1]['mpmaxstep'] = 1.0 if veryverbose: print "GUESSES: " print "\n".join(["%s: %s" % (p['parname'],p['value']) for p in parinfo]) mp = mpfit(mpfitfun(xax,data,err),parinfo=parinfo,quiet=quiet) mpp = mp.params if mp.perror is not None: mpperr = mp.perror else: mpperr = mpp*0 chi2 = mp.fnorm if mp.status == 0: raise Exception(mp.errmsg) if not shh: print "Fit message: ",mp.errmsg print "Final fit values: " for i,p in enumerate(mpp): parinfo[i]['value'] = p print parinfo[i]['parname'],p," +/- ",mpperr[i] print "Chi2: ",mp.fnorm," Reduced Chi2: ",mp.fnorm/len(data)," DOF:",len(data)-len(mpp) if mpp[1] > mpp[0]: mpp[1] = mpp[0] # force Tex>Tkin to Tex=Tkin (already done in n_ammonia) self.mp = mp self.mpp = mpp self.mpperr = mpperr self.model = self.n_ammonia(pars=mpp,**kwargs)(xax) return mpp,self.n_ammonia(pars=mpp,**kwargs)(xax),mpperr,chi2
def formaldehyde_radex_orthopara_temp(xarr, density=4, column=13, orthopara=1.0, temperature=15.0, xoff_v=0.0, width=1.0, Tbackground1=2.73, Tbackground2=2.73, grid_vwidth=1.0, grid_vwidth_scale=False, texgrid=None, taugrid=None, hdr=None, path_to_texgrid='', path_to_taugrid='', debug=False, verbose=False, getpars=False, **kwargs): """ Use a grid of RADEX-computed models to make a model line spectrum The RADEX models have to be available somewhere. OR they can be passed as arrays. If as arrays, the form should be: texgrid = ((minfreq1,maxfreq1,texgrid1),(minfreq2,maxfreq2,texgrid2)) xarr must be a SpectroscopicAxis instance xoff_v, width are both in km/s grid_vwidth is the velocity assumed when computing the grid in km/s this is important because tau = modeltau / width (see, e.g., Draine 2011 textbook pgs 219-230) grid_vwidth_scale is True or False: False for LVG, True for Sphere """ if texgrid is None and taugrid is None: if path_to_texgrid == '' or path_to_taugrid == '': raise IOError("Must specify model grids to use.") else: taugrid = [pyfits.getdata(path_to_taugrid)] texgrid = [pyfits.getdata(path_to_texgrid)] hdr = pyfits.getheader(path_to_taugrid) minfreq = (4.8, ) maxfreq = (5.0, ) elif len(taugrid) == len(texgrid) and hdr is not None: minfreq, maxfreq, texgrid = zip(*texgrid) minfreq, maxfreq, taugrid = zip(*taugrid) else: raise Exception densityarr = (np.arange(taugrid[0].shape[3]) + hdr['CRPIX1'] - 1) * hdr['CD1_1'] + hdr['CRVAL1'] # log density columnarr = (np.arange(taugrid[0].shape[2]) + hdr['CRPIX2'] - 1) * hdr['CD2_2'] + hdr['CRVAL2'] # log column temparr = (np.arange(taugrid[0].shape[1]) + hdr['CRPIX3'] - 1) * hdr['CDELT3'] + hdr['CRVAL3'] # temperature oprarr = (np.arange(taugrid[0].shape[0]) + hdr['CRPIX4'] - 1) * hdr['CDELT4'] + hdr['CRVAL4'] # log ortho/para ratio gridval1 = np.interp(density, densityarr, np.arange(len(densityarr))) gridval2 = np.interp(column, columnarr, np.arange(len(columnarr))) gridval3 = np.interp(temperature, temparr, np.arange(len(temparr))) gridval4 = np.interp(orthopara, oprarr, np.arange(len(oprarr))) if np.isnan(gridval1) or np.isnan(gridval2): raise ValueError("Invalid column/density") if scipyOK: slices = [ slice(int(np.floor(gv)), int(np.floor(gv) + 2)) for gv in (gridval4, gridval3, gridval2, gridval1) ] tau = [ scipy.ndimage.map_coordinates(tg[slices], np.array([[gridval4 % 1], [gridval3 % 1], [gridval2 % 1], [gridval1 % 1]]), order=1, prefilter=False) for tg in taugrid ] tex = [ scipy.ndimage.map_coordinates(tg[slices], np.array([[gridval4 % 1], [gridval3 % 1], [gridval2 % 1], [gridval1 % 1]]), order=1, prefilter=False) for tg in texgrid ] else: raise ImportError( "Couldn't import scipy, therefore cannot interpolate") #tau = modelgrid.line_params_2D(gridval1,gridval2,densityarr,columnarr,taugrid[temperature_gridnumber,:,:]) #tex = modelgrid.line_params_2D(gridval1,gridval2,densityarr,columnarr,texgrid[temperature_gridnumber,:,:]) # there can be different background temperatures at each frequency tbg = [Tbackground1, Tbackground2] if verbose: print( "density %20.12g column: %20.12g temperature: %20.12g opr: %20.12g xoff_v: %20.12g width: %20.12g" % (density, column, temperature, orthopara, xoff_v, width)) print("tau: ", tau, " tex: ", tex) print("minfreq: ", minfreq, " maxfreq: ", maxfreq) print("tbg: ", tbg) if debug > 1: import pdb pdb.set_trace() if getpars: return tau, tex spec = np.sum( [(formaldehyde_vtau(xarr.as_unit('Hz', quiet=True), Tex=float(tex[ii]), tau=float(tau[ii]), Tbackground=tbg[ii], xoff_v=xoff_v, width=width, **kwargs) * (xarr.as_unit('GHz') > minfreq[ii]) * (xarr.as_unit('GHz') < maxfreq[ii])) for ii in xrange(len(tex))], axis=0) return spec
def formaldehyde_radex(xarr, density=4, column=13, xoff_v=0.0, width=1.0, grid_vwidth=1.0, grid_vwidth_scale=False, texgrid=None, taugrid=None, hdr=None, path_to_texgrid='', path_to_taugrid='', temperature_gridnumber=3, debug=False, verbose=False, **kwargs): """ Use a grid of RADEX-computed models to make a model line spectrum The RADEX models have to be available somewhere. OR they can be passed as arrays. If as arrays, the form should be: texgrid = ((minfreq1,maxfreq1,texgrid1),(minfreq2,maxfreq2,texgrid2)) xarr must be a SpectroscopicAxis instance xoff_v, width are both in km/s grid_vwidth is the velocity assumed when computing the grid in km/s this is important because tau = modeltau / width (see, e.g., Draine 2011 textbook pgs 219-230) grid_vwidth_scale is True or False: False for LVG, True for Sphere """ if texgrid is None and taugrid is None: if path_to_texgrid == '' or path_to_taugrid == '': raise IOError("Must specify model grids to use.") else: taugrid = [pyfits.getdata(path_to_taugrid)] texgrid = [pyfits.getdata(path_to_texgrid)] hdr = pyfits.getheader(path_to_taugrid) yinds, xinds = np.indices(taugrid[0].shape[1:]) densityarr = (xinds + hdr['CRPIX1'] - 1) * hdr['CD1_1'] + hdr['CRVAL1'] # log density columnarr = (yinds + hdr['CRPIX2'] - 1) * hdr['CD2_2'] + hdr['CRVAL2'] # log column minfreq = (4.8, ) maxfreq = (5.0, ) elif len(taugrid) == len(texgrid) and hdr is not None: minfreq, maxfreq, texgrid = zip(*texgrid) minfreq, maxfreq, taugrid = zip(*taugrid) yinds, xinds = np.indices(taugrid[0].shape[1:]) densityarr = (xinds + hdr['CRPIX1'] - 1) * hdr['CD1_1'] + hdr['CRVAL1'] # log density columnarr = (yinds + hdr['CRPIX2'] - 1) * hdr['CD2_2'] + hdr['CRVAL2'] # log column else: raise Exception # Convert X-units to frequency in GHz xarr = xarr.as_unit('Hz', quiet=True) tau_nu_cumul = np.zeros(len(xarr)) gridval1 = np.interp(density, densityarr[0, :], xinds[0, :]) gridval2 = np.interp(column, columnarr[:, 0], yinds[:, 0]) if np.isnan(gridval1) or np.isnan(gridval2): raise ValueError("Invalid column/density") if scipyOK: slices = [temperature_gridnumber] + [ slice(np.floor(gv), np.floor(gv) + 2) for gv in (gridval2, gridval1) ] tau = [ scipy.ndimage.map_coordinates(tg[slices], np.array([[gridval2 % 1], [gridval1 % 1]]), order=1) for tg in taugrid ] tex = [ scipy.ndimage.map_coordinates(tg[slices], np.array([[gridval2 % 1], [gridval1 % 1]]), order=1) for tg in texgrid ] else: raise ImportError( "Couldn't import scipy, therefore cannot interpolate") #tau = modelgrid.line_params_2D(gridval1,gridval2,densityarr,columnarr,taugrid[temperature_gridnumber,:,:]) #tex = modelgrid.line_params_2D(gridval1,gridval2,densityarr,columnarr,texgrid[temperature_gridnumber,:,:]) if verbose: print("density %20.12g column %20.12g: tau %20.12g tex %20.12g" % (density, column, tau, tex)) if debug: import pdb pdb.set_trace() spec = np.sum( [(formaldehyde_vtau(xarr, Tex=float(tex[ii]), tau=float(tau[ii]), xoff_v=xoff_v, width=width, **kwargs) * (xarr.as_unit('GHz') > minfreq[ii]) * (xarr.as_unit('GHz') < maxfreq[ii])) for ii in xrange(len(tex))], axis=0) return spec
def formaldehyde_mm_radex(xarr, temperature=25, column=13, density=4, xoff_v=0.0, width=1.0, grid_vwidth=1.0, texgrid=None, taugrid=None, hdr=None, path_to_texgrid='', path_to_taugrid='', debug=False, verbose=False, **kwargs): """ Use a grid of RADEX-computed models to make a model line spectrum The RADEX models have to be available somewhere. OR they can be passed as arrays. If as arrays, the form should be: texgrid = ((minfreq1,maxfreq1,texgrid1),(minfreq2,maxfreq2,texgrid2)) xarr must be a SpectroscopicAxis instance xoff_v, width are both in km/s Parameters ---------- grid_vwidth : float the velocity assumed when computing the grid in km/s this is important because tau = modeltau / width (see, e.g., Draine 2011 textbook pgs 219-230) density : float Density! """ if texgrid is None and taugrid is None: if path_to_texgrid == '' or path_to_taugrid=='': raise IOError("Must specify model grids to use.") else: taugrid = [pyfits.getdata(path_to_taugrid)] texgrid = [pyfits.getdata(path_to_texgrid)] hdr = pyfits.getheader(path_to_taugrid) zinds,yinds,xinds = np.indices(taugrid[0].shape) if 'CD1_1' in hdr: cd11 = 'CD1_1' cd22 = 'CD2_2' else: cd11 = 'CDELT1' cd22 = 'CDELT2' densityarr = (xinds+hdr['CRPIX1']-1)*hdr[cd11]+hdr['CRVAL1'] # log density columnarr = (yinds+hdr['CRPIX2']-1)*hdr[cd22]+hdr['CRVAL2'] # log column temparr = (zinds+hdr['CRPIX3']-1)*hdr['CDELT3']+hdr['CRVAL3'] # lin temperature minfreq = (218.,) maxfreq = (219.,) elif len(taugrid)==len(texgrid) and hdr is not None: minfreq,maxfreq,texgrid = zip(*texgrid) minfreq,maxfreq,taugrid = zip(*taugrid) zinds,yinds,xinds = np.indices(taugrid[0].shape) if 'CD1_1' in hdr: cd11 = 'CD1_1' cd22 = 'CD2_2' else: cd11 = 'CDELT1' cd22 = 'CDELT2' densityarr = (xinds+hdr['CRPIX1']-1)*hdr[cd11]+hdr['CRVAL1'] # log density columnarr = (yinds+hdr['CRPIX2']-1)*hdr[cd22]+hdr['CRVAL2'] # log column temparr = (zinds+hdr['CRPIX3']-1)*hdr['CDELT3']+hdr['CRVAL3'] # lin temperature else: raise Exception # Convert X-units to frequency in GHz xarr = xarr.as_unit('Hz', quiet=True) #tau_nu_cumul = np.zeros(len(xarr)) gridval1 = np.interp(density, densityarr[0,0,:], xinds[0,0,:]) gridval2 = np.interp(column, columnarr[0,:,0], yinds[0,:,0]) gridval3 = np.interp(temperature, temparr[:,0,0], zinds[:,0,0]) if np.isnan(gridval1) or np.isnan(gridval2) or np.isnan(gridval3): raise ValueError("Invalid column/density") if scipyOK: # this is mostly a trick for speed: slice so you only have two thin layers to interpolate # between #slices = [density_gridnumber] + [slice(np.floor(gv),np.floor(gv)+2) for gv in (gridval2,gridval1)] slices = [slice(np.floor(gridval3),np.floor(gridval3)+2), slice(np.floor(gridval2),np.floor(gridval2)+2), slice(np.floor(gridval1),np.floor(gridval1)+2) ] tau = [scipy.ndimage.map_coordinates(tg[slices], np.array([[gridval3%1],[gridval2%1],[gridval1%1]]),order=1) for tg in taugrid] tex = [scipy.ndimage.map_coordinates(tg[slices], np.array([[gridval3%1],[gridval2%1],[gridval1%1]]),order=1) for tg in texgrid] else: raise ImportError("Couldn't import scipy, therefore cannot interpolate") #tau = modelgrid.line_params_2D(gridval1,gridval2,densityarr,columnarr,taugrid[temperature_gridnumber,:,:]) #tex = modelgrid.line_params_2D(gridval1,gridval2,densityarr,columnarr,texgrid[temperature_gridnumber,:,:]) if verbose: for ta,tk in zip(tau,tex): print("density %20.12g temperature %20.12g column %20.12g: tau %20.12g tex %20.12g" % (density, temperature, column, ta, tk)) if debug: import pdb; pdb.set_trace() spec = np.sum([ (formaldehyde_mm_vtau(xarr, Tex=float(tex[ii]), tau=float(tau[ii]), xoff_v=xoff_v, width=width, **kwargs) * (xarr.as_unit('GHz').value>minfreq[ii]) * (xarr.as_unit('GHz').value<maxfreq[ii])) for ii in xrange(len(tex))], axis=0) return spec
def __init__(self, specfit, targetfig, toolfig=None, nsubplots=12): """ *targetfig* The figure instance to adjust *toolfig* The figure instance to embed the subplot tool into. If None, a default figure will be created. If you are using this from the GUI """ self.targetfig = targetfig self.specfit = specfit self.baseline = specfit.Spectrum.baseline self.plotter = specfit.Spectrum.plotter from matplotlib import pyplot if toolfig is None: tbar = matplotlib.rcParams['toolbar'] # turn off the navigation toolbar for the toolfig matplotlib.rcParams['toolbar'] = 'None' self.toolfig = pyplot.figure(figsize=(6,3)) self.toolfig.canvas.set_window_title("Fit Tools for "+targetfig.canvas.manager.window.title()) self.toolfig.subplots_adjust(top=0.9,left=0.05,right=0.95) matplotlib.rcParams['toolbar'] = tbar else: self.toolfig = toolfig self.toolfig.subplots_adjust(left=0.0, right=1.0) #bax = self.toolfig.add_axes([0.6, 0.05, 0.15, 0.075]) #self.buttonrefresh = Button(bax, 'Refresh') # buttons ruin everything. # fax = self.toolfig.add_axes([0.1, 0.05, 0.15, 0.075]) # self.buttonfit = Button(fax, 'Fit') # # resetax = self.toolfig.add_axes([0.7, 0.05, 0.15, 0.075]) # self.buttonreset = Button(resetax, 'Reset') # resetblax = self.toolfig.add_axes([0.3, 0.05, 0.15, 0.075]) # self.buttonresetbl = Button(resetblax, 'Reset BL') # resetfitax = self.toolfig.add_axes([0.5, 0.05, 0.15, 0.075]) # self.buttonresetfit = Button(resetfitax, 'Reset fit') def refresh(event): thisdrawon = self.drawon self.drawon = False self.update_information() # draw the canvas self.drawon = thisdrawon if self.drawon: self.toolfig.canvas.draw() self.targetfig.canvas.draw() def fit(event): self.specfit.button3action(event) def reset_fit(event): self.specfit.guesses = [] self.specfit.npeaks = 0 self.specfit.includemask[:] = True self.refresh(event) def reset_baseline(event): self.baseline.unsubtract() self.refresh(event) def reset(event): reset_baseline(event) reset_fit(event) self.plotter() self.refresh(event) # during refresh there can be a temporary invalid state # depending on the order of the refresh so we turn off # validation for the refreshting #validate = self.toolfig.subplotpars.validate #self.toolfig.subplotpars.validate = False #self.buttonrefresh.on_clicked(refresh) #self.toolfig.subplotpars.validate = validate # these break everything. # self.buttonfit.on_clicked(fit) # self.buttonresetfit.on_clicked(reset_fit) # self.buttonresetbl.on_clicked(reset_baseline) # self.buttonreset.on_clicked(reset) #menuitems = [] #for label in ('polynomial','blackbody','log-poly'): # def on_select(item): # print 'you selected', item.labelstr # item = MenuItem(fig, label, props=props, hoverprops=hoverprops, # on_select=on_select) # menuitems.append(item) #menu = Menu(fig, menuitems) self.axes = [self.toolfig.add_subplot(nsubplots,1,spnum, frame_on=False, navigate=False, xticks=[], yticks=[]) for spnum in xrange(1,nsubplots+1)] #self.axes = self.toolfig.add_axes([0,0,1,1]) self.use_axes = [0,1,2,4,5,6,7,8,9,10,11] self.labels = dict([(axnum,None) for axnum in self.use_axes]) self.update_information() self.targetfig.canvas.mpl_connect('button_press_event',self.refresh) self.targetfig.canvas.mpl_connect('key_press_event',self.refresh) self.targetfig.canvas.mpl_connect('draw_event',self.refresh)
def h2co_mm_radex(xarr, Temperature=25, logColumn=13, logDensity=4, xoff_v=0.0, width=1.0, grid_vwidth=1.0, gridbundle = None, debug=False, verbose=False, **kwargs): """ Use a grid of RADEX-computed models to make a model line spectrum The RADEX models have to be available somewhere. OR they can be passed as arrays. If as arrays, the form should be: texgrid = ((minfreq1,maxfreq1,texgrid1),(minfreq2,maxfreq2,texgrid2)) xarr must be a SpectroscopicAxis instance xoff_v, width are both in km/s Parameters ---------- grid_vwidth : float the velocity assumed when computing the grid in km/s this is important because tau = modeltau / width (see, e.g., Draine 2011 textbook pgs 219-230) density : float Density! """ # Convert X-units to frequency in GHz xarr = xarr.as_unit('Hz', quiet=True) Tex303,Tex322,Tex321,tau303,tau322,tau321 = gridbundle # if this gets too far different from 1, we are gonna have a Bad Time. scalefac = grid_vwidth/width tex = (Tex303(logColumn,logDensity,Temperature), Tex322(logColumn,logDensity,Temperature), Tex321(logColumn,logDensity,Temperature)) tau = (tau303(logColumn,logDensity,Temperature)*scalefac, tau322(logColumn,logDensity,Temperature)*scalefac, tau321(logColumn,logDensity,Temperature)*scalefac) if np.any(np.isnan(tex)) or np.any(np.isnan(tau)): raise ValueError("Invalid column/density") if verbose: for ta,tk in zip(tau,tex): print("density %20.12g temperature %20.12g column %20.12g: tau %20.12g tex %20.12g" % (logDensity, Temperature, logColumn, ta, tk)) if debug: import pdb; pdb.set_trace() # here there be physics ckms = 2.99792458e5 freq_dict = { '303': 218.222192e9, '322': 218.475632e9, '321': 218.760066e9, } Tbg = 2.73 #because it totally is nu0 = np.array([ 218.222192e9, 218.475632e9,218.760066e9]) nuwidth = [width/ckms*nu for nu in nu0] nuoff = [xoff_v/ckms*nu for nu in nu0] minfreq = nu0/1e9 - 0.25 maxfreq = nu0/1e9 + 0.25 # spec2 = np.zeros(len(xarr)) # for ii in range(len(nu0)): # taunu = tau[ii]*np.exp(-(xarr+nuoff[ii]-nu0[ii])**2/(2.0*nuwidth[ii]**2)) # spec2 = spec2 + (1-np.exp(-taunu))*tex[ii] + Tbg*(np.exp(-taunu)-1) #second term assumes an ON-OFF spec = np.sum([ (formaldehyde_mm_vtau(xarr, Tex=float(tex[ii]), tau=float(tau[ii]), xoff_v=xoff_v, width=width, **kwargs) * (xarr.as_unit('GHz')>minfreq[ii]) * (xarr.as_unit('GHz')<maxfreq[ii])) for ii in xrange(len(tex))], axis=0) # import pdb # pdb.set_trace() return spec
def fitnh3tkin(input_dict, dobaseline=True, baselinekwargs={}, crop=False, cropunit=None, guessline='twotwo', tex=15, trot=20, column=15.0, fortho=0.66, tau=None, thin=False, quiet=False, doplot=True, fignum=1, guessfignum=2, smooth=False, scale_keyword=None, rebase=False, tkin=None, npeaks=1, guesses=None, guess_error=True, **kwargs): """ Given a dictionary of filenames and lines, fit them together e.g. {'oneone':'G000.000+00.000_nh3_11.fits'} Parameters ---------- input_dict : dict A dictionary in which the keys are the ammonia line names (e.g., 'oneone', 'twotwo', etc) and the values are either Spectrum objects or filenames of spectra dobaseline : bool Fit and subtract a baseline prior to fitting the model? Keyword arguments to `pyspeckit.spectrum.Spectrum.baseline` are specified in ``baselinekwargs``. baselinekwargs : dict The keyword arguments for the baseline crop : bool or tuple A range of values to crop the spectrum to. The units are specified by ``cropunit``; the default ``None`` will use pixels. If False, no cropping will be performed. cropunit : None or astropy unit The unit for the crop parameter guess_error : bool Use the guess line to estimate the error in all spectra? """ if tkin is not None: if trot == 20 or trot is None: trot = tkin else: raise ValueError("Please specify trot, not tkin") warnings.warn("Keyword 'tkin' is deprecated; use trot instead", DeprecationWarning) spdict = dict([(linename, Spectrum(value, scale_keyword=scale_keyword)) if type(value) is str else (linename, value) for linename, value in iteritems(input_dict) ]) splist = spdict.values() for transition, sp in spdict.items(): # required for plotting, cropping sp.xarr.convert_to_unit('km/s', velocity_convention='radio', refX=pyspeckit.spectrum.models.ammonia.freq_dict[transition]*u.Hz, quiet=True) if crop and len(crop) == 2: for sp in splist: sp.crop(*crop, unit=cropunit) if dobaseline: for sp in splist: sp.baseline(**baselinekwargs) if smooth and type(smooth) is int: for sp in splist: sp.smooth(smooth) spdict[guessline].specfit(fittype='gaussian', negamp=False, vheight=False, guesses='moments') ampguess, vguess, widthguess = spdict[guessline].specfit.modelpars if widthguess < 0: raise ValueError("Width guess was < 0. This is impossible.") print("RMS guess (errspec): ", spdict[guessline].specfit.errspec.mean()) print("RMS guess (residuals): ", spdict[guessline].specfit.residuals.std()) errguess = spdict[guessline].specfit.residuals.std() if rebase: # redo baseline subtraction excluding the centroid +/- about 20 km/s vlow = spdict[guessline].specfit.modelpars[1]-(19.8+spdict[guessline].specfit.modelpars[2]*2.35) vhigh = spdict[guessline].specfit.modelpars[1]+(19.8+spdict[guessline].specfit.modelpars[2]*2.35) for sp in splist: sp.baseline(exclude=[vlow, vhigh], **baselinekwargs) for sp in splist: if guess_error: sp.error[:] = errguess sp.xarr.convert_to_unit(u.GHz) if doplot: spdict[guessline].plotter(figure=guessfignum) spdict[guessline].specfit.plot_fit() spectra = Spectra(splist) spectra.specfit.npeaks = npeaks if tau is not None: if guesses is None: guesses = [a for i in xrange(npeaks) for a in (trot+random.random()*i, tex, tau+random.random()*i, widthguess+random.random()*i, vguess+random.random()*i, fortho)] fittype = 'ammonia_tau_thin' if thin else 'ammonia_tau' spectra.specfit(fittype=fittype, quiet=quiet, guesses=guesses, **kwargs) else: if guesses is None: guesses = [a for i in xrange(npeaks) for a in (trot+random.random()*i, tex, column+random.random()*i, widthguess+random.random()*i, vguess+random.random()*i, fortho)] if thin: raise ValueError("'thin' keyword not supported for the generic ammonia model") spectra.specfit(fittype='ammonia', quiet=quiet, guesses=guesses, **kwargs) if doplot: plot_nh3(spdict, spectra, fignum=fignum) return spdict, spectra
def formaldehyde_radex_tau(xarr, density=4, column=13, xoff_v=0.0, width=1.0, grid_vwidth=1.0, grid_vwidth_scale=False, taugrid=None, hdr=None, path_to_taugrid='', temperature_gridnumber=3, debug=False, verbose=False, return_hyperfine_components=False, **kwargs): """ Use a grid of RADEX-computed models to make a model line spectrum * uses hyperfine components * assumes *tau* varies but *tex* does not! The RADEX models have to be available somewhere. OR they can be passed as arrays. If as arrays, the form should be: texgrid = ((minfreq1,maxfreq1,texgrid1),(minfreq2,maxfreq2,texgrid2)) xarr must be a SpectroscopicAxis instance xoff_v, width are both in km/s grid_vwidth is the velocity assumed when computing the grid in km/s this is important because tau = modeltau / width (see, e.g., Draine 2011 textbook pgs 219-230) grid_vwidth_scale is True or False: False for LVG, True for Sphere """ if verbose: print("Parameters: dens=%f, column=%f, xoff=%f, width=%f" % (density, column, xoff_v, width)) if taugrid is None: if path_to_taugrid=='': raise IOError("Must specify model grids to use.") else: taugrid = [pyfits.getdata(path_to_taugrid)] hdr = pyfits.getheader(path_to_taugrid) yinds,xinds = np.indices(taugrid[0].shape[1:]) densityarr = (xinds+hdr['CRPIX1']-1)*hdr['CD1_1']+hdr['CRVAL1'] # log density columnarr = (yinds+hdr['CRPIX2']-1)*hdr['CD2_2']+hdr['CRVAL2'] # log column minfreq = (4.8,) maxfreq = (5.0,) elif hdr is not None: minfreq,maxfreq,taugrid = zip(*taugrid) yinds,xinds = np.indices(taugrid[0].shape[1:]) densityarr = (xinds+hdr['CRPIX1']-1)*hdr['CD1_1']+hdr['CRVAL1'] # log density columnarr = (yinds+hdr['CRPIX2']-1)*hdr['CD2_2']+hdr['CRVAL2'] # log column else: raise Exception # Convert X-units to frequency in GHz xarr = xarr.as_unit('Hz', quiet=True) gridval1 = np.interp(density, densityarr[0,:], xinds[0,:]) gridval2 = np.interp(column, columnarr[:,0], yinds[:,0]) if np.isnan(gridval1) or np.isnan(gridval2): raise ValueError("Invalid column/density") if scipyOK: slices = [temperature_gridnumber] + [slice(np.floor(gv),np.floor(gv)+2) for gv in (gridval2,gridval1)] tau = [scipy.ndimage.map_coordinates(tg[slices],np.array([[gridval2%1],[gridval1%1]]),order=1) for tg in taugrid] else: raise ImportError("Couldn't import scipy, therefore cannot interpolate") # let the hyperfine module determine the hyperfine components, and pass all of them here spec_components = [(formaldehyde_vtau(xarr.as_unit('Hz', quiet=True), tau=float(tau[ii]), xoff_v=xoff_v, width=width, return_tau=True, return_hyperfine_components=True, **kwargs) * (xarr.as_unit('GHz')>minfreq[ii]) * (xarr.as_unit('GHz')<maxfreq[ii])) for ii in xrange(len(tau))] # get an array of [n_lines, n_hyperfine, len(xarr)] if return_hyperfine_components: return np.array(spec_components).sum(axis=0) else: return np.sum(spec_components, axis=0).sum(axis=0)
def multigaussfit(self, xax, data, npeaks=1, err=None, params=[1, 0, 1], fixed=[False, False, False], limitedmin=[False, False, True], limitedmax=[False, False, False], minpars=[0, 0, 0], maxpars=[0, 0, 0], quiet=True, shh=True, veryverbose=False, negamp=None, tied=['', '', ''], parinfo=None, debug=False, **kwargs): """ An improvement on onepeakgaussfit. Lets you fit multiple gaussians. Inputs: xax - x axis data - y axis npeaks - How many gaussians to fit? Default 1 (this could supersede onepeakgaussfit) err - error corresponding to data These parameters need to have length = 3*npeaks. If npeaks > 1 and length = 3, they will be replicated npeaks times, otherwise they will be reset to defaults: params - Fit parameters: [amplitude, offset, width] * npeaks If len(params) % 3 == 0, npeaks will be set to len(params) / 3 fixed - Is parameter fixed? limitedmin/minpars - set lower limits on each parameter (default: width>0) limitedmax/maxpars - set upper limits on each parameter tied - link parameters together quiet - should MPFIT output each iteration? shh - output final parameters? kwargs are passed to mpfit Returns: Fit parameters Model Fit errors chi2 """ if len(params) != npeaks and (len(params) / 3) > npeaks: self.npeaks = len(params) / 3 else: self.npeaks = npeaks if isinstance(params, numpy.ndarray): params = params.tolist() # make sure all various things are the right length; if they're not, fix them using the defaults # multiformaldehydefit should process negamp directly if kwargs.has_key('negamp') is False: kwargs['negamp'] = None pardict = { "params": params, "fixed": fixed, "limitedmin": limitedmin, "limitedmax": limitedmax, "minpars": minpars, "maxpars": maxpars, "tied": tied } for parlistname in pardict: parlist = pardict[parlistname] if len(parlist) != 3 * self.npeaks: # if you leave the defaults, or enter something that can be multiplied by 3 to get to the # right number of formaldehydeians, it will just replicate if veryverbose: print("Correcting length of parameter %s" % parlistname) if len(parlist) == 3: parlist *= self.npeaks elif parlistname == "params": parlist[:] = [1, 0, 1] * self.npeaks elif parlistname == "fixed": parlist[:] = [False, False, False] * self.npeaks elif parlistname == "limitedmax": if negamp is None: parlist[:] = [False, False, False] * self.npeaks elif negamp is False: parlist[:] = [False, False, False] * self.npeaks else: parlist[:] = [True, False, False] * self.npeaks elif parlistname == "limitedmin": if negamp is None: parlist[:] = [ False, False, True ] * self.npeaks # Lines can't have negative width! elif negamp is False: parlist[:] = [True, False, True] * self.npeaks else: parlist[:] = [False, False, True] * self.npeaks elif parlistname == "minpars" or parlistname == "maxpars": parlist[:] = [0, 0, 0] * self.npeaks elif parlistname == "tied": parlist[:] = ['', '', ''] * self.npeaks # mpfit doesn't recognize negamp, so get rid of it now that we're done setting limitedmin/max and min/maxpars #if kwargs.has_key('negamp'): kwargs.pop('negamp') def mpfitfun(x, y, err): if err is None: def f(p, fjac=None): return [0, (y - self.n_gaussian(pars=p)(x))] else: def f(p, fjac=None): return [0, (y - self.n_gaussian(pars=p)(x)) / err] return f if xax is None: xax = numpy.arange(len(data)) parnames = {0: "AMPLITUDE", 1: "SHIFT", 2: "WIDTH"} if parinfo is None: parinfo = [{ 'n': ii, 'value': params[ii], 'limits': [minpars[ii], maxpars[ii]], 'limited': [limitedmin[ii], limitedmax[ii]], 'fixed': fixed[ii], 'parname': parnames[ii % 3] + str(ii / 3), 'error': ii, 'tied': tied[ii] } for ii in xrange(len(params))] if veryverbose: print("GUESSES: ") print("\n".join( ["%s: %s" % (p['parname'], p['value']) for p in parinfo])) if debug: for p in parinfo: print(p) mp = mpfit(mpfitfun(xax, data, err), parinfo=parinfo, quiet=quiet, **kwargs) mpp = mp.params if mp.perror is not None: mpperr = mp.perror else: mpperr = mpp * 0 chi2 = mp.fnorm if mp.status == 0: raise Exception(mp.errmsg) if not shh: print("Fit status: ", mp.status) print("Fit error message: ", mp.errmsg) print("Fit message: ", mpfit_messages[mp.status]) print("Final fit values: ") for i, p in enumerate(mpp): parinfo[i]['value'] = p print(parinfo[i]['parname'], p, " +/- ", mpperr[i]) print("Chi2: ", mp.fnorm, " Reduced Chi2: ", mp.fnorm / len(data), " DOF:", len(data) - len(mpp)) self.mp = mp self.mpp = mpp self.mpperr = mpperr self.model = self.n_gaussian(pars=mpp)(xax) return mpp, self.n_gaussian(pars=mpp)(xax), mpperr, chi2