def get_subcube(inp, xy_center, nside, wv_range=None): """ Parameters ---------- inp : str or Cube Input filename or Cube object xy_center : (float, float) Coordinates of the center in pixels nside : int Number of pixels to each side of the central one for the new cube wv_range: (float, float) Wavelength range of the subcube (wvmin,wvmax), must be in angstroms If None it gets the full spectral range Returns ------- cube : Cube Subcube """ if not isinstance(inp, Cube): # assume str cube = Cube(inp) else: cube = inp yx_center = (xy_center[1], xy_center[0]) # note that center in pmdaf is given as (y,x) unit_center = None # to be interpreted as pixels subcube = cube.subcube(yx_center, 2*nside+1, lbda=wv_range, unit_center=unit_center, unit_size=unit_center, unit_wave=u.Unit("Angstrom")) return subcube
def load_cube(self, filename): print('Loading cube {} ... '.format(filename), end='') self.cube = Cube(filename, dtype=None, copy=False) print('OK') with fits.open(filename) as hdul: if 'WHITE' in hdul: print('Loading white image ... ', end='') img = hdul['WHITE'].data print('OK') else: print('Creating white-light image ... ', end='') img = self.cube.mean(axis=0).data.data print('OK') self.white_item.setImage(img.T) # self.white_item.setLevels(zscale(img.data.filled(0))) self.white_item.setLevels(zscale(img)) self.show_image() self.add_roi(center=np.array(self.img.shape) / 2) # zoom to fit image self.white_plot.autoRange() self.img_plot.autoRange() self.update_spec_plot()
def slice_cube(r): """ Slice field A cube around the center within a given radius in kpc. """ ########################################################################### # Input files cubefile = "output_zap.fits" imfile="NGC3311_FieldA_exp1_(white)_IMAGE_FOV_2014-12-27T07:52:48.469.fits" ########################################################################### # Setting up the parameters of the new cube ps = 0.262 # kpc / arcsec rarcsec = r / ps center = np.array([dec0, ra0]) ymin, xmin = center - rarcsec / 3600. ymax, xmax = center + rarcsec / 3600. wave = wavelength_array(cubefile, axis=3, extension=1) wmin, wmax = wave[0], wave[1800] ########################################################################### # Loading data and processing im = Image(imfile) cube = Cube(cubefile) ########################################################################### # Peocessing data im2 = im.truncate(ymin, ymax, xmin, xmax) outim = "NGC3311_FieldA_{0}kpc_image.fits".format(r) im2.write(outim) outcube = "NGC3311_FieldA_{0}kpc_cube.fits".format(r) cube2 = cube.truncate([wmin, ymin, xmin, wmax, ymax, xmax]) cube2.write(outcube) return
def make_cube_from_images(flist, outf): data = [np.copy(fits.getdata(f, ext=1)) for f in flist] shape = np.max(np.array([d.shape for d in data]), axis=0) for d in data: d.resize(shape) cube = Cube(data=np.dstack(data), copy=False) cube.write(outf, savemask='nan')
def __init__(self, source=None, verbose=False, **kwargs) : """Initialisation of the opening of a Cube """ if source is not None: self.__dict__.update(source.__dict__) else : Cube.__init__(self, **kwargs) self.verbose = verbose
def test_get_FSF_from_cube_keywords(): cube = Cube(get_data_file('sdetect', 'minicube.fits')) with pytest.raises(ValueError): # This cube has no FSF info PSF, fwhm_pix, fwhm_arcsec = get_FSF_from_cube_keywords(cube, 13) cube = Cube(get_data_file('sdetect', 'subcub_mosaic.fits')) PSF, fwhm_pix, fwhm_arcsec = get_FSF_from_cube_keywords(cube, 13) assert len(PSF) == 9 assert len(fwhm_pix) == 9 np.testing.assert_allclose(fwhm_pix[0] * 0.2, fwhm_arcsec[0])
def test_fsf_model(tmpdir): cubename = get_data_file('sdetect', 'subcub_mosaic.fits') cube = Cube(cubename) # Read FSF model with the old method with pytest.warns(MpdafWarning): PSF, fwhm_pix, fwhm_arcsec = get_FSF_from_cube_keywords(cube, 13) # Read FSF model from file fsf = FSFModel.read(cubename) assert len(fsf) == 9 assert fsf[0].model == 2 assert_allclose(fsf[0].get_fwhm(cube.wave.coord()), fwhm_arcsec[0]) # Read FSF model from cube fsf = FSFModel.read(cube) assert len(fsf) == 9 assert fsf[0].model == 2 assert_allclose(fsf[0].get_fwhm(cube.wave.coord()), fwhm_arcsec[0]) # Read FSF model from header and for a specific field hdr = cube.primary_header.copy() hdr.update(cube.data_header) fsf = FSFModel.read(hdr, field=2) assert fsf.model == 2 assert_allclose(fsf.get_fwhm(cube.wave.coord()), fwhm_arcsec[1]) # test to_header assert [str(x).strip() for x in fsf.to_header().cards] == [ 'FSFMODE = 2 / Circular MOFFAT beta=poly(lbda) fwhm=poly(lbda)', 'FSFLB1 = 5000 / FSF Blue Ref Wave (A)', 'FSFLB2 = 9000 / FSF Red Ref Wave (A)', 'FSF00FNC= 2 / FSF00 FWHM Poly Ncoef', 'FSF00F00= -0.1204 / FSF00 FWHM Poly C00', 'FSF00F01= 0.6143 / FSF00 FWHM Poly C01', 'FSF00BNC= 1 / FSF00 BETA Poly Ncoef', 'FSF00B00= 2.8 / FSF00 BETA Poly C00' ] testfile = str(tmpdir.join('test.fits')) outcube = cube.copy() fsf.to_header(hdr=outcube.primary_header) outcube.write(testfile) fsf3 = FSFModel.read(testfile, field=0) assert fsf3.model == 2 assert fsf3.get_beta(7000) == fsf.get_beta(7000) assert fsf3.get_fwhm(7000) == fsf.get_fwhm(7000) assert fsf3.get_fwhm(7000, unit='pix') == fsf.get_fwhm(7000, unit='pix')
def remove_bg(cube_in,mask,cube_out,invert=False): """ Corrects background of a cube to zero (per wavelengh plane) Strong emission (e.g. cluster members) should be masked. Parameters: ---------- cube_in: string path to input cube to be corrected mask: string path to mask to be used. Image with 1 for masked regions (objects) and 0 for regions to be used to calculate the meadian background (sky)' cube_out: string path to the output (corrected) cube invert: boolean if True, 0 for masked regions (objects) and 1 for sky (old ZAP version) Returns: ---------- mpdaf.obj.Cube corrected cube """ c=Cube(cube_in) immask = Image(mask) c2=c.copy() mask = immask.data.astype(bool) if invert: mask = np.invert(mask) c.data.mask[:, mask] = True tstart = time.time() for k in range(c.shape[0]): posmin=max(k-25,0) posmax=min(c.shape[0],k+25) for p in range(c.shape[1]): med=np.ma.median(c.data[posmin:posmax,p,:]) c2.data.data[k,p,:]-=med for q in range(c.shape[2]): med=np.ma.median(c.data[posmin:posmax,:,q]) c2.data.data[k,:,q]-=med tend = time.time() print('ellapsed time %s'%(tend-tstart)) ## Update Header c2.primary_header.add_comment('This cube has been median subtracted') c2.write(cube_out) return c2
def make_white_image(inputfile, outputfile, verbose=False): t0 = time() if outputfile is None: outputfile = inputfile.replace('DATACUBE', 'IMAGE') if outputfile == inputfile: sys.exit('Input and output files are identical') print('Creating white light image {}'.format(outputfile)) cube = Cube(inputfile, convert_float64=False) if verbose: cube.info() im = cube.mean(axis=0) im.write(outputfile, savemask='nan') if verbose: print('Execution time {:.3f} seconds.'.format(time() - t0))
def compute_psf(lbda, seeing, GL, L0, npsflin=1, h=(100, 10000), three_lgs_mode=False, verbose=True): """Reconstruct a PSF from a set of seeing, GL, and L0 values. Parameters ---------- lbda : array Array of wavelength for which the PSF is computed (nm). npsflin : int Number of points where the PSF is reconstructed (on each axis). h : tuple of float Altitude of the ground and high layers (m). three_lgs_mode : bool If True, use only 3 LGS. verbose : bool If True (default) log informations """ if verbose: logger.info('Compute PSF with seeing=%.2f GL=%.2f L0=%.2f', seeing, GL, L0) Cn2 = [GL, 1 - GL] psd = simul_psd_wfm(Cn2, h, seeing, L0, zenith=0., npsflin=npsflin, dim=1280, three_lgs_mode=three_lgs_mode, verbose=verbose) # et voila la/les PSD. # Pour aller plus vite, on pourrait moyennee les PSD .. c'est presque # la meme chose que la moyenne des PSF ... et ca permet d'aller # npsflin^2 fois plus vite: # psd = psd.mean(axis=0) if npsflin == 1: psd = psd[0] # Passage PSD --> PSF psf = psf_muse(psd, lbda) # Convolve with MUSE PSF and Tip-tilt psf = convolve_final_psf(lbda, seeing, GL, L0, psf) # fit all planes with a Moffat and store fit parameters res = fit_psf_cube(lbda, Cube(data=psf, copy=False)) res.meta.update({'SEEING': seeing, 'GL': GL, 'L0': L0}) res['SEEING'] = seeing res['GL'] = GL res['L0'] = L0 return res, psf
def test_create_psf_cube(): src = Source.from_file(get_data_file('sdetect', 'origin-00026.fits')) cube = Cube(get_data_file('sdetect', 'subcub_mosaic.fits')) src.add_FSF(cube) wcs = src.images['MUSE_WHITE'].wcs shape = src.images['MUSE_WHITE'].shape # a, b, beta, field = src.get_FSF() a = 0.862 b = -3.46e-05 beta = 2.8 psf = b * cube.wave.coord() + a # Gaussian gauss = create_psf_cube(psf.shape + shape, psf, wcs=wcs) im = Image(data=gauss[0], wcs=wcs) res = im.gauss_fit() assert_almost_equal(wcs.sky2pix(res.center)[0], [12., 12.]) assert np.allclose(res.fwhm, psf[0]) assert np.allclose(res.flux, 1.0) # Moffat moff = create_psf_cube(psf.shape + shape, psf, wcs=wcs, beta=beta) im = Image(data=moff[0], wcs=wcs) res = im.moffat_fit() assert_almost_equal(wcs.sky2pix(res.center)[0], [12., 12.]) assert np.allclose(res.fwhm, psf[0]) assert np.allclose(res.flux, 1.0, atol=1e-2)
def __get__(self, obj, owner=None): if obj is None: return try: val = obj.__dict__[self.label] except KeyError: return if isinstance(val, str): if os.path.isfile(val): kind = self.kind if kind == 'cube': val = Cube(val) if kind == 'image': val = Image(val) elif kind == 'table': val = _format_cat(Table.read(val)) elif kind == 'array': val = np.loadtxt(val, ndmin=1) elif kind == 'spectra': val = load_spectra(val) obj.__dict__[self.label] = val else: val = None return val
def __init__(self, source=None, verbose=False, **kwargs): """Initialisation of the opening of a Cube """ if source is not None: self.__dict__.update(source.__dict__) else: Cube.__init__(self, **kwargs) self.verbose = verbose self._debug = kwargs.pop("debug", False) # PSF for that Cube self.psf_function = kwargs.pop("psf_function", "moffat") self.psf_fwhm0 = kwargs.pop("psf_fwhm0", 0.5) self.psf_l0 = kwargs.pop("psf_l0", 6483.58) self.psf_nmoffat = kwargs.pop("psf_nmoffat", 2.8) self.psf_b = kwargs.pop("psf_b", -3.0e-5)
def store_cube(self, name, data, **kwargs): """Create a MPDAF Cube and store it as an attribute.""" cube = Cube( data=data, wave=self.orig.wave, wcs=self.orig.wcs, mask=np.ma.nomask, copy=False, **kwargs, ) setattr(self, name, cube)
def cont_sub(self, line, z, dv, ddv, savefile=None): wv1, wv2, wv3, wv4 = interpWindow(line, z, dv, ddv) mask_b = (self.wave >= wv1) & (self.wave <= wv2) mask_r = (self.wave >= wv3) & (self.wave <= wv4) mask_mid = (self.wave >= (wv1 - 200)) & (self.wave <= (wv4 + 200)) wavefit = np.concatenate((self.wave[mask_b], self.wave[mask_r])) for y in range(0, self.ny): for x in range(0, self.nx): fluxfit = np.concatenate( (self.data[mask_b, y, x], self.data[mask_r, y, x])) z = np.polyfit(wavefit, fluxfit, 1) p = np.poly1d(z) linemodel = p(self.wave[mask_mid]) self.data[mask_mid, y, x] -= linemodel if savefile is not None: cubenew = Cube(data=self.data, var=(self.err)**2, data_header=self.dh, wcs=self.wcs, wave=self.wv) cubenew.write(savefile, savemask='none')
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 import_muse_fits(file_address): cube = Cube(filename=str(file_address)) header = cube.data_header cube.wave.info() dw = header['CD3_3'] w_min = header['CRVAL3'] nPixels = header['NAXIS3'] w_max = w_min + dw * nPixels wave = np.linspace(w_min, w_max, nPixels, endpoint=False) return wave, cube, header
def open_cube(self, cube_folder=None, cube_name=None) : """Open the cube """ # Check if cube folder and name are given if cube_folder is not None : self.cube_folder = cube_folder if cube_name is not None : self.cube_name = cube_name # Check if cube exists if (self.cube_folder is None) | (self.cube_name is None) : self._isCube = False print("WARNING: No appropriate Cube name and folder defined") return cubepath = joinpath(self.cube_folder, self.cube_name) if os.path.isfile(cubepath) : self._isCube = True self.cube_galaxy = Cube(cubepath) else : self._isCube = False print("WARNING: Cube {0} not found".format(cubepath))
def load_muse_cube(file_address): cube_array = Cube(filename=str(file_address)) header_obj = cube_array.data_header cube_array.wave.info() dw = header_obj['CD3_3'] w_min = header_obj['CRVAL3'] nPixels = header_obj['NAXIS3'] w_max = w_min + dw * nPixels wave_array = np.linspace(w_min, w_max, nPixels, endpoint=False) return wave_array, cube_array, header_obj
def get_subcube(inp, xy_center, nside, wv_range=None): """ Parameters ---------- inp : str or Cube Input filename or Cube object xy_center : (float, float) Coordinates of the center in pixels nside : int Number of pixels to each side of the central one for the new cube wv_range: (float, float) Wavelength range of the subcube (wvmin,wvmax), must be in angstroms If None it gets the full spectral range Returns ------- cube : Cube Subcube """ if not isinstance(inp, Cube): # assume str cube = Cube(inp) else: cube = inp yx_center = (xy_center[1], xy_center[0] ) # note that center in mpdaf is given as (y,x) unit_center = None # to be interpreted as pixels subcube = cube.subcube(yx_center, 2 * nside + 1, lbda=wv_range, unit_center=unit_center, unit_size=unit_center, unit_wave=u.Unit("Angstrom")) return subcube
def make_empty_cube(radec_center, pixscale, nside, wave_coord, wcs=None): """Makes a new datacube with different WCS and 2*nside+1 pixels per side Parameters ---------- radec_center : SkyCoord Coordinate of the central spaxel pixscale : Angle Pixel scale of the cube nside : int Number of pixels at each side of the central one wave_coord : mpdaf.obj.WaveCoord The WaveCoord object of the new datacube wcs : mpdaf.obj.WCS ; optional If given, it will overwrite radec_center, pixscale, nside Returns ------- cube : mpdaf.obj.Cube Cube object filled with zeros """ if wcs is None: ntot = 2 * nside + 1 # total side always odd ny, nx = ntot, ntot crpix = (nside + 1, nside + 1) crval = radec_center[1].to('deg').value, radec_center[0].to( 'deg').value cdelt = pixscale.to('deg').value dy, dx = cdelt, -1 * cdelt # dx is negative for convention East goes to negative x's # cd_matrix = np.zeros((2,2)) # cd_matrix[0,0] = cdelt # cd_matrix[1,1] = cdelt deg_bool = True shape = (ny, nx) wcs_new = WCS(crpix=crpix, crval=crval, cdelt=(dy, dx), deg=deg_bool, shape=shape) else: wcs_new = wcs nx = wcs.naxis1 ny = wcs.naxis2 nw = wave_coord.shape # data = np.zeros((nw, ny, nx)) cube = Cube(wcs=wcs_new, data=data, var=data, wave=wave_coord) return cube
def write_magecube_as_xspectrum1d(magecube_filename, airvac='air'): magecube = Cube(magecube_filename) nw, ny, nx = magecube.shape assert nx == 1, "Your magecube does not have the conventional astrometry, where the slit is aligned in the y-axis" spec_list = [] for ii in range(ny): sp = magecube[:, ii, 0] spec = ntu.xspectrum1d_from_mpdaf_spec(sp, airvac=airvac) spec_list += [spec] specs = collate(spec_list) new_name = magecube_filename.replace('.fits', '_xspec.fits') specs.write(new_name) return specs
def test_add_FSF(minicube): """Source class: testing add_FSF method""" src = Source.from_file(get_data_file('sdetect', 'origin-00026.fits')) assert src.get_FSF() is None with pytest.raises(ValueError): src.add_FSF(minicube) cube = Cube(get_data_file('sdetect', 'subcub_mosaic.fits')) src.add_FSF(cube) assert src.FSF99BET == 2.8 assert src.FSF99FWA == 0.843 assert src.FSF99FWB == -3.301e-05 assert src.get_FSF() == (0.843, -3.301e-05, 2.8, 99)
def masked_cube(inp, mask, value=0., method='value'): """Returns a masked version of the input cube inp : str or Cube Input filename or Cube object mask : boolean np.array 2D of same shape (ny, nx) than cube spatial Pixels to mask value : float Values to replaces masked spaxels if method = 'value' method : str Method to perform the masking value : replaces by the given value madian : replaces by the median of all contiguous pixels including the value of the pixel itself """ if not isinstance(inp, Cube): # assume str cube = Cube(inp) else: cube = inp newcube = cube.copy() nw, ny, nx = cube.shape for ii in range(nw): image = cube.data[ii, :, :] if method == 'value': aux = np.where(mask, value, image) elif method == 'median': inds_y, inds_x = np.where(mask) aux = image.data for yx in zip(inds_y, inds_x): # import pdb;pdb.set_trace() median = np.median(image[yx[0] - 1:yx[0] + 1, yx[1] - 1:yx[1] + 1]) aux[yx[0], yx[1]] = median newcube[ii, :, :] = aux # import pdb;pdb.set_trace() return newcube
def masked_cube(inp, mask, value=0., method='value'): """Returns a masked version of the input cube inp : str or Cube Input filename or Cube object mask : boolean np.array 2D of same shape (ny, nx) than cube spatial Pixels to mask value : float Values to replaces masked spaxels if method = 'value' method : str Method to perform the masking value : replaces by the given value madian : replaces by the median of all contiguous pixels including the value of the pixel itself """ if not isinstance(inp, Cube): # assume str cube = Cube(inp) else: cube = inp newcube = cube.copy() nw, ny, nx = cube.shape for ii in range(nw): image = cube.data[ii,:,:] if method == 'value': aux = np.where(mask, value, image) elif method == 'median': inds_y, inds_x = np.where(mask) aux = image.data for yx in zip(inds_y,inds_x): # import pdb;pdb.set_trace() median = np.median(image[yx[0]-1:yx[0]+1, yx[1]-1:yx[1]+1]) aux[yx[0],yx[1]] = median newcube[ii,:,:] = aux # import pdb;pdb.set_trace() return newcube
def test_add_FSF(minicube): """Source class: testing add_FSF method""" src = Source.from_file(get_data_file('sdetect', 'origin-00026.fits')) assert src.get_FSF() is None with pytest.raises(ValueError): src.add_FSF(minicube) cube = Cube(get_data_file('sdetect', 'subcub_mosaic.fits')) src.add_FSF(cube) assert src.FSFMODE == 2 assert src.FSF99FNC == 2 assert src.FSF99BNC == 1 assert np.isclose(src.FSF99B00, 2.8, 1e-2)
def test_get_band_image(): c = Cube(get_data_file('obj', 'CUBE.fits')) # Test unknown filter with pytest.raises(ValueError): c.get_band_image('foo') # Test non-overlapping filter with pytest.raises(ValueError): c.get_band_image('Johnson_B') im = c.get_band_image('Cousins_I') assert im.data.count() == 200 assert im.primary_header['ESO DRS MUSE FILTER NAME'] == 'Cousins_I'
def test_fsf_arrays(): cubename = get_data_file('sdetect', 'subcub_mosaic.fits') cube = Cube(cubename) fsf = FSFModel.read(cube, field=2) with pytest.raises(ValueError): fsf.get_2darray([7000], (20, 20)) with pytest.raises(ValueError): fsf.get_image([7000], cube.wcs) ima = fsf.get_image(7000, cube.wcs, center=(10, 10)) assert np.unravel_index(ima.data.argmax(), ima.shape) == (10, 10) tcube = cube[:5, :, :] c = fsf.get_cube(tcube.wave, cube.wcs, center=(10, 10)) assert c.shape == (5, 30, 30) assert np.unravel_index(c[0].data.argmax(), c.shape[1:]) == (10, 10)
def postprocess(self, rawCube=None, paramsPostProcess=None): if paramsPostProcess is not None: self.paramsPostProcess = paramsPostProcess if rawCube is not None: cube = Cube(rawCube) else: cube = self.cube if self.postprocessing is None: self.postprocessing = postp.Postprocess( cube, self.listSources, self.listPvalMap, self.listIndexMap, params=self.params, paramsPreProcess=self.paramsPreProcess, paramsDetection=self.paramsDetection, paramsPostProcess=self.paramsPostProcess, ) self.postprocessing.paramsPostProcess = self.paramsPostProcess self.postprocessing.createResultSources() if self.paramsPostProcess.newSource == True: self.listResultSources = self.postprocessing.listResultSources
def __init__( self, filename, name="origin", path=".", loglevel="DEBUG", logcolor=False, fieldmap=None, profiles=None, PSF=None, LBDA_FWHM_PSF=None, FWHM_PSF=None, PSF_size=25, param=None, imawhite=None, wfields=None, ): self.path = path self.name = name self.outpath = os.path.join(path, name) self.param = param or {} self.file_handler = None os.makedirs(self.outpath, exist_ok=True) # stdout & file logger setup_logging( name="muse_origin", level=loglevel, color=logcolor, fmt="%(levelname)-05s: %(message)s", stream=sys.stdout, ) self.logger = logging.getLogger("muse_origin") self._setup_logfile(self.logger) self.param["loglevel"] = loglevel self.param["logcolor"] = logcolor self._loginfo("Step 00 - Initialization (ORIGIN v%s)", __version__) # dict of Step instances, indexed by step names self.steps = OrderedDict() # dict containing the data attributes of each step, to expose them on # the ORIGIN object self._dataobjs = {} for i, cls in enumerate(steps.STEPS, start=1): # Instantiate the step object, give it a step number step = cls(self, i, self.param) # force its signature to be the same as step.run (without the # ORIGIN instance), which allows to see its arguments and their # default value. sig = inspect.signature(step.run) step.__signature__ = sig.replace(parameters=[ p for p in sig.parameters.values() if p.name != "orig" ]) self.steps[step.name] = step # Insert the __call__ method of the step in the ORIGIN object. This # allows to run a step with a method like "step01_preprocessing". self.__dict__[step.method_name] = step for name, _ in step._dataobjs: self._dataobjs[name] = step # MUSE data cube self._loginfo("Read the Data Cube %s", filename) self.param["cubename"] = filename self.cube = Cube(filename) self.Nz, self.Ny, self.Nx = self.shape = self.cube.shape # RA-DEC coordinates self.wcs = self.cube.wcs # spectral coordinates self.wave = self.cube.wave # List of spectral profile if profiles is None: profiles = os.path.join(CURDIR, "Dico_3FWHM.fits") self.param["profiles"] = profiles # FSF self.param["fieldmap"] = fieldmap self.param["PSF_size"] = PSF_size self._read_fsf( self.cube, fieldmap=fieldmap, wfields=wfields, PSF=PSF, LBDA_FWHM_PSF=LBDA_FWHM_PSF, FWHM_PSF=FWHM_PSF, PSF_size=PSF_size, ) # additional images self.ima_white = imawhite if imawhite else self.cube.mean(axis=0) self.testO2, self.histO2, self.binO2 = None, None, None self._loginfo("00 Done")
class ORIGIN(steps.LogMixin): """ORIGIN: detectiOn and extRactIon of Galaxy emIssion liNes This is the main class to interact with all the steps. An Origin object is mainly composed by: - cube data (raw data and covariance) - 1D dictionary of spectral profiles - MUSE PSF Attributes ---------- path : str Path where the ORIGIN data will be stored. name : str Name of the session and basename for the sources. param : dict Parameters values. cube_raw : array (Nz, Ny, Nx) Raw data. var : array (Nz, Ny, Nx) Variance. wcs : `mpdaf.obj.WCS` RA-DEC coordinates. wave : `mpdaf.obj.WaveCoord` Spectral coordinates. profiles : list of array List of spectral profiles to test FWHM_profiles : list FWHM of the profiles in pixels. wfields : None or list of arrays List of weight maps (one per fields in the case of MUSE mosaic) None: just one field PSF : array (Nz, PSF_size, PSF_size) or list of arrays MUSE PSF (one per field) LBDA_FWHM_PSF: list of floats Value of the FWMH of the PSF in pixel for each wavelength step (mean of the fields). FWHM_PSF : float or list of float Mean of the fwhm of the PSF in pixel (one per field). imawhite : `~mpdaf.obj.Image` White image segmap : `~mpdaf.obj.Image` Segmentation map cube_std : `~mpdaf.obj.Cube` standardized data for PCA. Result of step01. cont_dct : `~mpdaf.obj.Cube` DCT continuum. Result of step01. ima_std : `~mpdaf.obj.Image` Mean of standardized data for PCA along the wavelength axis. Result of step01. ima_dct : `~mpdaf.obj.Image` Mean of DCT continuum cube along the wavelength axis. Result of step01. nbAreas : int Number of area (segmentation) for the PCA computation. Result of step02. areamap : `~mpdaf.obj.Image` PCA area. Result of step02. testO2 : list of arrays (one per PCA area) Result of the O2 test (step03). histO2 : list of arrays (one per PCA area) PCA histogram (step03). binO2 : list of arrays (one per PCA area) Bins for the PCA histogram (step03). thresO2 : list of float For each area, threshold value (step03). meaO2 : list of float Location parameter of the Gaussian fit used to estimate the threshold (step03). stdO2 : list of float Scale parameter of the Gaussian fit used to estimate the threshold (step03). cube_faint : `~mpdaf.obj.Cube` Projection on the eigenvectors associated to the lower eigenvalues of the data cube (representing the faint signal). Result of step04. mapO2 : `~mpdaf.obj.Image` The numbers of iterations used by testO2 for each spaxel. Result of step04. cube_correl : `~mpdaf.obj.Cube` Cube of T_GLR values (step05). cube_profile : `~mpdaf.obj.Cube` (type int) PSF profile associated to the T_GLR (step05). maxmap : `~mpdaf.obj.Image` Map of maxima along the wavelength axis (step05). cube_local_max : `~mpdaf.obj.Cube` Local maxima from max correlation (step05). cube_local_min : `~mpdaf.obj.Cube` Local maxima from min correlation (step05). threshold : float Estimated threshold (step06). Pval : `astropy.table.Table` Table with the purity results for each threshold (step06): - PVal_r : The purity function - index_pval : index value to plot - Det_m : Number of detections (-DATA) - Det_M : Number of detections (+DATA) Cat0 : `astropy.table.Table` Catalog returned by step07 Pval_comp : `astropy.table.Table` Table with the purity results for each threshold in compl (step08): - PVal_r : The purity function - index_pval : index value to plot - Det_m : Number of detections (-DATA) - Det_M : Number of detections (+DATA) Cat1 : `astropy.table.Table` Catalog returned by step08 spectra : list of `~mpdaf.obj.Spectrum` Estimated lines. Result of step09. Cat2 : `astropy.table.Table` Catalog returned by step09. """ def __init__( self, filename, name="origin", path=".", loglevel="DEBUG", logcolor=False, fieldmap=None, profiles=None, PSF=None, LBDA_FWHM_PSF=None, FWHM_PSF=None, PSF_size=25, param=None, imawhite=None, wfields=None, ): self.path = path self.name = name self.outpath = os.path.join(path, name) self.param = param or {} self.file_handler = None os.makedirs(self.outpath, exist_ok=True) # stdout & file logger setup_logging( name="muse_origin", level=loglevel, color=logcolor, fmt="%(levelname)-05s: %(message)s", stream=sys.stdout, ) self.logger = logging.getLogger("muse_origin") self._setup_logfile(self.logger) self.param["loglevel"] = loglevel self.param["logcolor"] = logcolor self._loginfo("Step 00 - Initialization (ORIGIN v%s)", __version__) # dict of Step instances, indexed by step names self.steps = OrderedDict() # dict containing the data attributes of each step, to expose them on # the ORIGIN object self._dataobjs = {} for i, cls in enumerate(steps.STEPS, start=1): # Instantiate the step object, give it a step number step = cls(self, i, self.param) # force its signature to be the same as step.run (without the # ORIGIN instance), which allows to see its arguments and their # default value. sig = inspect.signature(step.run) step.__signature__ = sig.replace(parameters=[ p for p in sig.parameters.values() if p.name != "orig" ]) self.steps[step.name] = step # Insert the __call__ method of the step in the ORIGIN object. This # allows to run a step with a method like "step01_preprocessing". self.__dict__[step.method_name] = step for name, _ in step._dataobjs: self._dataobjs[name] = step # MUSE data cube self._loginfo("Read the Data Cube %s", filename) self.param["cubename"] = filename self.cube = Cube(filename) self.Nz, self.Ny, self.Nx = self.shape = self.cube.shape # RA-DEC coordinates self.wcs = self.cube.wcs # spectral coordinates self.wave = self.cube.wave # List of spectral profile if profiles is None: profiles = os.path.join(CURDIR, "Dico_3FWHM.fits") self.param["profiles"] = profiles # FSF self.param["fieldmap"] = fieldmap self.param["PSF_size"] = PSF_size self._read_fsf( self.cube, fieldmap=fieldmap, wfields=wfields, PSF=PSF, LBDA_FWHM_PSF=LBDA_FWHM_PSF, FWHM_PSF=FWHM_PSF, PSF_size=PSF_size, ) # additional images self.ima_white = imawhite if imawhite else self.cube.mean(axis=0) self.testO2, self.histO2, self.binO2 = None, None, None self._loginfo("00 Done") def __getattr__(self, name): # Use __getattr__ to provide access to the steps data attributes # via the ORIGIN object. This will also trigger the loading of # the objects if needed. if name in self._dataobjs: return getattr(self._dataobjs[name], name) else: raise AttributeError(f"unknown attribute {name}") def __dir__(self): return (super().__dir__() + list(self._dataobjs.keys()) + [o.method_name for o in self.steps.values()]) @lazyproperty def cube_raw(self): # Flux - set to 0 the Nan return self.cube.data.filled(fill_value=0) @lazyproperty def mask(self): return self.cube._mask @lazyproperty def var(self): # variance - set to Inf the Nan return self.cube.var.filled(np.inf) @classmethod def init( cls, cube, fieldmap=None, profiles=None, PSF=None, LBDA_FWHM_PSF=None, FWHM_PSF=None, PSF_size=25, name="origin", path=".", loglevel="DEBUG", logcolor=False, ): """Create a ORIGIN object. An Origin object is composed by: - cube data (raw data and covariance) - 1D dictionary of spectral profiles - MUSE PSF - parameters used to segment the cube in different zones. Parameters ---------- cube : str Cube FITS file name fieldmap : str FITS file containing the field map (mosaic) profiles : str FITS of spectral profiles If None, a default dictionary of 20 profiles is used. PSF : str Cube FITS filename containing a MUSE PSF per wavelength. If None, PSF are computed with a Moffat function (13x13 pixels, beta=2.6, fwhm1=0.76, fwhm2=0.66, lambda1=4750, lambda2=7000) LBDA_FWHM_PSF: list of float Value of the FWMH of the PSF in pixel for each wavelength step (mean of the fields). FWHM_PSF : list of float FWHM of the PSFs in pixels, one per field. PSF_size : int Spatial size of the PSF (when reconstructed from the cube header). name : str Name of this session and basename for the sources. ORIGIN.write() method saves the session in a folder that has this name. The ORIGIN.load() method will be used to load a session, continue it or create a new from it. loglevel : str Level for the logger (defaults to DEBUG). logcolor : bool Use color for the logger levels. """ return cls( cube, path=path, name=name, fieldmap=fieldmap, profiles=profiles, PSF=PSF, LBDA_FWHM_PSF=LBDA_FWHM_PSF, FWHM_PSF=FWHM_PSF, PSF_size=PSF_size, loglevel=loglevel, logcolor=logcolor, ) @classmethod @timeit def load(cls, folder, newname=None, loglevel=None, logcolor=None): """Load a previous session of ORIGIN. ORIGIN.write() method saves a session in a folder that has the name of the ORIGIN object (self.name). Parameters ---------- folder : str Folder name (with the relative path) where the ORIGIN data have been stored. newname : str New name for this session. This parameter lets the user to load a previous session but continue in a new one. If None, the user will continue the loaded session. loglevel : str Level for the logger (by default reuse the saved level). logcolor : bool Use color for the logger levels. """ path = os.path.dirname(os.path.abspath(folder)) name = os.path.basename(folder) with open(f"{folder}/{name}.yaml", "r") as stream: param = load_yaml(stream) if "FWHM PSF" in param: FWHM_PSF = np.asarray(param["FWHM PSF"]) else: FWHM_PSF = None if "LBDA_FWHM PSF" in param: LBDA_FWHM_PSF = np.asarray(param["LBDA FWHM PSF"]) else: LBDA_FWHM_PSF = None if os.path.isfile(param["PSF"]): PSF = param["PSF"] else: if os.path.isfile("%s/cube_psf.fits" % folder): PSF = "%s/cube_psf.fits" % folder else: PSF_files = glob.glob("%s/cube_psf_*.fits" % folder) if len(PSF_files) == 0: PSF = None elif len(PSF_files) == 1: PSF = PSF_files[0] else: PSF = sorted(PSF_files) wfield_files = glob.glob("%s/wfield_*.fits" % folder) if len(wfield_files) == 0: wfields = None else: wfields = sorted(wfield_files) # step0 if os.path.isfile("%s/ima_white.fits" % folder): ima_white = Image("%s/ima_white.fits" % folder) else: ima_white = None if newname is not None: # copy outpath to the new path shutil.copytree(os.path.join(path, name), os.path.join(path, newname)) name = newname loglevel = loglevel if loglevel is not None else param["loglevel"] logcolor = logcolor if logcolor is not None else param["logcolor"] obj = cls( path=path, name=name, param=param, imawhite=ima_white, loglevel=loglevel, logcolor=logcolor, filename=param["cubename"], fieldmap=param["fieldmap"], wfields=wfields, profiles=param["profiles"], PSF=PSF, FWHM_PSF=FWHM_PSF, LBDA_FWHM_PSF=LBDA_FWHM_PSF, ) for step in obj.steps.values(): step.load(obj.outpath) # special case for step3 NbAreas = param.get("nbareas") if NbAreas is not None: if os.path.isfile("%s/testO2_1.txt" % folder): obj.testO2 = [ np.loadtxt("%s/testO2_%d.txt" % (folder, area), ndmin=1) for area in range(1, NbAreas + 1) ] if os.path.isfile("%s/histO2_1.txt" % folder): obj.histO2 = [ np.loadtxt("%s/histO2_%d.txt" % (folder, area), ndmin=1) for area in range(1, NbAreas + 1) ] if os.path.isfile("%s/binO2_1.txt" % folder): obj.binO2 = [ np.loadtxt("%s/binO2_%d.txt" % (folder, area), ndmin=1) for area in range(1, NbAreas + 1) ] return obj def info(self): """Prints the processing log.""" with open(self.logfile) as f: for line in f: if line.find("Done") == -1: print(line, end="") def status(self): """Prints the processing status.""" for name, step in self.steps.items(): print(f"- {step.idx:02d}, {name}: {step.status.name}") def _setup_logfile(self, logger): if self.file_handler is not None: # Remove the handlers before adding a new one self.file_handler.close() logger.handlers.remove(self.file_handler) self.logfile = os.path.join(self.outpath, self.name + ".log") self.file_handler = RotatingFileHandler(self.logfile, "a", 1000000, 1) self.file_handler.setLevel(logging.DEBUG) formatter = logging.Formatter("%(asctime)s %(message)s") self.file_handler.setFormatter(formatter) logger.addHandler(self.file_handler) def set_loglevel(self, level): """Set the logging level for the console logger.""" handler = next(h for h in self.logger.handlers if isinstance(h, logging.StreamHandler)) handler.setLevel(level) self.param["loglevel"] = level @property def nbAreas(self): """Number of area (segmentation) for the PCA.""" return self.param.get("nbareas") @property def threshold_correl(self): """Estimated threshold used to detect lines on local maxima of max correl.""" return self.param.get("threshold") @threshold_correl.setter def threshold_correl(self, value): self.param["threshold"] = value @property def threshold_std(self): """Estimated threshold used to detect complementary lines on local maxima of std cube.""" return self.param.get("threshold_std") @threshold_std.setter def threshold_std(self, value): self.param["threshold_std"] = value @lazyproperty def profiles(self): """Read the list of spectral profiles.""" profiles = self.param["profiles"] self._loginfo("Load dictionary of spectral profile %s", profiles) with fits.open(profiles) as hdul: profiles = [hdu.data for hdu in hdul[1:]] # check that the profiles have the same size if len({p.shape[0] for p in profiles}) != 1: raise ValueError("The profiles must have the same size") return profiles @lazyproperty def FWHM_profiles(self): """Read the list of FWHM of the spectral profiles.""" with fits.open(self.param["profiles"]) as hdul: return [hdu.header["FWHM"] for hdu in hdul[1:]] def _read_fsf( self, cube, fieldmap=None, wfields=None, PSF=None, LBDA_FWHM_PSF=None, FWHM_PSF=None, PSF_size=25, ): """Read FSF cube(s), with fieldmap in the case of MUSE mosaic. There are two ways to specify the PSF informations: - with the ``PSF``, ``FWHM_PSF``, and ``LBDA_FWHM`` parameters. - or read from the cube header and fieldmap. If there are multiple fields, for a mosaic, we also need weight maps. If the cube contains a FSF model and a fieldmap is given, these weight maps are computed automatically. Parameters ---------- cube : mpdaf.obj.Cube The input datacube. fieldmap : str FITS file containing the field map (mosaic). wfields : list of str List of weight maps (one per fields in the case of MUSE mosaic). PSF : str or list of str Cube FITS filename containing a MUSE PSF per wavelength, or a list of filenames for multiple fields (mosaic). LBDA_FWHM_PSF: list of float Value of the FWMH of the PSF in pixel for each wavelength step (mean of the fields). FWHM_PSF : list of float FWHM of the PSFs in pixels, one per field. PSF_size : int Spatial size of the PSF (when reconstructed from the cube header). """ self.wfields = None info = self.logger.info if PSF is None or FWHM_PSF is None or LBDA_FWHM_PSF is None: info("Compute FSFs from the datacube FITS header keywords") if "FSFMODE" not in cube.primary_header: raise ValueError( "missing PSF keywords in the cube FITS header") # FSF created from FSF*** keywords try: from mpdaf.MUSE import FSFModel except ImportError: sys.exit("you must upgrade MPDAF") fsf = FSFModel.read(cube) lbda = cube.wave.coord() shape = (PSF_size, PSF_size) if isinstance(fsf, FSFModel): # just one FSF self.PSF = fsf.get_3darray(lbda, shape) self.LBDA_FWHM_PSF = fsf.get_fwhm(lbda, unit="pix") self.FWHM_PSF = np.mean(self.LBDA_FWHM_PSF) # mean of the fwhm of the FSF in pixel info("mean FWHM of the FSFs = %.2f pixels", self.FWHM_PSF) else: self.PSF = [f.get_3darray(lbda, shape) for f in fsf] fwhm = np.array([f.get_fwhm(lbda, unit="pix") for f in fsf]) self.LBDA_FWHM_PSF = np.mean(fwhm, axis=0) self.FWHM_PSF = np.mean(fwhm, axis=1) for i, fwhm in enumerate(self.FWHM_PSF): info("mean FWHM of the FSFs (field %d) = %.2f pixels", i, fwhm) info("Compute weight maps from field map %s", fieldmap) fmap = FieldsMap(fieldmap, nfields=len(fsf)) # weighted field map self.wfields = fmap.compute_weights() self.param["PSF"] = cube.primary_header["FSFMODE"] else: self.LBDA_FWHM_PSF = LBDA_FWHM_PSF if isinstance(PSF, str): info("Load FSFs from %s", PSF) self.param["PSF"] = PSF self.PSF = fits.getdata(PSF) if self.PSF.shape[1] != self.PSF.shape[2]: raise ValueError("PSF must be a square image.") if not self.PSF.shape[1] % 2: raise ValueError( "The spatial size of the PSF must be odd.") if self.PSF.shape[0] != self.shape[0]: raise ValueError("PSF and data cube have not the same" "dimensions along the spectral axis.") # mean of the fwhm of the FSF in pixel self.FWHM_PSF = np.mean(FWHM_PSF) self.param["FWHM PSF"] = FWHM_PSF.tolist() info("mean FWHM of the FSFs = %.2f pixels", self.FWHM_PSF) else: nfields = len(PSF) self.wfields = [] self.PSF = [] self.FWHM_PSF = list(FWHM_PSF) for n in range(nfields): info("Load FSF from %s", PSF[n]) self.PSF.append(fits.getdata(PSF[n])) info("Load weight maps from %s", wfields[n]) self.wfields.append(fits.getdata(wfields[n])) info("mean FWHM of the FSFs (field %d) = %.2f pixels", n, FWHM_PSF[n]) self.param["FWHM PSF"] = self.FWHM_PSF.tolist() self.param["LBDA FWHM PSF"] = self.LBDA_FWHM_PSF.tolist() @timeit def write(self, path=None, erase=False): """Save the current session in a folder that will have the name of the ORIGIN object (self.name). The ORIGIN.load(folder, newname=None) method will be used to load a session. The parameter newname will let the user to load a session but continue in a new one. Parameters ---------- path : str Path where the folder (self.name) will be stored. erase : bool Remove the folder if it exists. """ self._loginfo("Writing...") # adapt session if path changes if path is not None and path != self.path: if not os.path.exists(path): raise ValueError(f"path does not exist: {path}") self.path = path outpath = os.path.join(path, self.name) # copy outpath to the new path shutil.copytree(self.outpath, outpath) self.outpath = outpath self._setup_logfile(self.logger) if erase: shutil.rmtree(self.outpath) os.makedirs(self.outpath, exist_ok=True) # PSF if isinstance(self.PSF, list): for i, psf in enumerate(self.PSF): cube = Cube(data=psf, mask=np.ma.nomask, copy=False) cube.write(os.path.join(self.outpath, "cube_psf_%02d.fits" % i)) else: cube = Cube(data=self.PSF, mask=np.ma.nomask, copy=False) cube.write(os.path.join(self.outpath, "cube_psf.fits")) if self.wfields is not None: for i, wfield in enumerate(self.wfields): im = Image(data=wfield, mask=np.ma.nomask) im.write(os.path.join(self.outpath, "wfield_%02d.fits" % i)) if self.ima_white is not None: self.ima_white.write("%s/ima_white.fits" % self.outpath) for step in self.steps.values(): step.dump(self.outpath) # parameters in .yaml with open(f"{self.outpath}/{self.name}.yaml", "w") as stream: dump_yaml(self.param, stream) # step3 - saving this manually for now if self.nbAreas is not None: if self.testO2 is not None: for area in range(1, self.nbAreas + 1): np.savetxt("%s/testO2_%d.txt" % (self.outpath, area), self.testO2[area - 1]) if self.histO2 is not None: for area in range(1, self.nbAreas + 1): np.savetxt("%s/histO2_%d.txt" % (self.outpath, area), self.histO2[area - 1]) if self.binO2 is not None: for area in range(1, self.nbAreas + 1): np.savetxt("%s/binO2_%d.txt" % (self.outpath, area), self.binO2[area - 1]) self._loginfo("Current session saved in %s", self.outpath) def plot_areas(self, ax=None, **kwargs): """ Plot the 2D segmentation for PCA from self.step02_areas() on the test used to perform this segmentation. Parameters ---------- ax : matplotlib.Axes The Axes instance in which the image is drawn. kwargs : matplotlib.artist.Artist Optional extra keyword/value arguments to be passed to ``ax.imshow()``. """ if ax is None: ax = plt.gca() kwargs.setdefault("cmap", "jet") kwargs.setdefault("alpha", 0.7) kwargs.setdefault("interpolation", "nearest") kwargs["origin"] = "lower" cax = ax.imshow(self.areamap._data, **kwargs) i0 = np.min(self.areamap._data) i1 = np.max(self.areamap._data) if i0 != i1: from matplotlib.colors import BoundaryNorm from mpl_toolkits.axes_grid1 import make_axes_locatable n = i1 - i0 + 1 bounds = np.linspace(i0, i1 + 1, n + 1) - 0.5 norm = BoundaryNorm(bounds, n + 1) divider = make_axes_locatable(ax) cax2 = divider.append_axes("right", size="5%", pad=1) plt.colorbar( cax, cax=cax2, cmap=kwargs["cmap"], norm=norm, spacing="proportional", ticks=bounds + 0.5, boundaries=bounds, format="%1i", ) def plot_step03_PCA_threshold(self, log10=False, ncol=3, legend=True, xlim=None, fig=None, **fig_kw): """ Plot the histogram and the threshold for the starting point of the PCA. Parameters ---------- log10 : bool Draw histogram in logarithmic scale or not ncol : int Number of colomns in the subplots legend : bool If true, write pfa and threshold values as legend xlim : (float, float) Set the data limits for the x-axes fig : matplotlib.Figure Figure instance in which the image is drawn **fig_kw : matplotlib.artist.Artist All additional keyword arguments are passed to the figure() call. """ if self.nbAreas is None: raise ValueError("Run the step 02 to initialize self.nbAreas") if fig is None: fig = plt.figure() if self.nbAreas <= ncol: n = 1 m = self.nbAreas else: n = self.nbAreas // ncol m = ncol if (n * m) < self.nbAreas: n = n + 1 for area in range(1, self.nbAreas + 1): if area == 1: ax = fig.add_subplot(n, m, area, **fig_kw) else: ax = fig.add_subplot(n, m, area, sharey=fig.axes[0], **fig_kw) self.plot_PCA_threshold(area, "step03", log10, legend, xlim, ax) # Fine-tune figure for a in fig.axes[:-1]: a.set_xlabel("") for a in fig.axes[1:]: a.set_ylabel("") plt.setp([a.get_yticklabels() for a in fig.axes], visible=False) plt.setp([a.get_yticklabels() for a in fig.axes[0::m]], visible=True) plt.setp([a.get_yticklines() for a in fig.axes], visible=False) plt.setp([a.get_yticklines() for a in fig.axes[0::m]], visible=True) fig.subplots_adjust(wspace=0) if xlim is not None: plt.setp([a.get_xticklabels() for a in fig.axes[:-m]], visible=False) plt.setp([a.get_xticklines() for a in fig.axes[:-m]], visible=False) fig.subplots_adjust(hspace=0) def plot_step03_PCA_stat(self, cutoff=5, ax=None): """Plot the threshold value according to the area. Median Absolute Deviation is used to find outliers. Parameters ---------- cutoff : float Median Absolute Deviation cutoff ax : matplotlib.Axes The Axes instance in which the image is drawn """ if self.nbAreas is None: raise ValueError("Run the step 02 to initialize self.nbAreas") if self.thresO2 is None: raise ValueError("Run the step 03 to compute the threshold values") if ax is None: ax = plt.gca() ax.plot(np.arange(1, self.nbAreas + 1), self.thresO2, "+") med = np.median(self.thresO2) diff = np.absolute(self.thresO2 - med) mad = np.median(diff) if mad != 0: ksel = (diff / mad) > cutoff if ksel.any(): ax.plot( np.arange(1, self.nbAreas + 1)[ksel], np.asarray(self.thresO2)[ksel], "ro", ) ax.set_xlabel("area") ax.set_ylabel("Threshold") ax.set_title(f"PCA threshold (med={med:.2f}, mad= {mad:.2f})") def plot_PCA_threshold(self, area, pfa_test="step03", log10=False, legend=True, xlim=None, ax=None): """ Plot the histogram and the threshold for the starting point of the PCA. Parameters ---------- area : int in [1, nbAreas] Area ID pfa_test : float or str PFA of the test (if 'step03', the value set during step03 is used) log10 : bool Draw histogram in logarithmic scale or not legend : bool If true, write pfa and threshold values as legend xlim : (float, float) Set the data limits for the x-axis ax : matplotlib.Axes Axes instance in which the image is drawn """ if self.nbAreas is None: raise ValueError("Run the step 02 to initialize self.nbAreas") if pfa_test == "step03": param = self.param["compute_PCA_threshold"]["params"] if "pfa_test" in param: pfa_test = param["pfa_test"] hist = self.histO2[area - 1] bins = self.binO2[area - 1] thre = self.thresO2[area - 1] mea = self.meaO2[area - 1] std = self.stdO2[area - 1] else: raise ValueError( "pfa_test param is None: set a value or run the Step03") else: if self.cube_std is None: raise ValueError("Run the step 01 to initialize self.cube_std") # limits of each spatial zone ksel = self.areamap._data == area # Data in this spatio-spectral zone cube_temp = self.cube_std._data[:, ksel] # Compute_PCA_threshold from .lib_origin import Compute_PCA_threshold testO2, hist, bins, thre, mea, std = Compute_PCA_threshold( cube_temp, pfa_test) if ax is None: ax = plt.gca() from scipy import stats center = (bins[:-1] + bins[1:]) / 2 gauss = stats.norm.pdf(center, loc=mea, scale=std) gauss *= hist.max() / gauss.max() if log10: with warnings.catch_warnings(): warnings.simplefilter("ignore") gauss = np.log10(gauss) hist = np.log10(hist) ax.plot(center, hist, "-k") ax.plot(center, hist, ".r") ax.plot(center, gauss, "-b", alpha=0.5) ax.axvline(thre, color="b", lw=2, alpha=0.5) ax.grid() if xlim is None: ax.set_xlim((center.min(), center.max())) else: ax.set_xlim(xlim) ax.set_xlabel("frequency") ax.set_ylabel("value") kwargs = dict(transform=ax.transAxes, bbox=dict(facecolor="red", alpha=0.5)) if legend: text = "zone %d\npfa %.2f\nthreshold %.2f" % (area, pfa_test, thre) ax.text(0.1, 0.8, text, **kwargs) else: ax.text(0.9, 0.9, "%d" % area, **kwargs) def plot_mapPCA(self, area=None, iteration=None, ax=None, **kwargs): """ Plot at a given iteration (or at the end) the number of times a spaxel got cleaned by the PCA. Parameters ---------- area: int in [1, nbAreas] if None draw the full map for all areas iteration : int Display the nuisance/bacground pixels at iteration k ax : matplotlib.Axes The Axes instance in which the image is drawn kwargs : matplotlib.artist.Artist Optional extra keyword/value arguments to be passed to ``ax.imshow()``. """ if self.mapO2 is None: raise ValueError("Run the step 04 to initialize self.mapO2") themap = self.mapO2.copy() title = "Number of times the spaxel got cleaned by the PCA" if iteration is not None: title += "\n%d iterations" % iteration if area is not None: mask = np.ones_like(self.mapO2._data, dtype=np.bool) mask[self.areamap._data == area] = False themap._mask = mask title += " (zone %d)" % area if iteration is not None: themap[themap._data < iteration] = np.ma.masked if ax is None: ax = plt.gca() kwargs.setdefault("cmap", "jet") themap.plot(title=title, colorbar="v", ax=ax, **kwargs) def plot_purity(self, comp=False, ax=None, log10=False, legend=True): """Draw number of sources per threshold computed in step06/step08. Parameters ---------- comp : bool If True, plot purity curves for the complementary lines (step08). ax : matplotlib.Axes The Axes instance in which the image is drawn. log10 : bool To draw histogram in logarithmic scale or not. legend : bool To draw the legend. """ if ax is None: ax = plt.gca() if comp: threshold = self.threshold_std purity = self.param["purity_std"] Pval = self.Pval_comp else: threshold = self.threshold_correl purity = self.param["purity"] Pval = self.Pval if Pval is None: raise ValueError("Run the step 06") Tval_r = Pval["Tval_r"] ax2 = ax.twinx() ax2.plot(Tval_r, Pval["Pval_r"], "y.-", label="purity") ax.plot(Tval_r, Pval["Det_M"], "b.-", label="n detections (+DATA)") ax.plot(Tval_r, Pval["Det_m"], "g.-", label="n detections (-DATA)") ax2.plot(threshold, purity, "xr") if log10: ax.set_yscale("log") ax2.set_yscale("log") ym, yM = ax.get_ylim() ax.plot( [threshold, threshold], [ym, yM], "r", alpha=0.25, lw=2, label="automatic threshold", ) ax.set_ylim((ym, yM)) ax.set_xlabel("Threshold") ax2.set_ylabel("Purity") ax.set_ylabel("Number of detections") ax.set_title("threshold %f" % threshold) h1, l1 = ax.get_legend_handles_labels() h2, l2 = ax2.get_legend_handles_labels() if legend: ax.legend(h1 + h2, l1 + l2, loc=2) def plot_NB(self, src_ind, ax1=None, ax2=None, ax3=None): """Plot the narrow band images. Parameters ---------- src_ind : int Index of the object in self.Cat0. ax1 : matplotlib.Axes The Axes instance in which the NB image around the source is drawn. ax2 : matplotlib.Axes The Axes instance in which a other NB image for check is drawn. ax3 : matplotlib.Axes The Axes instance in which the difference is drawn. """ if self.Cat0 is None: raise ValueError("Run the step 05 to initialize self.Cat0") if ax1 is None and ax2 is None and ax3 is None: fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(12, 4)) # Coordinates of the source x0 = self.Cat0[src_ind]["x0"] y0 = self.Cat0[src_ind]["y0"] z0 = self.Cat0[src_ind]["z0"] # Larger spatial ranges for the plots longxy0 = 20 y01 = max(0, y0 - longxy0) y02 = min(self.shape[1], y0 + longxy0 + 1) x01 = max(0, x0 - longxy0) x02 = min(self.shape[2], x0 + longxy0 + 1) # Coordinates in this window y00 = y0 - y01 x00 = x0 - x01 # spectral profile num_prof = self.Cat0[src_ind]["profile"] profil0 = self.profiles[num_prof] # length of the spectral profile profil1 = profil0[profil0 > 1e-13] long0 = profil1.shape[0] # half-length of the spectral profile longz = long0 // 2 # spectral range intz1 = max(0, z0 - longz) intz2 = min(self.shape[0], z0 + longz + 1) # subcube for the plot cube_test_plot = self.cube_raw[intz1:intz2, y01:y02, x01:x02] wcs = self.wcs[y01:y02, x01:x02] # controle cube nb_ranges = 3 if (z0 + longz + nb_ranges * long0) < self.shape[0]: intz1c = intz1 + nb_ranges * long0 intz2c = intz2 + nb_ranges * long0 else: intz1c = intz1 - nb_ranges * long0 intz2c = intz2 - nb_ranges * long0 cube_controle_plot = self.cube_raw[intz1c:intz2c, y01:y02, x01:x02] # (1/sqrt(2)) * difference of the 2 sububes diff_cube_plot = (1 / np.sqrt(2)) * (cube_test_plot - cube_controle_plot) if ax1 is not None: ax1.plot(x00, y00, "m+") ima_test_plot = Image(data=cube_test_plot.sum(axis=0), wcs=wcs) title = "cube test - (%d,%d)\n" % (x0, y0) title += "lambda=%d int=[%d,%d[" % (z0, intz1, intz2) ima_test_plot.plot(colorbar="v", title=title, ax=ax1) ax1.get_xaxis().set_visible(False) ax1.get_yaxis().set_visible(False) if ax2 is not None: ax2.plot(x00, y00, "m+") ima_controle_plot = Image(data=cube_controle_plot.sum(axis=0), wcs=wcs) title = "check - (%d,%d)\n" % (x0, y0) + "int=[%d,%d[" % (intz1c, intz2c) ima_controle_plot.plot(colorbar="v", title=title, ax=ax2) ax2.get_xaxis().set_visible(False) ax2.get_yaxis().set_visible(False) if ax3 is not None: ax3.plot(x00, y00, "m+") ima_diff_plot = Image(data=diff_cube_plot.sum(axis=0), wcs=wcs) title = "Difference narrow band - (%d,%d)\n" % ( x0, y0) + "int=[%d,%d[" % ( intz1c, intz2c, ) ima_diff_plot.plot(colorbar="v", title=title, ax=ax3) ax3.get_xaxis().set_visible(False) ax3.get_yaxis().set_visible(False) def plot_sources(self, x, y, circle=False, vmin=0, vmax=30, title=None, ax=None, **kwargs): """Plot detected emission lines on the 2D map of maximum of the T_GLR values over the spectral channels. Parameters ---------- x : array Coordinates along the x-axis of the estimated lines in pixels. y : array Coordinates along the y-axis of the estimated lines in pixels. circle : bool If true, plot circles with a diameter equal to the mean of the fwhm of the PSF. vmin : float Minimum pixel value to use for the scaling. vmax : float Maximum pixel value to use for the scaling. title : str An optional title for the figure (None by default). ax : matplotlib.Axes the Axes instance in which the image is drawn kwargs : matplotlib.artist.Artist Optional arguments passed to ``ax.imshow()``. """ if ax is None: ax = plt.gca() self.maxmap.plot(vmin=vmin, vmax=vmax, title=title, ax=ax, **kwargs) if circle: fwhm = (self.FWHM_PSF if self.wfields is None else np.max( np.array(self.FWHM_PSF))) radius = np.round(fwhm / 2) for pos in zip(x, y): ax.add_artist(plt.Circle(pos, radius, color="k", fill=False)) else: ax.plot(x, y, "k+") def plot_segmaps(self, axes=None, figsize=(6, 6)): """Plot the segmentation maps: - segmap_cont: segmentation map computed on the white-light image. - segmap_merged: segmentation map merged with the cont one and another one computed on the residual. - segmap_purity: combines self.segmap and a segmentation on the maxmap. - segmap_label: segmentation map used for the catalog, either the one given as input, otherwise self.segmap_cont. """ segmaps = {} ncolors = 0 for name in ("segmap_cont", "segmap_merged", "segmap_purity", "segmap_label"): segm = getattr(self, name, None) if segm: segmaps[name] = segm ncolors = max(ncolors, len(np.unique(segm._data))) nseg = len(segmaps) if nseg == 0: self.logger.warning("nothing to plot") return try: # TODO: this will be renamed to make_random_cmap in a future # version of photutils from photutils.utils.colormaps import random_cmap except ImportError: self.logger.error("photutils is needed for this") cmap = "jet" else: cmap = random_cmap(ncolors=ncolors) cmap.colors[0] = (0.0, 0.0, 0.0) if axes is None: figsize = (figsize[0] * nseg, figsize[1]) fig, axes = plt.subplots(1, nseg, sharex=True, sharey=True, figsize=figsize) if nseg == 1: axes = [axes] for ax, (name, im) in zip(axes, segmaps.items()): im.plot(ax=ax, cmap=cmap, title=name, colorbar="v") def plot_min_max_hist(self, ax=None, comp=False): """Plot the histograms of local maxima and minima.""" if comp: cube_local_max = self.cube_std_local_max._data cube_local_min = self.cube_std_local_min._data else: cube_local_max = self.cube_local_max._data cube_local_min = self.cube_local_min._data if ax is None: fig, ax = plt.subplots(1, 1, figsize=(12, 6)) ax.set_yscale("log") ax.grid(which="major", linewidth=1) ax.grid(which="minor", linewidth=1, linestyle=":") maxloc = cube_local_max[cube_local_max > 0] bins = np.arange((maxloc.max() + 1) * 2) / 2 ax.hist(maxloc, bins=bins, histtype="step", label="max", linewidth=2, cumulative=-1) minloc = cube_local_min[cube_local_min > 0] bins = np.arange((minloc.max() + 1) * 2) / 2 ax.hist(minloc, bins=bins, histtype="step", label="min", linewidth=2, cumulative=-1) minloc2 = cube_local_min[:, self.segmap_purity._data == 0] minloc2 = minloc2[minloc2 > 0] ax.hist( minloc2, bins=bins, histtype="step", label="min filt", linewidth=2, cumulative=-1, ) ax.legend() ax.set_title("Cumulative histogram of min/max loc") def timestat(self, table=False): """Print CPU usage by steps. If ``table`` is True, an astropy.table.Table is returned. """ if table: name = [] exdate = [] extime = [] tot = 0 for s in self.steps.items(): if "execution_date" in s[1].meta.keys(): name.append(s[1].method_name) exdate.append(s[1].meta["execution_date"]) t = s[1].meta["runtime"] tot += t extime.append(datetime.timedelta(seconds=t)) name.append("Total") exdate.append("") extime.append(str(datetime.timedelta(seconds=tot))) return Table( data=[name, exdate, extime], names=["Step", "Exec Date", "Exec Time"], masked=True, ) else: tot = 0 for s in self.steps.items(): name = s[1].method_name if "execution_date" in s[1].meta.keys(): exdate = s[1].meta["execution_date"] t = s[1].meta["runtime"] tot += t extime = datetime.timedelta(seconds=t) self.logger.info("%s executed: %s run time: %s", name, exdate, str(extime)) self.logger.info("*** Total run time: %s", str(datetime.timedelta(seconds=tot))) def stat(self): """Print detection summary.""" d = self._get_stat() self.logger.info( "ORIGIN PCA pfa %.2f Back Purity: %.2f " "Threshold: %.2f Bright Purity %.2f Threshold %.2f", d["pca"], d["back_purity"], d["back_threshold"], d["bright_purity"], d["bright_threshold"], ) self.logger.info("Nb of detected lines: %d", d["tot_nlines"]) self.logger.info( "Nb of sources Total: %d Background: %d Cont: %d", d["tot_nsources"], d["back_nsources"], d["cont_nsources"], ) self.logger.info( "Nb of sources detected in faint (after PCA): %d " "in std (before PCA): %d", d["faint_nsources"], d["bright_nsources"], ) def _get_stat(self): p = self.param cat = self.Cat3_sources if cat: back = cat[cat["seg_label"] == 0] cont = cat[cat["seg_label"] > 0] bright = cat[cat["comp"] == 1] faint = cat[cat["comp"] == 0] return dict( pca=p["compute_PCA_threshold"]["params"]["pfa_test"], back_purity=p["purity"], back_threshold=p["threshold"], bright_purity=p["purity_std"], bright_threshold=p["threshold_std"], tot_nlines=len(self.Cat3_lines), tot_nsources=len(cat), back_nsources=len(back), cont_nsources=len(cont), faint_nsources=len(faint), bright_nsources=len(bright), )
dest_count=[] dest_n=[] for i in range(len(dest_wave)): dest_count.append(0) dest_n.append(0) print wave_start, wave_end, wave_step, len(dest_wave) for cubeid in cubelist: if "#" in cubeid: continue if "X" in cubeid: break qso=cubeid.split()[0] #orginal fluxcube #qso in fields.txt holds all identifiers such as 'J0014m0028' cube=Cube('%s/cube.fits' % qso) #the (current) final full catalogs #http://tucana.astro.physik.uni-potsdam.de/~mwendt/2020/mf/catalogs_final/ catalog=open('../mfcatalogs/catalog_%s.txt' % qso,'r') tags=catalog.readline() average_ptr=tags.split(',').index(' average') print 'pointer to average:',average_ptr print 'qso', qso plt.figure(figsize=(16,9)) count=0 for line in catalog: if 'id' in line: continue
args = parser.parse_args() if len(vars(args)) < 2: print 'Missing argument' print 'Usage: python measure_psf.py cube star_list' sys.exit() f = open('psf_results.dat','w') f.write('Wavelengh (A) Gaussian FWHM (") Moffat n/beta (") Moffat fwhm (") Moffat alpha(")\n') f.write('--------------------------------------------------------------------------------------------------------\n') ## Read star list and open output results id,ra,dec = np.loadtxt(args.star_list,unpack=True) ## Load cube and bin c = Cube(args.cube) c = c.rebin((2*368,1,1)) sample_fwhm = [] sample_gfwhm= [] sample_n = [] sample_alpha = [] white = c.sum(axis=0) (gfwhm,std_gfwhm,fwhm,std_fwhm,n,std_n) = measure_psf(white,ra,dec) alpha = fwhm / (2 *np.sqrt(2 * (1/n) - 1)) err_alpha = std_fwhm / (2 *np.sqrt(2 * (1/n) - 1)) + std_n * fwhm / (2 * np.sqrt(2/n- n*n)) ## ugly derivative with Wolfram Alpha f.write('white light \t %0.2f +/- %0.2f \t %0.2f +/- %0.2f \t %0.2f +/- %0.2f \t %0.2f +/- %0.2f \n' %(gfwhm,std_gfwhm,n,std_n,fwhm,std_fwhm, alpha,err_alpha)) f.write('--------------------------------------------------------------------------------------------------------\n') for k in range(0,c.shape[0]):
def make_MagE_cube_v2(config_file, format='txt'): """A new version to create the MagE cubes, it uses MPDAF objects. It works for .fits 1-d spectra files stored in a directory with a specific format. In particular: XXX_yyy_??.txt Errors are looked for in the same directory with *_sig.fits extension Parameters ---------- config_file : str Configuration file with important parameters. See mage.dump_config_make_MagE_cube() format : str Either 'fits' of 'txt' Returns: Cube fits files associated to each "XX_yyy" """ # read config_filename and add filename to params f = open(config_file) params = json.load(f) params['config_filename'] = config_file # reference files reference_mage_file = params['reference_mage'] reference_muse_file = params['reference_muse'] # Add comment on how the header was created comment = 'Cube created by pyntejos.mage.make_MagE_cube.py based on headers from {} and {}. Astrometry ' \ 'and wavelength axis given by input configuration ' \ 'file {}.'.format(reference_mage_file.split('/')[-1],reference_muse_file.split('/')[-1], params['config_filename']) # Print message print('') print(comment.replace("Cube created", "MagE cube will be created")) dirname = params['directory_mage'] # filenames = glob.glob(dirname+"/*syn_??.fits") if format == 'txt': rootname = params["rootname"] print('rootname: '+rootname) filenames = glob.glob(dirname+"/{}_??.txt".format(rootname)) # print(filenames) elif format == 'fits': raise NotImplementedError("Not properly implemented. It's almost there but depends on the file structures.") if len(filenames) == 0: raise ValueError("No files found matching the rootname: {}".format(rootname)) # sort them filenames.sort() # create the new datacube structure # spec_ref = readspec(filenames[0]) nw, ny, nx = params['NAXIS3'], params['NAXIS2'], params['NAXIS1'] # get wavecoord # hdr = spec_ref.header # hdr['CUNIT1'] = 'Angstrom' crpix = params['CRPIX3'] cdelt = params['CD3_3'] crval = params['CRVAL3'] wave = WaveCoord(hdr=None, crpix=crpix, cdelt=cdelt, crval=crval, cunit=u.AA, ctype='LINEAR', shape=None) # wave = WaveCoord(hdr=hdr) # get WCS crpix_yx = params['CRPIX2'], params['CRPIX1'] # note y,x order crval_decra = params['CRVAL2'], params['CRVAL1'] # ditto cdelt_yx = params['PIXSCALE_Y'], -1*params['PIXSCALE_X'] # ditto (negative to decrease towards east) PA = params['POS_ANGLE'] if PA == "None": # get angle from MagE reference header print("No PA given in parameter file. Reding PA from MagE reference .fits header") hdulist_mage = fits.open(params['reference_mage']) PA = hdulist_mage[0].header['ROTANGLE'] - 44.5 # this is the current offset in angle print("PA={}deg".format(PA)) shape = (ny, nx) wcs = WCS(crpix=crpix_yx, crval=crval_decra, cdelt=cdelt_yx, deg=True, shape=shape, rot=-1*PA) # for some reason negative PA works # redefine wcs to have CD matrix rather than PC matrix if 1: #rot != 0: hdr = wcs.to_header() hdr.rename_keyword('PC1_1','CD1_1') hdr.rename_keyword('PC2_1','CD2_1') hdr.rename_keyword('PC1_2','CD1_2') hdr.rename_keyword('PC2_2','CD2_2') # hdr['CD1_1'] = hdr['PC1_1'] # hdr['CD2_1'] = hdr['PC2_1'] # hdr['CD1_2'] = hdr['PC1_2'] # hdr['CD2_2'] = hdr['PC2_2'] wcs = WCS(hdr=hdr) # create data structures data = np.zeros_like(np.ndarray(shape=(nw,ny,nx))) var = np.zeros_like(data) # read the files and fill the data cubes (cube and variance) print("Reading files from directory {} ordered as:".format(dirname)) for ii,fname in enumerate(filenames): fn = fname.split('/')[-1] yspaxel = ny - ii # larger y will be the northern one print("\t {}: {} --goes to--> spaxel (1,{})".format(ii + 1, fn, yspaxel)) # MAKE SURE THE SPECTRA IS PROPERLY SORTED nfile = fn.split('.')[0].split('_')[-1] nfile = int(nfile) assert nfile == ii + 1, "The files in the directory are not sorted properly. Please check." if format=='fits': try: spec = readspec(fname) if 0: # dummy spec.flux = 1 spec.sig = 0 data[:,yspaxel-1,0] = spec.flux.value # position "01" is the most north, y increase to north if np.isnan(spec.sig): try: spec_sig = readspec(fname.replace('flux','sigm')) spec.sig = spec_sig.flux.value except: spec.sig = 0 var[:,yspaxel-1,0] = (spec.sig.value)**2 except: print("Something is wrong with spectrum {}".format(fname.split('/')[-1])) import pdb; pdb.set_trace() raise ValueError("Something is wrong with spectrum {}".format(fname.split('/')[-1])) elif format=='txt': spec = read_single_mage_file(fname) data[:, yspaxel - 1, 0] = spec.flux var[:, yspaxel - 1, 0] = (spec.sig)**2 # import pdb;pdb.set_trace() # create the Cube object cube = Cube(data=data, var=var, wcs=wcs, wave=wave) outputname = '{}_cube_{}.fits'.format(params['rootname'], params['datestamp']) cube.write(outputname) print('Wrote file: {}'.format(outputname)) # create white image white = cube.sum(axis=0) white.write(outputname.replace('cube', 'white')) print('Wrote file: {}'.format(outputname.replace('cube', 'white'))) # import pdb; pdb.set_trace() return cube
def write(self, path=None, erase=False): """Save the current session in a folder that will have the name of the ORIGIN object (self.name). The ORIGIN.load(folder, newname=None) method will be used to load a session. The parameter newname will let the user to load a session but continue in a new one. Parameters ---------- path : str Path where the folder (self.name) will be stored. erase : bool Remove the folder if it exists. """ self._loginfo("Writing...") # adapt session if path changes if path is not None and path != self.path: if not os.path.exists(path): raise ValueError(f"path does not exist: {path}") self.path = path outpath = os.path.join(path, self.name) # copy outpath to the new path shutil.copytree(self.outpath, outpath) self.outpath = outpath self._setup_logfile(self.logger) if erase: shutil.rmtree(self.outpath) os.makedirs(self.outpath, exist_ok=True) # PSF if isinstance(self.PSF, list): for i, psf in enumerate(self.PSF): cube = Cube(data=psf, mask=np.ma.nomask, copy=False) cube.write(os.path.join(self.outpath, "cube_psf_%02d.fits" % i)) else: cube = Cube(data=self.PSF, mask=np.ma.nomask, copy=False) cube.write(os.path.join(self.outpath, "cube_psf.fits")) if self.wfields is not None: for i, wfield in enumerate(self.wfields): im = Image(data=wfield, mask=np.ma.nomask) im.write(os.path.join(self.outpath, "wfield_%02d.fits" % i)) if self.ima_white is not None: self.ima_white.write("%s/ima_white.fits" % self.outpath) for step in self.steps.values(): step.dump(self.outpath) # parameters in .yaml with open(f"{self.outpath}/{self.name}.yaml", "w") as stream: dump_yaml(self.param, stream) # step3 - saving this manually for now if self.nbAreas is not None: if self.testO2 is not None: for area in range(1, self.nbAreas + 1): np.savetxt("%s/testO2_%d.txt" % (self.outpath, area), self.testO2[area - 1]) if self.histO2 is not None: for area in range(1, self.nbAreas + 1): np.savetxt("%s/histO2_%d.txt" % (self.outpath, area), self.histO2[area - 1]) if self.binO2 is not None: for area in range(1, self.nbAreas + 1): np.savetxt("%s/binO2_%d.txt" % (self.outpath, area), self.binO2[area - 1]) self._loginfo("Current session saved in %s", self.outpath)
def determine_best_astrometry(magecube_filename, musecube_filename, xypa_tuples, chi2_wvrange, renorm_wvrange, plot_wvrange, plot=True): """Utility for determining the best astrometry for a MagE Cube based on a comparisom with a reference MUSE Cube. It will use different (xc, yc, PA) positions taken from xypa_tuples for a virtual MagE slit on a MUSE reference datacube and will determine the set that has the minimum Chi2 within a spectral region. It will return a astropy.table.Table object with the results. Parameters ---------- magecube : str Filename of the MagE cube musecube_filename : str Filename of the MUSE reference cube xyc_tuples: list of tuples List of (xc, yc, PA) tuples xc and yc are in units of MUSE pixels and PA is in the position angle in degrees chi2_wvrange : (float, float) Wavelength range for performing the chi2 comparison. Ideally it has to be a small region centred in a spectral feature (e.g. emission line) renorm_wvrange : (float, float) Wavelength range for performing the flux renormalization of MagE to MUSE. Ideally it has to be a small region close to `chi2_range`. plot_wvrange : (float, float) Wavelength range for plotting. Must be larger than either renorm_range or chi2_range. plot : bool Whether to plot the iterations for visual inspection Returns ------- results : astropy.table.Table Table with the results """ from PyMUSE import musecube as mc import os # load the cubes musecube_pymuse = mc.MuseCube(musecube_filename) musecube = Cube(musecube_filename) magecube_orig = Cube(magecube_filename) # create master subdir master_dirname = magecube_filename.split('/')[-1].replace('.fits', '_astrometry_runs') if not os.path.exists(master_dirname): os.makedirs(master_dirname) # define mage slit geometry l=0.3*3*11 w=1. n=11 pixscale = 0.2 # of MUSE datacube # create a table to store the results tab = Table() # will create auxiliary cubes from MUSE to match geometry of mage names = [] if 1: # left only for the indentantion if 1: # left for the indentation for tup in xypa_tuples: xc, yc, pac = tup rootname = '{:.1f}-{:.1f}-{:.1f}'.format(xc,yc,pac) rootname = rootname.replace('.','p') names += [rootname] if not os.path.exists(master_dirname+'/'+rootname): os.makedirs(master_dirname+'/'+rootname) output = master_dirname + '/' + rootname + '/mage_slit_in_muse_{}.reg'.format(rootname) ntr.create_regfile_nboxes_from_slit(output, (xc,yc), pac, l, w, n, pixscale) # define name newcube_name = master_dirname + '/' + rootname + '/magecube_from_muse_{}.fits'.format(rootname) # check whether this cube already exists if os.path.isfile(newcube_name): print('File already exists, skiping: {}'.format(newcube_name.split('/')[-1])) continue else: print('Writing file: {}'.format(newcube_name)) # create the data structure nw, ny, nx = magecube_orig.shape nw = musecube.shape[0] # replace mw with that of muse datacube data = np.zeros(shape=(nw, ny, nx)) var = np.zeros_like(data) # get the spectra from MUSE in the given region for ii in range(n): plt.figure(musecube_pymuse.n) spec = musecube_pymuse.get_spec_from_ds9regfile(output, mode='sum', i=ii, frac=0.1, npix=0, empirical_std=False, n_figure=None, save=False, save_mask=False, plot=False) # pymuse gets spectrum in vacuum, thus we will convert to air # because the MUSEcube reduction is in air # does not seem important, but I leave this here for transparency spec.vactoair() data[:, ny-ii-1, 0] = spec.flux var[:, ny-ii-1, 0] = spec.sig**2 # import pdb; pdb.set_trace() # redefine the structure magecube_new = Cube(wcs=magecube_orig.wcs, wave=musecube.wave, data=data, var=var) newcube_name = master_dirname + '/' + rootname + '/magecube_from_muse_{}.fits'.format(rootname) magecube_new.write(newcube_name) # plt.show() tab['name'] = names tab['xc'] = [float(name.split('-')[0].replace('p','.')) for name in tab['name']] tab['yc'] = [float(name.split('-')[1].replace('p','.')) for name in tab['name']] tab['PA'] = [float(name.split('-')[2].replace('p','.')) for name in tab['name']] # now read the cubes and perform the chi2 chi2_spec = [] chi2_flux = [] fl_mage_11 = [] fl_muse_11 = [] for jj, name in enumerate(tab['name']): newcube_name = master_dirname + '/' + name + '/magecube_from_muse_{}.fits'.format(name) magecube1 = magecube_orig # MagE original magecube2 = Cube(newcube_name) # from MUSE chi2_s, chi2_f, fl_mage, fl_muse = compute_chi2_magecubes(magecube1, magecube2, chi2_wvrange=chi2_wvrange, renorm_wvrange=renorm_wvrange, plot_wvrange=plot_wvrange, plot=plot, text1= 'MagE', text2='MUSE') print("{} [{}/{}]".format(name, jj + 1, len(tab))) print(" Total Chi2_spec/DOF = {:.1f}".format(chi2_s)) print(" Total Chi2 flux/DOF = {:.1f}".format(chi2_f)) chi2_spec += [chi2_s] chi2_flux += [chi2_f] fl_mage_11 += [fl_mage] fl_muse_11 += [fl_muse] if plot: fig = plt.gcf() fig.suptitle(name) tab['fl_muse_11'] = fl_muse_11 tab['fl_mage_11'] = fl_mage_11 tab['chi2_spec'] = chi2_spec tab['chi2_flux'] = chi2_flux return tab
galPA = 55 galcen = SkyCoord(ra=237.52059628552735 * u.degree, dec=-78.188149277705151 * u.degree, frame='icrs') specwidth = [14, 36] s_n_mask = np.load('out_r4_3.npy') all_pixels = [] for i in range(len(s_n_mask)): posi = [s_n_mask[i][0], s_n_mask[i][1]] all_pixels.append(posi) all_pixels = np.asarray(all_pixels) datacube = Cube('nor_cut_cube.fits') xlen = len(datacube[0, :, 0].data) ylen = len(datacube[0, 0, :].data[0]) wcs1 = WCS(crval=0, cdelt=0.2) MyData = np.ones((xlen, ylen)) ima = Image(data=MyData, wcs=wcs1) for i in range(xlen): for j in range(ylen): par = params.valuesdict() incli = par['incli'] col_denst = par['col_dens'] h = par['height'] bes = par['dop_param']
parser.add_argument("mask",type=str, help = 'mask to be used. Image with 1 for masked regions (objects) and 0 for regions to be used to calculate the meadian background (sky)') parser.add_argument("cube_out", type=str, help = 'path to the output (corrected) cube') parser.add_argument("--invert", type=str, help = 'if True, 0 for masked regions (objects) and 1 for sky (old ZAP version)') args = parser.parse_args() if len(vars(args)) < 3: print 'Missing argument' print 'Usage: python rowcol.py cube_in mask cube_out --invert' sys.exit() else: print('Using multiprocessing') c=Cube(args.cube_in) immask = Image(args.mask) mask = immask.data.astype(bool) if args.invert: print('Using inverted mask') mask = np.invert(mask) c.data.mask[:, mask] = True manager = mtp.Manager() return_dict = manager.dict() tstart = time.time() #pool = Pool(processes=20) #pool.map(dummy_function, range(c.shape[0])) dummy_im = c[1,:,:].copy()