Skip to content
map2d.py 5.7 KiB
Newer Older
Shuai Feng's avatar
Shuai Feng committed
from __future__ import division

import scipy.special as sp
import numpy as np
from astropy.io import fits
from skimage.transform import resize

def Sersic2D(x, y, mag = 12, r_eff = 1, n = 2, ellip = 0.5, 
             theta = 0, x_0 = 0, y_0 = 0, pixelscale = 0.01):
    
    # Produce Sersic profile
    bn = sp.gammaincinv(2. * n, 0.5)
    a, b = r_eff, (1 - ellip) * r_eff
    cos_theta, sin_theta = np.cos(theta), np.sin(theta)
    x_maj = (x - x_0) * cos_theta + (y - y_0) * sin_theta
    x_min = -(x - x_0) * sin_theta + (y - y_0) * cos_theta
    z = (abs(x_maj / a) ** 2 + abs(x_min / b)  ** 2) ** (1 / 2)
    profile = np.exp(-bn * (z ** (1 / n) - 1))
    
    # Normalization
    integral = a * b * 2 * np.pi * n * np.exp(bn) / (bn ** (2 * n)) * sp.gamma(2 * n)
    prof_norm = profile / integral * pixelscale
    
    # Calibration
    total_flux = 10. ** ((22.5 - mag) * 0.4)
    sb_mag = 22.5 - 2.5 * np.log10(prof_norm * total_flux / pixelscale)
    
    return sb_mag

def VelMap2D(x, y, vmax = 200, rt = 1, ellip = 0.5, 
             theta = 0, x_0 = 0, y_0 = 0):
    
    # Produce tanh profile
    a, b = rt, (1 - ellip) * rt
    cos_theta, sin_theta = np.cos(theta), np.sin(theta)
    x_maj = (x - x_0) * cos_theta + (y - y_0) * sin_theta
    x_min = -(x - x_0) * sin_theta + (y - y_0) * cos_theta
    z = (abs(x_maj / a) ** 2 + abs(x_min / b)  ** 2) ** (1 / 2)
    profile = vmax * np.tanh(z) * ((x_maj / a) / z)
    
    return profile

def GradMap2D(x, y, a0 = 10, r_eff = 1, gred = -1, ellip = 0.5, 
              theta = 0, x_0 = 0, y_0 = 0):
    
    # Produce gradiant profile
    a, b = r_eff, (1 - ellip) * r_eff
    cos_theta, sin_theta = np.cos(theta), np.sin(theta)
    x_maj = (x - x_0) * cos_theta + (y - y_0) * sin_theta
    x_min = -(x - x_0) * sin_theta + (y - y_0) * cos_theta
    z = (abs(x_maj / a) ** 2 + abs(x_min / b)  ** 2) ** (1 / 2)
    profile = a0 + z * gred
    
    return profile

class Map2d(object):

    """
    Generate an x, y grid in a rectangular region, sampled with xsamp and
    ysamp spacings in the x and y directions, respectively.  Origin is at the
    upper-left corner to match numpy and matplotlib convention. astropy.io.fits
    also assumes UL origin by default.

    Parameters
    ----------
    xsamp, ysamp : float, float
        The sampling spacing in the x and y directions.
    nx, ny: int, int
        Number of samples in the x and y directions.
    """

    def __init__(self, inst):
        self.xsamp = inst.dpix
        self.ysamp = inst.dpix
        startx = -(inst.nx - 1) / 2.0 * self.xsamp
        stopx  = (inst.nx - 1) / 2.0 * self.xsamp
        starty = -(inst.ny - 1) / 2.0 * self.ysamp
        stopy  = (inst.ny - 1) / 2.0 * self.ysamp
        xvals  = np.linspace(startx, stopx, num = inst.nx)
        yvals  = np.linspace(starty, stopy, num = inst.ny)

        ones = np.ones((inst.ny, inst.nx))
        x = ones * xvals
        y = np.flipud(ones * yvals.reshape(int(inst.ny), 1))

        self.nx = inst.nx
        self.ny = inst.ny
        self.x  = x
        self.y  = y
        self.row = xvals
        # flip Y axis because we use Y increasing from bottom to top
        self.col = yvals[::-1]
    
    def sersic_map(self, mag = 12, r_eff = 2, n = 2.5, ellip = 0.5, theta = -50):
        self.mag   = mag
        self.reff  = r_eff / self.xsamp
        self.n     = n
        self.ellip = ellip
        self.theta = theta
        self.map = Sersic2D(self.x, self.y, mag = self.mag, 
                            r_eff = self.reff, n = self.n, 
                            ellip = self.ellip, theta = self.theta, 
                            pixelscale = self.xsamp * self.ysamp)
        
    def tanh_map(self, vmax = 200, rt = 2, ellip = 0.5, theta = -50):
        self.vmax  = vmax
        self.rt    = rt / self.xsamp
        self.ellip = ellip
        self.theta = theta
        self.map = VelMap2D(self.x, self.y, vmax = self.vmax, rt = self.rt, 
                            ellip = self.ellip, theta = self.theta)
    
    def gred_map(self, a0 = 10, r_eff = 1, gred = -1, ellip = 0.5, theta = 0):
        self.a0    = a0
        self.reff  = r_eff / self.xsamp
        self.gred  = gred
        self.ellip = ellip
        self.theta = theta
        self.map = GradMap2D(self.x, self.y, a0 = self.a0, r_eff = self.reff, 
                             gred = self.gred, ellip = self.ellip, theta = self.theta)
        
    def load_map(self, image):
        if np.ndim(image) == 2:
            self.map = resize(image, (self.nx, self.ny))
            
class StellarPopulationMap():
    
    def __init__(self, inst, sbright = None, logage = None, 
                 feh = None, vel = None, vdisp = None, ebv = None):
        self.nx    = inst.nx
        self.ny    = inst.ny
        self.dpix  = inst.dpix
        self.fov_x = inst.fov_x
        self.fov_y = inst.fov_y
        
        self.sbright = sbright.map
        self.logage  = logage.map
        self.feh     = feh.map
        self.vel     = vel.map
        self.vdisp   = vdisp.map
        self.ebv     = ebv.map
        
        self.mag = self.sbright + 2.5 * np.log10(self.dpix * self.dpix)
        self.age = 10 ** self.logage / 1e9
        
        self.vdisp[self.vdisp < 10] = 10
        self.ebv[self.ebv < 0]      = 0
        
class IonizedGasMap():
    
    def __init__(self, inst, halpha = None, zh = None, vel = None, vdisp = None, ebv = None):
        
        self.nx    = inst.nx
        self.ny    = inst.ny
        self.dpix  = inst.dpix
        self.fov_x = inst.fov_x
        self.fov_y = inst.fov_y
        
        self.halpha = halpha.map
        self.zh     = zh.map
        self.vel    = vel.map
        self.vdisp  = vdisp.map
        self.ebv    = ebv.map
        
        self.vdisp[self.vdisp < 10] = 10
        self.ebv[self.ebv < 0]      = 0