Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
    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()
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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')
Ejemplo n.º 5
0
    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
Ejemplo n.º 6
0
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])
Ejemplo n.º 7
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')
Ejemplo n.º 8
0
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
Ejemplo n.º 9
0
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))
Ejemplo n.º 10
0
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
Ejemplo n.º 11
0
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)
Ejemplo n.º 12
0
    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
Ejemplo n.º 13
0
    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)
Ejemplo n.º 14
0
 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)
Ejemplo n.º 15
0
 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')
Ejemplo n.º 16
0
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
Ejemplo n.º 18
0
    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))
Ejemplo n.º 19
0
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
Ejemplo n.º 20
0
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
Ejemplo n.º 21
0
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
Ejemplo n.º 22
0
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
Ejemplo n.º 23
0
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)
Ejemplo n.º 24
0
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
Ejemplo n.º 25
0
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
Ejemplo n.º 26
0
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)
Ejemplo n.º 27
0
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'
Ejemplo n.º 28
0
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)
Ejemplo n.º 29
0
    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
Ejemplo n.º 30
0
    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")
Ejemplo n.º 31
0
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),
        )
Ejemplo n.º 32
0
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
Ejemplo n.º 33
0
        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]):
Ejemplo n.º 34
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
Ejemplo n.º 35
0
    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)
Ejemplo n.º 36
0
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
Ejemplo n.º 37
0
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']
Ejemplo n.º 38
0
        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()