def poly_sub(cfg_par, x, y, deg): ''' Continuum subtraction on spectrum through polynomial fitting INPUT: parameter file x-axis of the spectrum y-axis of the spectrum degre of polynomial to fit OUTPUT: continuum subtracted y-axis of spectrum ''' if cfg_par['abs_ex'].get('zunit') == 'm/s': unit_z = u.m / u.s else: unit_z = u.Hz step = (x[-1] - x[0]) / len(x) wave_for_spec = WaveCoord(cdelt=step, crval=x[0], cunit=unit_z) spe = Spectrum(wave=wave_for_spec, data=y) cont = spe.poly_spec(deg) cont_sub = y - cont.data return cont_sub
def test_spectrum_methods(spec_var, spec_novar): """Spectrum class: testing sum/mean/abs/sqrt methods""" wave = WaveCoord(crpix=2.0, cdelt=3.0, crval=0.5, cunit=u.nm, shape=10) spectrum1 = Spectrum(data=np.array([0.5, 1, 2, 3, 4, 5, 6, 7, 8, 9]), wave=wave) sum1 = spectrum1.sum() assert_almost_equal(sum1[0], spectrum1.data.sum()) spectrum2 = spectrum1[1:-2] sum1 = spectrum1.sum(lmin=spectrum1.wave.coord(1), lmax=spectrum1.wave.coord(10 - 3), unit=u.nm) sum2 = spectrum2.sum() assert_almost_equal(sum1, sum2) mean1 = spectrum1.mean(lmin=spectrum1.wave.coord(1), lmax=spectrum1.wave.coord(10 - 3), unit=u.nm) mean2 = spectrum2.mean() assert_almost_equal(mean1, mean2) spvar2 = spec_var.abs() assert spvar2[23] == np.abs(spec_var[23]) spvar2 = spec_var.abs().sqrt() assert spvar2[8] == np.sqrt(np.abs(spec_var[8])) assert_almost_equal(spec_var.mean()[0], 11.526, 2) assert_almost_equal(spec_novar.mean()[0], 11.101, 2) spvarsum = spvar2 + 4 * spvar2 - 56 / spvar2 assert_almost_equal(spvarsum[10], spvar2[10] + 4 * spvar2[10] - 56 / spvar2[10], 2) assert_almost_equal(spec_var.get_step(), 0.630, 2) assert_almost_equal(spec_var.get_start(), 4602.604, 2) assert_almost_equal(spec_var.get_end(), 7184.289, 2) assert_almost_equal(spec_var.get_range()[0], 4602.604, 2) assert_almost_equal(spec_var.get_range()[1], 7184.289, 2)
def test_rebin(spec_var, spec_novar): """Spectrum class: testing rebin function""" wave = WaveCoord(crpix=2.0, cdelt=3.0, crval=0.5, shape=10) spectrum1 = Spectrum(data=np.array([0.5, 1, 2, 3, 4, 5, 6, 7, 8, 9]) * 2.3, wave=wave) unit = spectrum1.wave.unit factor = 3 s = slice(0, factor * (spectrum1.shape[0] // factor)) # The rebinned slice flux1 = spectrum1[s].sum()[0] * spectrum1[s].wave.get_step(unit=unit) spectrum2 = spectrum1.rebin(factor, margin='left') flux2 = spectrum2.sum()[0] * spectrum2.wave.get_step(unit=unit) assert_almost_equal(flux1, flux2, 2) unit = spec_novar.wave.unit factor = 4 s = slice(0, factor * (spec_novar.shape[0] // factor)) flux1 = spec_novar[s].sum()[0] * spec_novar[s].wave.get_step(unit=unit) spnovar2 = spec_novar.rebin(factor, margin='left') flux2 = spnovar2.sum()[0] * spnovar2.wave.get_step(unit=unit) assert_almost_equal(flux1, flux2, 2) unit = spec_var.wave.unit factor = 4 s = slice(0, factor * (spec_var.shape[0] // factor)) flux1 = spec_var[s].sum(weight=False)[0] * \ spec_var[s].wave.get_step(unit=unit) spvar2 = spec_var.rebin(factor, margin='left') flux2 = spvar2.sum(weight=False)[0] * spvar2.wave.get_step(unit=unit) assert_almost_equal(flux1, flux2, 2)
def test_resample2(): """Spectrum class: testing resampling function with a spectrum of integers and resampling to a smaller pixel size""" wave = WaveCoord(crpix=2.0, cdelt=3.0, crval=0.5, cunit=u.nm) spectrum1 = Spectrum(data=np.array([0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0]), wave=wave) flux1 = spectrum1.sum()[0] * spectrum1.wave.get_step() spectrum2 = spectrum1.resample(0.3) flux2 = spectrum2.sum()[0] * spectrum2.wave.get_step() assert_almost_equal(flux1, flux2, 2)
def residual(p, datacube=datacube, pos=None, specwidth=spectralpixel, galcen=galcen, galPA=galPA): par = p.valuesdict() incli = par['incli'] col_denst = par['col_dens'] h = par['height'] bes = par['dop_param'] v_max = par['vel_max'] h_vt = par['h_v'] csize = par['csize'] r_0t = par['r_0'] print(par) h_v = 10**h_vt col_dens = 10**col_denst r_0 = 10**r_0t coord_sky = datacube.wcs.pix2sky([int(pos[0]), int(pos[1])], unit=u.deg) dec = coord_sky[0][0] ra = coord_sky[0][1] c1 = SkyCoord(ra * u.degree, dec * u.degree, frame='icrs') flux = datacube[:, int(pos[0]), int(pos[1])] abso = flux.data[specwidth[0]:specwidth[1] + 1] sigma = np.sqrt(flux.var[specwidth[0]:specwidth[1] + 1]) scale = 7.28 # z=0.73379 # plat scale (kpc/") for Planck c1 = SkyCoord(ra * u.degree, dec * u.degree, frame='icrs') #D = scale * galcen.separation(c1).arcsec #pa = galcen.position_angle(c1).to(u.deg) #alpha = galPA - pa.value alpha = csu.get_alpha(c1, galcen, galPA) D = csu.get_impactparam(c1, galcen, scale) lam2 = np.arange(4825.12, 4886.37 + 1.5, 0.1) wave1 = WaveCoord(cdelt=0.1, crval=4825.12, cunit=u.angstrom) model = Disco(h, incli, Rcore=0.1) spec = model.averagelos(D, alpha, lam2, 100, 12, z, csize, col_dens, bes, r_0, v_max, h_v, 0) spe = Spectrum(wave=wave1, data=spec) rspe = spe.resample(1.25) fluxmodel = rspe.data absomodel = fluxmodel[specwidth[0]:specwidth[1] + 1] #ymodelt = np.concatenate((ymodel[0][14:36],ymodel[1][16:36],ymodel[2][16:36])) rest = ((abso - absomodel) / sigma)**2 dif = abso - absomodel return (fluxmodel, rest, datacube[:, int(pos[0]), int(pos[1])].data, sigma, dif)
def __init__(self, source=None, **kwargs) : """Initialisation of the opening of spectra """ self.verbose = kwargs.pop('verbose', False) # Arguments for the plots self.title = kwargs.pop('title', "Spectrum") self.add_sky_lines = kwargs.pop("add_sky_lines", False) if self.add_sky_lines : self.color_sky = kwargs.pop("color_sky", "red") self.linestyle_sky = kwargs.pop("linestyle_sky", "--") self.alpha_sky = kwargs.pop("alpha_sky", 0.3) if source is not None: self.__dict__.update(source.__dict__) else : Spectrum.__init__(self, **kwargs)
def load_spectra(filename): spectra = OrderedDict() with fits.open(filename) as hdul: for i in range(1, len(hdul), 2): spec_id = int(hdul[i].name[4:]) spectra[spec_id] = Spectrum(hdulist=hdul, ext=(i, i + 1)) return spectra
def test_get_Spectrum(spec_var): """Spectrum class: testing getters""" wave = WaveCoord(crpix=2.0, cdelt=3.0, crval=0.5, cunit=u.nm) spectrum1 = Spectrum(data=np.array([0.5, 1, 2, 3, 4, 5, 6, 7, 8, 9]) * 2.3, wave=wave) a = spectrum1[1:7] assert a.shape[0] == 6 a = spectrum1.subspec(1.2, 15.6, unit=u.nm) assert a.shape[0] == 6 unit = spec_var.wave.unit spvarcut = spec_var.subspec(5560, 5590, unit=unit) assert spvarcut.shape[0] == 48 assert_almost_equal(spvarcut.get_start(unit=unit), 5560.25, 2) assert_almost_equal(spvarcut.get_end(unit=unit), 5589.89, 2) assert_almost_equal(spvarcut.get_step(unit=unit), 0.63, 2)
def get_sky_spectrum(filename) : """Read sky spectrum from MUSE data reduction """ sky = pyfits.getdata(filename) crval = sky['lambda'][0] cdelt = sky['lambda'][1] - crval wavein = WaveCoord(cdelt=cdelt, crval=crval, cunit= units.angstrom) spec = Spectrum(wave=wavein, data=sky['data'], var=sky['stat']) return spec
def getsp(self): """ Get estimated spectra as a dict (with the HST ids as keys) of mpdaf Spectra """ cat = {} for k, key in enumerate(self.listHST_ID): cat[key] = Spectrum(data=self.sources[k], var=self.varSources[k], wave=self.src.spectra['MUSE_TOT'].wave) return cat
def test_arithmetric(): """Spectrum class: testing arithmetic functions""" wave = WaveCoord(crpix=2.0, cdelt=3.0, crval=0.5, cunit=u.nm) spectrum1 = Spectrum(data=np.array([0.5, 1, 2, 3, 4, 5, 6, 7, 8, 9]), wave=wave) spectrum2 = spectrum1 > 6 # [-,-,-,-,-,-,-,7,8,9] # + spectrum3 = spectrum1 + spectrum2 assert spectrum3.data.data[3] == 3 assert spectrum3.data.data[8] == 16 spectrum3 = 4.2 + spectrum1 assert spectrum3.data.data[3] == 3 + 4.2 # - spectrum3 = spectrum1 - spectrum2 assert spectrum3.data.data[3] == 3 assert spectrum3.data.data[8] == 0 spectrum3 = spectrum1 - 4.2 assert spectrum3.data.data[8] == 8 - 4.2 # * spectrum3 = spectrum1 * spectrum2 assert spectrum3.data.data[8] == 64 spectrum3 = 4.2 * spectrum1 assert spectrum3.data.data[9] == 9 * 4.2 # / spectrum3 = spectrum1 / spectrum2 # divide functions that have a validity domain returns the masked constant # whenever the input is masked or falls outside the validity domain. assert spectrum3.data.data[8] == 1 spectrum3 = 1.0 / (4.2 / spectrum1) assert spectrum3.data.data[5] == 5 / 4.2 # with cube wcs = WCS() cube1 = Cube(data=np.ones(shape=(10, 6, 5)), wave=wave, wcs=wcs) cube2 = spectrum1 + cube1 sp1data = spectrum1.data[:, np.newaxis, np.newaxis] assert_array_almost_equal(cube2.data, sp1data + cube1.data) cube2 = spectrum1 - cube1 assert_array_almost_equal(cube2.data, sp1data - cube1.data) cube2 = spectrum1 * cube1 assert_array_almost_equal(cube2.data, sp1data * cube1.data) cube2 = spectrum1 / cube1 assert_array_almost_equal(cube2.data, sp1data / cube1.data) # spectrum * image data = np.ones(shape=(6, 5)) * 2 image1 = Image(data=data, wcs=wcs) cube2 = spectrum1 * image1 assert_array_almost_equal(cube2.data, sp1data * image1.data[np.newaxis, :, :])
def get_sky_spectrum(specname): """Read sky spectrum from MUSE data reduction """ if not os.path.isfile(specname): upipe.print_error("{0} not found".format(specname)) return None sky = pyfits.getdata(specname) crval = sky['lambda'][0] cdelt = sky['lambda'][1] - crval wavein = WaveCoord(cdelt=cdelt, crval=crval, cunit=u.angstrom) spec = Spectrum(wave=wavein, data=sky['data'], var=sky['stat']) return spec
def run(self, orig, grid_dxy=0, spectrum_size_fwhm=6): self.Cat2, line_est, line_var = estimation_line( orig.Cat1, orig.cube_raw, orig.var, orig.PSF, orig.wfields, orig.wcs, orig.wave, size_grid=grid_dxy, criteria='flux', order_dct=30, horiz_psf=1, horiz=5, ) _format_cat(self.Cat2) self._loginfo('Save the updated catalog in self.Cat2 (%d lines)', len(self.Cat2)) # Radius for spectrum trimming radius = np.ceil(np.array(orig.FWHM_profiles) * spectrum_size_fwhm / 2) radius = radius.astype(int) self.spectra = OrderedDict() for row, data, vari in zip(self.Cat2, line_est, line_var): profile, z, num_line = row['profile', 'z', 'num_line'] z_min = z - radius[profile] z_max = z + radius[profile] if len(data) > 1: # RB test for bug in GridAnalysis sp = Spectrum(data=data, var=vari, wave=orig.wave, mask=np.ma.nomask, copy=False) self.spectra[num_line] = sp.subspec(z_min, z_max, unit=None) self._loginfo('Save estimated spectrum of each line in self.spectra')
def to_mpdaf(self, row=1, scale=True): """ Creates a mpdaf.obj.Spectrum object from FADO 1D files. The spectrum can be specified as a row or as a name. Allowed names are the following 1: 'Observed' spectrum de-redshifted and rebinned' 2: 'Error' 3: 'Mask' 4: 'Best' 5: 'Average' of individual solutions 6: 'Median' 7: 'Stdev' 8: 'Stellar' using best fit' 9: 'Nebular' using best fit' 10: 'AGN' using best fit' 11: 'M/L' 12: 'LSF' Line spread function The best-fits for individual solution can only be specified as a number 13-XX see self.max_rows Parameters ---------- row: str, int Allowed names: 'observed', 'error', 'mask', 'best', 'average', 'median', 'stdev', 'stellar', 'nebular' 'agn', 'm/l', 'lsf' The row number where the spectra is extracted, default=1 (0 in python notation) scale: bool, whether to scale the spectra or not, default True Returns ------- a mpdaf.obj.Spectrum object """ try: from mpdaf.obj import Spectrum, WaveCoord except ImportError as e: print(e, "MPDAF not installed, cannot continue") raise sp = self.spectrum(row, scale) wave_coord = WaveCoord(cdelt=self.header["CDELT1"], crval=self.header["CRVAL1"], cunit=self.fado_load.wave_unit) sp_mpdaf = Spectrum(wave=wave_coord, data=sp.flux) return sp_mpdaf
def read(self): """Read sky continuum spectrum from MUSE data reduction """ if not os.path.isfile(self.filename): upipe.print_error("{0} not found".format(self.filename)) crval = 0.0 data = np.zeros(0) cdelt = 1.0 else: sky = pyfits.getdata(self.filename) crval = sky['lambda'][0] cdelt = sky['lambda'][1] - crval data = sky['flux'] wavein = WaveCoord(cdelt=cdelt, crval=crval, cunit=u.angstrom) self.spec = Spectrum(wave=wavein, data=data)
def test_mag(): """Spectrum class: testing magnitude computations.""" Vega = Spectrum(get_data_file('obj', 'Vega.fits')) Vega.unit = u.Unit('2E-17 erg / (Angstrom cm2 s)') Vega.wave.wcs.wcs.cunit[0] = u.angstrom assert_almost_equal(Vega.abmag_filter_name('V')[0], 0, 1) mag = Vega.abmag_filter_name('Ic')[0] assert mag > 0.4 and mag < 0.5 mag = Vega.abmag_band(22500, 2500)[0] assert mag > 1.9 and mag < 2.0
def test_write(tmpdir): """Spectrum class: testing write.""" testfile = str(tmpdir.join('spec.fits')) sp = Spectrum(data=np.arange(10), wave=WaveCoord(cunit=u.nm)) sp.write(testfile) with fits.open(testfile) as hdu: assert_array_equal(hdu[1].data.shape, sp.shape) hdr = hdu[1].header assert hdr['EXTNAME'] == 'DATA' assert hdr['NAXIS'] == 1 assert u.Unit(hdr['CUNIT1']) == u.nm assert hdr['NAXIS1'] == sp.shape[0] # Same with Angstrom sp = Spectrum(data=np.arange(10), wave=WaveCoord(cunit=u.angstrom)) sp.write(testfile) with fits.open(testfile) as hdu: assert u.Unit(hdu[1].header['CUNIT1']) == u.angstrom
def test_gauss_fit(capsys, cont): """Spectrum class: testing Gaussian fit""" contval = cont or 0 wave = WaveCoord(crpix=1, cdelt=0.3, crval=400, cunit=u.nm) spem = Spectrum(data=np.zeros(600) + contval, wave=wave) spem.add_gaussian(5000, 1200, 20, unit=u.angstrom) setup_logging() gauss = spem.gauss_fit(lmin=(4500, 4800), lmax=(5200, 6000), lpeak=5000, cont=cont, unit=u.angstrom) gauss.print_param() out, err = capsys.readouterr() assert '[INFO] Gaussian center = 5000 ' in err assert_almost_equal(gauss.lpeak, 5000, 2) assert_almost_equal(gauss.flux, 1200, 2) assert_almost_equal(gauss.fwhm, 20, 2) assert_allclose(spem.fwhm(gauss.lpeak, cont=contval), 20, atol=0.2)
ra = coord_sky[0][1] scale = 7.28 # z=0.73379 # plat scale (kpc/") for Planck c1 = SkyCoord(ra * u.degree, dec * u.degree, frame='icrs') alpha = csu.get_alpha(c1, galcen, galPA) D = csu.get_impactparam(c1, galcen, scale) flux = datacube[:, i, j] abso = flux.data[specwidth[0]:specwidth[1] + 1] sigma = np.sqrt(flux.var[specwidth[0]:specwidth[1] + 1]) lam2 = np.arange(4825.12, 4886.37 + 1.5, 0.1) wave1 = WaveCoord(cdelt=0.1, crval=4825.12, cunit=u.angstrom) model = Disco(h, incli, Rcore=0.1) spec = model.averagelos(D, alpha, lam2, 100, 12, z, csize, col_dens, bes, r_0, v_max, h_v, 0) spe = Spectrum(wave=wave1, data=spec) rspe = spe.resample(1.25) fluxmodel = rspe.data absomodel = fluxmodel[specwidth[0]:specwidth[1] + 1] #ymodelt = np.concatenate((ymodel[0][14:36],ymodel[1][16:36],ymodel[2][16:36])) dif = np.sum(((abso - absomodel) / sigma)**2) ima.data[i, j] = dif ima.mask[i, j] = False else: ima.data[i, j] = 0 ima.mask[i, j] = True ima.plot(colorbar='v') plt.show()
def spec_var(): return Spectrum(get_data_file('obj', 'Spectrum_Variance.fits'), ext=[0, 1])
def spec_novar(): return Spectrum(get_data_file('obj', 'Spectrum_Novariance.fits'))
def __init__(self): self.img = None self.cube = None self.spec = None if os.path.exists(SKYREF): self.sky = Spectrum(SKYREF) self.sky.data /= self.sky.data.max() else: print('Sky file missing') self.sky = None pg.mkQApp() self.win = win = QtGui.QWidget() win.setWindowTitle('MUSE Cube Viewer') # layout = QtGui.QGridLayout() layout = QtGui.QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) win.setLayout(layout) self.splitter = QtGui.QSplitter() self.splitter.setOrientation(QtCore.Qt.Horizontal) layout.addWidget(self.splitter) self.tree = ParameterTree(showHeader=False) self.splitter.addWidget(self.tree) self.params = Parameter.create(name='params', type='group', children=PARAMS) self.tree.setParameters(self.params, showTop=False) self.tree.setWindowTitle('pyqtgraph example: Parameter Tree') self.connect(('Median filter', ), self.update_spec_plot) self.connect(('Sky', ), self.update_spec_plot) self.connect(('Spectrum', 'Lambda Max'), self.show_image) self.connect(('Spectrum', 'Lambda Min'), self.show_image) self.connect(('Spectrum', 'Line color'), self.update_spec_plot) self.connect(('Spectrum', 'Selection color'), self.update_spec_plot) self.connect(('Spectrum', 'Show Zoom'), self.add_zoom_window) self.win_inner = win = pg.GraphicsLayoutWidget() self.splitter2 = QtGui.QSplitter() self.splitter2.setOrientation(QtCore.Qt.Vertical) self.splitter.addWidget(self.win_inner) # self.img_label = win.addLabel('Hello !', justify='right') # A plot area (ViewBox + axes) for displaying the white-light image win.nextRow() self.white_plot = win.addPlot(title='White-light Image') self.white_plot.setAspectLocked(lock=True, ratio=1) self.white_item = pg.ImageItem() self.white_plot.addItem(self.white_item) # A plot area (ViewBox + axes) for displaying the image win.nextColumn() self.img_plot = win.addPlot(title='Band Image') self.img_plot.setAspectLocked(lock=True, ratio=1) self.img_item = pg.ImageItem() self.img_plot.addItem(self.img_item) # pg.SignalProxy(self.img_plot.scene().sigMouseMoved, rateLimit=60, # slot=self.on_mouse_moved) # Contrast/color control self.hist = hist = pg.HistogramLUTItem() hist.setImageItem(self.img_item) win.addItem(hist) # self.specplot = win.addPlot(row=2, col=0, colspan=2) win.nextRow() self.specplot = win.addPlot(title='Spectrum', colspan=3) self.zoomplot = None self.zoomplot_visible = False # self.lbdareg = region = pg.LinearRegionItem(movable=False) # self.specplot.addItem(region) # region.setZValue(10) self.zoomreg = region = pg.LinearRegionItem() region.setZValue(10) self.specplot.addItem(self.zoomreg, ignoreBounds=True) region.sigRegionChanged.connect(self.update_zoom_spec_from_region) p = self.params lmin = p['Spectrum', 'Lambda Min'] lmax = p['Spectrum', 'Lambda Max'] region.setRegion([lmin, lmax]) self.win.resize(1000, 800) self.win.show()
def test_integrate(): """Spectrum class: testing integration""" wave = WaveCoord(crpix=2.0, cdelt=3.0, crval=0.5, cunit=u.nm) spectrum1 = Spectrum(data=np.array([0.5, 1, 2, 3, 4, 5, 6, 7, 8, 9]), wave=wave, unit=u.Unit('ct/Angstrom')) # Integrate the whole spectrum, by not specifying starting or ending # wavelengths. This should be the sum of the pixel values multiplied # by cdelt in angstroms (because the flux units are per angstrom). result, err = spectrum1.integrate() expected = spectrum1.get_step(unit=u.angstrom) * spectrum1.sum()[0] assert_almost_equal(result.value, expected) assert result.unit == u.ct assert np.isinf(err) # The result should not change if we change the wavelength units of # the wavelength limits to nanometers. result = spectrum1.integrate(unit=u.nm)[0] expected = spectrum1.get_step(unit=u.angstrom) * spectrum1.sum()[0] assert_almost_equal(result.value, expected) assert result.unit == u.ct # new spectrum with variance spectrum1 = Spectrum(data=np.array([0.5, 1, 2, 3, 4, 5, 6, 7, 8, 9]), var=np.ones(10), wave=wave, unit=u.Unit('ct/Angstrom')) # Integrate over a wavelength range 3.5 to 6.5 nm. The WCS # conversion equation from wavelength to pixel index is, # # index = crpix-1 + (lambda-crval)/cdelt # index = 1 + (lambda - 0.5) / 3.0 # # So wavelengths 3.5 and 6.5nm, correspond to pixel indexes # of 2.0 and 3.0. These are the centers of pixels 2 and 3. # Thus the integration should be the value of pixel 2 times # half of cdelt, plus the value of pixel 3 times half of cdelt. # This comes to 2*3.0/2 + 3*3.0/2 = 7.5 ct/Angstrom*nm, which # should be rescaled to 75 ct, since nm/Angstrom is 10.0. result, err = spectrum1.integrate(lmin=3.5, lmax=6.5, unit=u.nm) assert_almost_equal(result.value, 75) assert result.unit == u.ct datasum, var = spectrum1.sum(lmin=3.5, lmax=6.5, unit=u.nm) assert_almost_equal(result.value, datasum * 15) assert_almost_equal(err.value, var * 15) # Do the same test, but specify the wavelength limits in angstroms. # The result should be the same as before. result = spectrum1.integrate(lmin=35.0, lmax=65.0, unit=u.angstrom)[0] assert_almost_equal(result.value, 75) assert result.unit == u.ct assert_almost_equal(result.value, datasum * 15) assert_almost_equal(err.value, var * 15) # Do the same experiment yet again, but this time after changing # the flux units of the spectrum to simple counts, without any per # wavelength units. Since there are no wavelength units in the # flux units, the result should not be rescaled from the native # value of 7.5, and because we specified a wavelength range in # angstroms, the resulting units should be counts * nm. spectrum1.unit = u.ct result = spectrum1.integrate(lmin=3.5, lmax=6.5, unit=u.nm)[0] assert_almost_equal(result.value, 7.5) assert result.unit == u.ct * u.nm
wavep = 4855 for i in range(datacube.shape[1]): for j in range(datacube.shape[2]): spe = datacube[:, i, j] var = spe.var sigma = np.sqrt(var) #arrays flux = spe.data sigma = sigma.data wave = spe.wave.coord() #vel = (wave/(wave0*(1+zabs))-1)*299792.458 #mask pixels for continuum definition p1 = np.int(spe.wave.pixel(wavep - 60 * 1.25)) p2 = np.int(spe.wave.pixel(wavep - 30 * 1.25)) p3 = np.int(spe.wave.pixel(wavep + 30 * 1.25)) p4 = np.int(spe.wave.pixel(wavep + 100 * 1.25)) flux1 = flux[np.r_[p1:p2, p3:p4]] wave1 = wave[np.r_[p1:p2, p3:p4]] #continuum z = np.polyfit(wave1, flux1, 2) p = np.poly1d(z) cont = p(wave) nflux = flux / cont stdev = sigma / cont #for displaying #mpdaf normalized continuum wave_aux = WaveCoord(cdelt=1.25, crval=wave[0], cunit=u.angstrom) nspe = Spectrum(wave=wave_aux, data=nflux, var=(stdev**2)) norcube[:, i, j] = nspe #co[:] = sp-ai
def test_resample(): """Spectrum class: Test resampling""" # Choose the dimensions of the spectrum, choosing a large number that is # *not* a convenient power of 2. oldshape = 4000 # Choose the wavelength pixel size and the default wavelength units. oldstep = 1.0 oldunit = u.angstrom # Create the wavelength axis coordinates. wave = WaveCoord(crpix=2.0, cdelt=oldstep, crval=0.5, cunit=oldunit, shape=oldshape) # Specify the desired increase in pixel size, and the resulting pixel size. factor = 6.5 newstep = ((factor * oldstep) * oldunit).to(u.nm).value # Specify the wavelength at which the peak of the resampled spectrum should # be expected. expected_peak_wave = 3000.0 # Create the array in which the test spectrum will be composed. data = np.zeros(oldshape) # Get the wavelength coordinates of each pixel in the spectrum. w = wave.coord() # Add the following list gaussians to the spectrum, where each # gaussian is specified as: (amplitude, sigma_in_pixels, # center_wavelength). Given that narrow gaussians are reduced in # amplitude by resampling more than wide gaussians, we arrange # that the peak gaussian before and after correctly resampling are # different. gaussians = [ (0.5, 12.0, 800.0), (0.7, 5.0, 1200.0), (0.4, 700.0, 1600.0), (1.5, 2.6, 1980.0), # Peak before resampling (1.2, 2.6, 2000.0), (1.3, 15.0, expected_peak_wave), # Peak if resampled correctly (1.0, 2.0, 3200.0) ] for amp, sigma, center in gaussians: sigma *= oldstep data += amp * np.exp(-0.5 * ((center - w) / sigma)**2) # Fill the variance array with a simple window function. var = np.hamming(oldshape) # Add gaussian random noise to the spectrum, but leave 3 output # pixel widths zero at each end of the spectrum so that the PSF of # the output grid doesn't spread flux from the edges off the edge # of the output grid. It takes about 3 pixel widths for the gaussian # PSF to drop to about 0.01 of its peak. margin = np.ceil(3 * factor).astype(int) data[margin:-margin] += np.random.normal(scale=0.1, size=data.shape - 2 * margin) # Install the spectral data in a Spectrum container. oldsp = Spectrum(data=data, var=var, wave=wave) # Mask a few pixels. masked_slice = slice(900, 910) oldsp.mask[masked_slice] = True # Create a down-sampled version of the input spectrum. newsp = oldsp.resample(newstep, unit=u.nm) # Check that the integral flux in the resampled spectrum matches that of # the original spectrum. expected_flux = oldsp.sum(weight=False)[0] * oldsp.wave.get_step( unit=oldunit) actual_flux = newsp.sum(weight=False)[0] * newsp.wave.get_step( unit=oldunit) assert_allclose(actual_flux, expected_flux, 1e-2) # Do the same test, but with fluxes weighted by the inverse of the variances. expected_flux = oldsp.sum(weight=True)[0] * oldsp.wave.get_step( unit=oldunit) actual_flux = newsp.sum(weight=True)[0] * newsp.wave.get_step(unit=oldunit) assert_allclose(actual_flux, expected_flux, 1e-2) # Check that the peak of the resampled spectrum is at the wavelength # where the strongest gaussian was centered in the input spectrum. assert_allclose(np.argmax(newsp.data), newsp.wave.pixel(expected_peak_wave, nearest=True)) # Now upsample the downsampled spectrum to the original pixel size. # This won't recover the same spectrum, since higher spatial frequencies # are lost when downsampling, but the total flux should be about the # same, and the peak should be at the same wavelength as the peak in # original spectrum within one pixel width of the downsampled spectrum. newsp2 = newsp.resample(oldstep, unit=oldunit) # Check that the doubly resampled spectrum has the same integrated flux # as the original. expected_flux = oldsp.sum(weight=False)[0] * oldsp.wave.get_step( unit=oldunit) actual_flux = newsp2.sum(weight=False)[0] * newsp2.wave.get_step( unit=oldunit) assert_allclose(actual_flux, expected_flux, 1e-2) # Check that the peak of the up-sampled spectrum is at the wavelength # of the peak of the down-sampled spectrum to within the pixel resolution # of the downsampled spectrum. assert_allclose( newsp.wave.pixel(newsp2.wave.coord(np.argmax(newsp2.data)), nearest=True), newsp.wave.pixel(expected_peak_wave, nearest=True)) # Check that pixels that were masked in the input spectrum are still # masked in the final spectrum. np.testing.assert_equal(newsp2.mask[masked_slice], oldsp.mask[masked_slice])
def create_source( source_id, source_table, source_lines, origin_params, cube_cor_filename, cube_std_filename, mask_filename, skymask_filename, spectra_fits_filename, segmaps, version, source_ts, profile_fwhm, *, author="", nb_fwhm=2, expmap_filename=None, save_to=None, ): """Create a MPDAF source. This function create a MPDAF source object for the ORIGIN source. Parameters ---------- source_id : int Identifier for the source in the source and line tables. source_table : astropy.table.Table Catalogue of sources like the Cat3_sources one. source_lines : astropy.table.Table Catalogue of lines like the Cat3_lines one. origin_params : dict Dictionary of the parameters for the ORIGIN run. cube_cor_filename : str Name of the file containing the correlation cube of the ORIGIN run. cube_std_filename : str Name of the file containing the std cube of the ORIGIN run. mask_filenam e: str Name of the file containing the mask of the source. skymask_filename : str Name of the file containing the sky mask of the source. spectra_fits_filename : str Name of the FITS file containing the spectra of the lines. segmaps : dict(str: str) Dictionnary associating to a segmap type the associated FITS file name. version : str Version number stored in the source. source_ts : str Time stamp for when the source was created. profile_fwhm : list of int List of line profile FWHM in pixel. The index in the list is the profile number. author : str Name of the author. nb_fwhm : float Factor multiplying the FWHM of the line to compute the width of the narrow band image. expmap_filename : str Name of the file containing the exposure map. If not None, a cut-out of the exposure map will be added to the source file. save_to : str If not None, the source will be saved to the given file. Returns ------- mpdaf.sdetect.Source or None If save_to is used, the function returns None. """ logger = logging.getLogger(__name__) # [0] is to get a Row not a table. source_table = source_table.filled() source_info = source_table[source_table["ID"] == source_id][0] # The mask size is used for the cut-out size. mask = Image(mask_filename) mask_size = mask.shape[0] data_cube = Cube(origin_params["cubename"], convert_float64=False) origin = ( "ORIGIN", origin_version, os.path.basename(origin_params["cubename"]), data_cube.primary_header.get("CUBE_V", ""), ) source = Source.from_data( source_info["ID"], source_info["ra"], source_info["dec"], origin ) # Information about the source in the headers source.header["SRC_V"] = version, "Source version" source.header["SRC_TS"] = source_ts, "Timestamp of the source creation" source.header["CAT3_TS"] = ( source_table.meta["CAT3_TS"], "Timestamp of the catalog creation", ) source.add_history("Source created with ORIGIN", author) source.header["OR_X"] = source_info["x"], "x position in pixels" source.header["OR_Y"] = source_info["y"], "y position in pixels" source.header["OR_SEG"] = ( source_info["seg_label"], "Label in the segmentation map", ) source.header["OR_V"] = origin_version, "ORIGIN version" source.header["OR_FLUX"] = source_info["flux"], "flux maximum in all lines" source.header["OR_PMAX"] = (source_info["purity"], "maximum purity in all lines") if not np.isnan(source_info["STD"]): source.header["OR_STD"] = (source_info["STD"], "STD max value in all lines") if not np.isnan(source_info["nsigSTD"]): source.header["OR_nSTD"] = ( source_info["nsigSTD"], "max of STD/std(STD) in all lines", ) if not np.isnan(source_info["T_GLR"]): source.header["OR_TGLR"] = ( source_info["T_GLR"], "T_GLR max value in all lines", ) if not np.isnan(source_info["nsigTGLR"]): source.header["OR_nTGLR"] = ( source_info["nsigTGLR"], "max of T_GLR/std(T_GLR) in all lines", ) # source_header_keyword: (key_in_origin_param, description) parameters_to_add = { "OR_PROF": ("profiles", "OR input, spectral profiles"), "OR_FSF": ("PSF", "OR input, FSF cube"), "OR_THL%02d": ("threshold_list", "OR input threshold per area"), "OR_NA": ("nbareas", "OR number of areas"), "preprocessing": {"OR_DCT": ("dct_order", "OR input, DCT order")}, "areas": { "OR_PFAA": ("pfa", "OR input, PFA used to create the area map"), "OR_SIZA": ("maxsize", "OR input, maximum area size in pixels"), "OR_MSIZA": ("minsize", "OR input, minimum area size in pixels"), }, "compute_PCA_threshold": {"OR_PFAT": ("pfa_test", "OR input, PFA test")}, "compute_greedy_PCA": { "OR_FBG": ("Noise_population", "OR input: fraction of spectra estimated"), "OR_ITMAX": ("itermax", "OR input, maximum number of iterations"), }, "compute_TGLR": {"OR_NG": ("size", "OR input, connectivity size")}, "detection": { "OR_DXY": ("tol_spat", "OR input, spatial tolerance for merging (pix)"), "OR_DZ": ("tol_spec", "OR input, spectral tolerance for merging (pix)"), }, "compute_spectra": {"OR_NXZ": ("grid_dxy", "OR input, grid Nxy")}, } def add_keyword(keyword, param, description, params): if param == "threshold_list" and param in params: for idx, threshold in enumerate(params["threshold_list"]): source.header[keyword % idx] = (float("%0.2f" % threshold), description) elif param in params: if params[param] is None: source.header[keyword] = "", description else: source.header[keyword] = params[param], description else: logger.debug("Parameter %s absent of the parameter list.", param) for keyword, val in parameters_to_add.items(): if isinstance(val, dict) and keyword in origin_params: for key, val2 in val.items(): add_keyword(key, *val2, origin_params[keyword]["params"]) else: add_keyword(keyword, *val, origin_params) source.header["COMP_CAT"] = ( source_info["comp"], "1/0 (1=Pre-detected in STD, 0=detected in CORREL)", ) if source.COMP_CAT: threshold_keyword, purity_keyword = "threshold_std", "purity_std" else: threshold_keyword, purity_keyword = "threshold", "purity" source.header["OR_TH"] = ( float("%0.2f" % origin_params[threshold_keyword]), "OR input, threshold", ) source.header["OR_PURI"] = ( float("%0.2f" % origin_params[purity_keyword]), "OR input, purity", ) # Mini-cubes source.add_cube( data_cube, "MUSE_CUBE", size=mask_size, unit_size=None, add_white=True ) # Add FSF with the full cube, to have the same shape as fieldmap, then we # can work directly with the subcube has_fsf = True try: source.add_FSF(data_cube, fieldmap=origin_params["fieldmap"]) except: logger.debug('No FSF information found in the cube') has_fsf = False data_cube = source.cubes["MUSE_CUBE"] if source.COMP_CAT: cube_ori = Cube(cube_std_filename, convert_float64=False) source.add_cube(cube_ori, "ORI_SNCUBE", size=mask_size, unit_size=None) cube_ori = source.cubes["ORI_SNCUBE"] else: cube_ori = Cube(cube_cor_filename, convert_float64=False) source.add_cube(cube_ori, "ORI_CORREL", size=mask_size, unit_size=None) cube_ori = source.cubes["ORI_CORREL"] # Table of sources around the exported sources. radius = mask_size / 2 x_min, x_max = source_info["x"] - radius, source_info["x"] + radius y_min, y_max = source_info["y"] - radius, source_info["y"] + radius nearby_sources = ( (source_table["x"] >= x_min) & (source_table["x"] <= x_max) & (source_table["y"] >= y_min) & (source_table["y"] <= y_max) ) source.tables["ORI_CAT"] = source_table["ID", "ra", "dec"][nearby_sources] # Maps # The white map was added when adding the MUSE cube. source.images["ORI_MAXMAP"] = cube_ori.max(axis=0) # Using add_image, the image size is taken from the white map. source.add_image(mask, "ORI_MASK_OBJ") source.add_image(Image(skymask_filename), "ORI_MASK_SKY") for segmap_type, segmap_filename in segmaps.items(): source.add_image(Image(segmap_filename), "ORI_SEGMAP_%s" % segmap_type) if expmap_filename is not None: source.add_image(Image(expmap_filename), "EXPMAP") # Full source spectra source.extract_spectra( data_cube, obj_mask="ORI_MASK_OBJ", sky_mask="ORI_MASK_SKY", skysub=True ) source.extract_spectra( data_cube, obj_mask="ORI_MASK_OBJ", sky_mask="ORI_MASK_SKY", skysub=False ) if source.COMP_CAT: source.spectra["ORI_CORR"] = ( source.cubes["ORI_SNCUBE"] * source.images["ORI_MASK_OBJ"] ).mean(axis=(1, 2)) else: source.spectra["ORI_CORR"] = ( source.cubes["ORI_CORREL"] * source.images["ORI_MASK_OBJ"] ).mean(axis=(1, 2)) # Add the FSF information to the source and use this information to compute # the PSF weighted spectra. if has_fsf: try: fsfmodel = source.get_FSF() fwhm_fsf = fsfmodel.get_fwhm(data_cube.wave.coord()) beta_fsf = fsfmodel.get_beta(data_cube.wave.coord()) source.extract_spectra( data_cube, obj_mask="ORI_MASK_OBJ", sky_mask="ORI_MASK_SKY", skysub=True, psf=fwhm_fsf, beta=beta_fsf, ) source.extract_spectra( data_cube, obj_mask="ORI_MASK_OBJ", sky_mask="ORI_MASK_SKY", skysub=False, psf=fwhm_fsf, beta=beta_fsf, ) except: # WIP to work with the new FSF model has_fsf = False # Per line data: the line table, the spectrum of each line, the narrow band # map from the data and from the correlation cube. # Content of the line table in the source line_columns, line_units, line_fmt = zip( *[ ("NUM_LINE", None, None), ("RA_LINE", u.deg, ".2f"), ("DEC_LINE", u.deg, ".2f"), ("LBDA_OBS", u.Angstrom, ".2f"), ("FWHM", u.Angstrom, ".2f"), ("FLUX", u.erg / (u.s * u.cm ** 2), ".1f"), ("GLR", None, ".1f"), ("nGLR", None, ".1f"), ("PROF", None, None), ("PURITY", None, ".2f"), ] ) # If the line is a complementary one, the GLR column is replace by STD if source.COMP_CAT: line_columns = list(line_columns) line_columns[6] = "STD" line_columns[7] = "nSTD" # We put all the ORIGIN lines in an ORI_LINES tables but keep only the # unique lines in the LINES tables. source.add_table(source_lines, "ORI_LINES", select_in=None, col_dist=None) # Table containing the information on the narrow band images. nb_par_rows = [] hdulist = fits.open(spectra_fits_filename) for line in source_lines[source_lines["merged_in"] == -9999]: num_line, lbda_ori, prof = line[["num_line", "lbda", "profile"]] fwhm_ori = profile_fwhm[prof] * data_cube.wave.get_step(unit=u.Angstrom) if source.COMP_CAT: glr_std = line["STD"] nglr_std = line["nsigSTD"] else: glr_std = line["T_GLR"] nglr_std = line["nsigTGLR"] source.add_line( cols=line_columns, values=[ num_line, line["ra"], line["dec"], lbda_ori, fwhm_ori, line["flux"], glr_std, nglr_std, prof, line["purity"], ], units=line_units, fmt=line_fmt, desc=None, ) if f"DATA{num_line}" in hdulist: # RB add test source.spectra[f"ORI_SPEC_{num_line}"] = Spectrum( hdulist=hdulist, ext=(f"DATA{num_line}", f"STAT{num_line}"), convert_float64=False, ) source.add_narrow_band_image_lbdaobs( data_cube, f"NB_LINE_{num_line}", lbda=lbda_ori, width=nb_fwhm * fwhm_ori, method="sum", subtract_off=True, margin=10.0, fband=3.0, ) nb_par_rows.append( [f"NB_LINE_{num_line}", lbda_ori, nb_fwhm * fwhm_ori, 10.0, 3.0] ) source.add_narrow_band_image_lbdaobs( cube_ori, f"ORI_CORR_{num_line}", lbda=lbda_ori, width=nb_fwhm * fwhm_ori, method="max", subtract_off=False, ) # Compute the spectra weighted by the correlation map for the # current line tags = [f"ORI_CORR_{num_line}"] source.extract_spectra( data_cube, obj_mask="ORI_MASK_OBJ", sky_mask="ORI_MASK_SKY", skysub=True, tags_to_try=tags, ) source.extract_spectra( data_cube, obj_mask="ORI_MASK_OBJ", sky_mask="ORI_MASK_SKY", skysub=False, tags_to_try=tags, ) # set REFSPEC to the spectrum weighted by the correlation map of the # brightest line num_max = source.lines["NUM_LINE"][np.argmax(source.lines["FLUX"])] source.header["REFSPEC"] = f"ORI_CORR_{num_max}_SKYSUB" hdulist.close() nb_par = Table( names=["LINE", "LBDA", "WIDTH", "MARGIN", "FBAND"], dtype=["U20", float, float, float, float], rows=nb_par_rows, ) source.add_table(nb_par, "NB_PAR", select_in=None, col_dist=None) if save_to is not None: source.write(save_to) else: return source