This notebook will be used to develop code for when the airglow emission rate should be treated as known to improve the inversion. (e.g., terminator crossover)

How do we handle the terminator? One way is to use a model for airglow VER. Treating VER as known has two aspects:

  1. How does this change the onion-peeling?
  2. What location do we ascribe the resulting wind to?

For item 1:

Measurement model: $$ \underbrace{g_i}_{\textrm{measured interferogram}} = \sum_{j=1}^{N} ~~~\underbrace{a(\bar{r}_{ij})}_{\textrm{VER}}~~~~ \underbrace{f(\bar{r}_{ij})}_{\textrm{interferogram}} ~~~~~\underbrace{\Delta s_{ij}}_{\textrm{path length}}$$

Usually we assume:

  • VER is spherically symmetric: $ a(\bar{r}_{ij}) = a(r_{j}) $
  • winds and temps are spherically symmetric: $ f(\bar{r}_{ij}) = f(r_{j}) $
$$ g_i= \sum_{j=1}^{N} a(r_{j})f(r_{j}) \Delta s_{ij}$$

And we solve for $a(r_{j})f(r_{j})$ by creating path length matrix using $\Delta s_{ij}$

If airglow is known:

  • VER is not spherically symmetric: $ a(\bar{r}_{ij})$
  • winds and temps are spherically symmetric: $ f(\bar{r}_{ij}) = f(r_{j}) $
$$ g_i= \sum_{j=1}^{N} f(r_{j}) a(\bar{r}_{ij}) \Delta s_{ij}$$

Solve for $f(r_{j})$ by creating path length matrix using $a(\bar{r}_{ij}) \Delta s_{ij}$

In [468]:
%pylab inline
from Scientific.IO.NetCDF import NetCDFFile as Dataset
from datetime import datetime, timedelta
from pyglow import pyglow
from mpl_toolkits.basemap import Basemap
import os
import glob
from scipy.interpolate import griddata
import subprocess
import MIGHTI
import ICON
import sys
import time as time_module
from IPython.display import display, clear_output

# Make default figure size bigger
matplotlib.rcParams['savefig.dpi'] = 150
matplotlib.rcParams['figure.figsize'] = (4,3)
matplotlib.rcParams['font.size'] = 8
Populating the interactive namespace from numpy and matplotlib
WARNING: pylab import has clobbered these variables: ['f', 'unwrap', 'datetime', 'griddata']
`%matplotlib` prevents importing * from pylab and numpy

Simulate L1 data product

In [469]:
# Ignore satellite motion for now. (Include velocity in phase, though).
dt = datetime(2013,9,23,2,0,0) # UT
icon_alt = 550.
icon_lat = 20.5
icon_lon = 180. # daytime
icon_velocity = 0.0 # this will likely not be implemented
az = 90. # so that we're only measuring zonal wind
ze_top = 103
ze_bot = 109
ny = 50
nk = 101
#ny = 50
#nk = 501
impose_spherical_symmetry_winds = True
impose_spherical_symmetry_temps = True
impose_spherical_symmetry_ver = True


satlla = [icon_lat, icon_lon, icon_alt]
# Let's say that ICON is going purely east. This shouldn't matter, since velocity is zero.
icon_ecef_ram_vector = ICON.ven_to_ecef(satlla, [0, 1, 0])


# LOAD MIGHTI CONSTANTS/PARAMETERS
emission_color = 'red' # 'red' or 'green'
day = True

emissions =  MIGHTI.get_emission_constants()
params = MIGHTI.get_instrument_constants()

params['exptime'] =        60.   # sec
params['lam'] =            emissions[emission_color]['lam']
params['frq'] =            emissions[emission_color]['frq']
params['mass'] =           emissions[emission_color]['mass']
if day: # 10% reduction in aperture size during "day", and shorter exposure time
    params['aperture_area'] =  params['aperture_area'] * 0.1
    params['exptime'] =        30.


ray_length = 6000./nk
print '%.2f km steps' % ray_length

tanlla_top = ICON.tangent_point(satlla, az, ze_top)
tanlla_bot = ICON.tangent_point(satlla, az, ze_bot)

zevec = linspace(ze_bot, ze_top, ny)
mighti_ecef_vectors = zeros((ny,3))
alts_along_ray = zeros((nk,ny))
lats_along_ray = zeros((nk,ny))
lons_along_ray = zeros((nk,ny))
tang_alts      = zeros(ny)
tang_lats      = zeros(ny)
tang_lons      = zeros(ny)

for i in range(ny):
    ze = zevec[i]
    # Determine tangent point
    tang_pt = ICON.tangent_point(satlla, az, ze)
    tang_alts[i] = tang_pt[2]
    tang_lats[i] = tang_pt[0]
    tang_lons[i] = tang_pt[1]
    # Determine the ECEF look direction and record it
    mighti_ecef_vectors[i,:] = ICON.azze_to_ecef(satlla, az, ze)
    # Project the line of sight and record winds, temps, and amplitudes along the ray
    xyz_proj,lla_proj = ICON.project_line_of_sight(satlla, az, ze, \
                                                   step_size = ray_length, total_distance=ray_length*nk)
    alts_along_ray[:,i] = lla_proj[2,:]
    lats_along_ray[:,i] = lla_proj[0,:]
    lons_along_ray[:,i] = lla_proj[1,:]

print '%.1f - %.1f km' % (tang_alts[-1], tang_alts[0])
59.41 km steps
372.4 - 172.4 km
In [470]:
i = 0
figure(figsize=(9,3))
subplot(1,3,1)
plot(lats_along_ray[:,i],'k')
subplot(1,3,2)
plot(lons_along_ray[:,i],'k')
subplot(1,3,3)
plot(alts_along_ray[:,i],'k')
Out[470]:
[<matplotlib.lines.Line2D at 0x7fdd67697410>]

Create functions that know about wind, temperature, and VER at every point

In [471]:
def get_airglow_VER(pt):
    #VER = exp(-0.5*((altk - 250)/(50))**2)
    VER = MIGHTI.get_redline_airglow(pt)
    return VER

def get_wind(pt):
    pt.run_hwm14()
    return pt.u,pt.v,0.0

def get_temperature(pt):
    pt.run_msis()
    return pt.Tn_msis
In [472]:
# Build the three quantities needed by forward model: VER, LoS wind, and temp
emission_along_ray_r = zeros((nk,ny))
u_along_ray = zeros((nk,ny))
v_along_ray = zeros((nk,ny))
w_along_ray = zeros((nk,ny))
winds_along_ray = zeros((nk,ny)) # LoS
temps_along_ray = zeros((nk,ny))
ven_along_ray = zeros((nk,ny,3))

for i in range(ny):
    look_xyz = mighti_ecef_vectors[i,:]
    for k in range(nk):
        latk = lats_along_ray[k,i]
        lonk = lons_along_ray[k,i]
        altk = alts_along_ray[k,i]
        
        ##### local VEN
        ven_look = ICON.ecef_to_ven([latk, lonk, altk], look_xyz)

        
        ##### winds
        if impose_spherical_symmetry_winds:
            pt = pyglow.Point(dt, icon_lat, icon_lon, altk)
        else:
            pt = pyglow.Point(dt, latk, lonk, altk)
        u,v,w = get_wind(pt)
        u_along_ray[k,i] = u
        v_along_ray[k,i] = v
        w_along_ray[k,i] = w
        winds_along_ray[k,i] = dot(ven_look,[w,u,v])
        
        
        ##### temperature
        if impose_spherical_symmetry_temps:
            pt = pyglow.Point(dt, icon_lat, icon_lon, altk)
        else:
            pt = pyglow.Point(dt, latk, lonk, altk)
        T = get_temperature(pt)
        temps_along_ray[k,i] = T
        
        
        ##### VER
        if impose_spherical_symmetry_ver:
            pt = pyglow.Point(dt, icon_lat, icon_lon, altk)
        else:
            pt = pyglow.Point(dt, latk, lonk, altk)
        VER = get_airglow_VER(pt)
        emission_along_ray_r[k,i] = VER*ray_length*1e3*1e6/1e10 # cm, km, 10^10 ph
        
       
    clear_output(wait=True)
    time_module.sleep(0.01)
    print '%03i/%03i' % (i+1,ny)
    sys.stdout.flush()
050/050
In [473]:
i = 0
figure(figsize=(9,3))
subplot(1,3,1)
plot(u_along_ray[:,i],'k')
title('u')
subplot(1,3,2)
plot(v_along_ray[:,i],'k')
title('v')
subplot(1,3,3)
plot(w_along_ray[:,i],'k')
title('w')
Out[473]:
<matplotlib.text.Text at 0x7fdd674dae10>
In [474]:
i = 0
In [475]:
figure(figsize=(9,3))
subplot(1,3,1)
plot(winds_along_ray[:,i],'k')
title('Winds')
subplot(1,3,2)
plot(temps_along_ray[:,i],'k')
title('Temps')
subplot(1,3,3)
plot(emission_along_ray_r[:,i],'k')
title('Emission')
tight_layout()

The following code has been more or less copied from MIGHTI_Convert_EnglandFile_to_UIUCL1.

Generate interferogram

In [476]:
nx = params['npixelx']
ny = shape(winds_along_ray)[1] # number of rows of interferogram
nk = shape(winds_along_ray)[0] # number of steps along ray

Iraw = np.zeros((ny,nx))

for i in range(ny):
    for k in range(nk):

        amp = emission_along_ray_r[k,i]
        vel = winds_along_ray[k,i]
        temp = temps_along_ray[k,i]      

        params['V'] = vel
        params['T'] = temp
        params['I'] = amp

        ccdslice = MIGHTI.interferogram(params)

        Iraw[i,:] += ccdslice
        
In [477]:
figure(figsize=(5,3))
X,TANH = meshgrid(arange(nx),tang_alts)
pcolormesh(X,TANH, Iraw,cmap='jet')
axis('tight')
xlabel('Interferogram Bin')
ylabel('Tangent height [km]')
title('Raw Noiseless Interferogram')
colorbar()
Out[477]:
<matplotlib.colorbar.Colorbar instance at 0x7fdd67113a70>

Add Noise (skipped for now)

In [478]:
#Inoisy = MIGHTI.add_noise(Iraw, params) # make sure units are right
Inoisy = Iraw.copy()

L1 processing

In [479]:
Ir = zeros(shape(Inoisy))
Ii = zeros(shape(Inoisy))
for i in range(shape(Inoisy)[0]):
    # Filter with Hann window (in frequency) surrounding the peak
    f = Inoisy[i,:]
    F = np.fft.fft(f)
    N = len(F)
    n = arange(N)
    # Create filter as per Ken Marr's email 2013/10/29
    peaki = abs(F[5:floor(N/2)]).argmax() + 5
    width1 = 20 # width of Hann window
    width2 = 5 # width of plateau
    hann = np.hanning(width1)
    # Create full filter
    H = hstack((zeros(peaki-width1/2-(width2-1)/2), 
                hann[:width1/2], 
                ones(width2), 
                hann[width1/2:], 
                zeros(N - peaki - width1/2 - (width2-1)/2 - 1)))
    ap = hanning(N)
    f = f - f.mean()
    fap = f*ap
    F = np.fft.fft(fap)
    F2 = F * H
    f2 = np.fft.ifft(F2)
    Ir[i,:] = np.real(f2)
    Ii[i,:] = np.imag(f2)
In [480]:
figure(figsize=(5,3))
X,TANH = meshgrid(arange(nx),tang_alts)
pcolormesh(X,TANH, Ir,cmap='RdBu')
axis('tight')

xlabel('Interferogram Bin')
ylabel('Tangent height [km]')
title('L1-Processed Interferogram [Real Part]')
Out[480]:
<matplotlib.text.Text at 0x7fdd6750f2d0>
In [481]:
# Save L1 UIUC file
# Assume no change from start to end of exposure.
fn = '/home/bhardin2/MIGHTI/MIGHTI_L1_UIUC_red_known_airglow.npz'
np.savez(fn,
         Ir=Ir, 
         Ii=Ii, 
         tang_alt_start=  tang_alts,
         tang_alt_end  =  tang_alts,
         tang_lat_start=  tang_lats,
         tang_lat_end  =  tang_lats,
         tang_lon_start=  tang_lons,
         tang_lon_end  =  tang_lons,
         emission_color=  'red',
         icon_alt_start=  icon_alt,
         icon_alt_end=    icon_alt,
         icon_lat_start=  icon_lat,
         icon_lat_end=    icon_lat,
         icon_lon_start=  icon_lon,
         icon_lon_end=    icon_lon,
         mighti_ecef_vectors_start=  mighti_ecef_vectors,
         mighti_ecef_vectors_end=    mighti_ecef_vectors,
         icon_ecef_ram_vector_start= icon_ecef_ram_vector,
         icon_ecef_ram_vector_end=   icon_ecef_ram_vector,
         icon_velocity_start=        icon_velocity,
         icon_velocity_end=          icon_velocity,
         datetime_created=           datetime.now(),
         source_files =              [],
         time =                      dt,)
print 'Saved to %s' % fn
Saved to /home/bhardin2/MIGHTI/MIGHTI_L1_UIUC_red_known_airglow.npz

Obtain truth los winds and save to separate file

In [482]:
# Create a column vector with projected satellite velocity. 
# Remember, we are ignoring horizontal extent for now.
proj_icon_vel = zeros(ny)
for i in range(ny):
    look_ecef = mighti_ecef_vectors[i,:] # look direction of this pixel in ECEF
    proj_icon_vel[i] = icon_velocity * np.dot(icon_ecef_ram_vector, look_ecef)
    
tang_point_along_ray = zeros(ny)
for i in range(ny):
    kstar = argmin(alts_along_ray[:,i])
    tang_point_along_ray[i] = kstar
    
truth_winds = zeros(ny)
truth_amps = zeros(ny)
for i in range(ny):
    kstar = tang_point_along_ray[i]
    truth_winds[i] = winds_along_ray[kstar,i] - proj_icon_vel[i]
    truth_amps[i] = emission_along_ray_r[kstar,i]
subplot(1,2,1)
plot(truth_winds,tang_alts,'k.-')
subplot(1,2,2)
plot(truth_amps,tang_alts,'k')

tfn = '/home/bhardin2/MIGHTI/truth_winds_known_airglow.npz'
np.savez(tfn,
         tang_alts=tang_alts,truth_winds=truth_winds,\
         truth_amps=truth_amps)
print 'Saved to %s' % tfn
Saved to /home/bhardin2/MIGHTI/truth_winds_known_airglow.npz

Run the files through the L1-L2.1 processing.

This is copied from the central source of this code: MIGHTI_UIUCL1_to_L21.ipynb
In [483]:
from Scientific.IO.NetCDF import NetCDFFile as Dataset
from datetime import datetime, timedelta
from pyglow import pyglow
from mpl_toolkits.basemap import Basemap
import os
import glob
from scipy.interpolate import griddata
import subprocess
import MIGHTI
import ICON

def unwrap(x):
    '''
    Phase-unwrap a signal in [-pi,pi] which is increasing
    '''
    dx = diff(x)
    xnew = zeros(len(x))
    xnew[0] = x[0]
    idx = dx < 0 # 0, or -pi ???
    dx[idx] = dx[idx] + 2*pi
    xnew[1:] = xnew[0] + cumsum(dx)
    return xnew

def circular_mean(angle0,angle1):
    '''
    Find the mean angle, taking into account 0/360 crossover.
    Input and output angles in degrees.
    '''
    return 180/pi*angle((exp(1j*angle0*pi/180.) + exp(1j*angle1*pi/180.))/2)

def level21(l1uiucfn, l21fn, show_plot=True):
    '''
    This attemps to do everything in the notebook above. Set l21fn='' to not save anything
    '''

    Nignore = 20 # ignore first and last few samples due to wraparound ringing (Can this be fixed by zero-padding?)
    # ^ This is replicated in L1 code, but that's ok. As long as this one is larger.
    phase_offset = -pi # This constant is added to all phases so that there is no chance of a pi/-pi crossover.
                      # In practice, we'll have to see how the interferometer turns out before deciding on this.
    c = 299792458.0 # m/s, speed of light
    # Specify ranges for plotting (even though the whole image is analyzed)
    min_alt = {'red': 100, 'green': 87}
    max_alt = {'red': 400, 'green': 200}
    min_amp = 0.0 # if amplitude of fringe is below this, don't even try to analyze it

    # Zero wind/phase (TODO: how will this be done for real, and what's the best way to simulate it?)
    zero_phase = 252.07801858 + phase_offset# - 0.00196902 # last term added 2015-05-18 for vertical wind study

    npzfile = load(l1uiucfn)

    # Extract all information from the UIUC L1 file
    Ii = npzfile['Ii']
    Ir = npzfile['Ir']
    emission_color = str(npzfile['emission_color'])
    icon_alt_end = npzfile['icon_alt_end']
    icon_alt_start = npzfile['icon_alt_start']
    icon_lat_end = npzfile['icon_lat_end']
    icon_lat_start = npzfile['icon_lat_start']
    icon_lon_end = npzfile['icon_lon_end']
    icon_lon_start = npzfile['icon_lon_start']
    icon_velocity_start = npzfile['icon_velocity_start']
    icon_velocity_end = npzfile['icon_velocity_end']
    icon_ecef_ram_vector_start = npzfile['icon_ecef_ram_vector_start']
    icon_ecef_ram_vector_end = npzfile['icon_ecef_ram_vector_end']
    mighti_ecef_vectors_end = npzfile['mighti_ecef_vectors_end']
    mighti_ecef_vectors_start = npzfile['mighti_ecef_vectors_start']
    tang_alt_end = npzfile['tang_alt_end']
    tang_alt_start = npzfile['tang_alt_start']
    tang_lat_end = npzfile['tang_lat_end']
    tang_lat_start = npzfile['tang_lat_start']
    tang_lon_end = npzfile['tang_lon_end']
    tang_lon_start = npzfile['tang_lon_start']
    datetime_created = npzfile['datetime_created']
    source_files = npzfile['source_files']
    time = npzfile['time'] # we should really have start time and end time
    #print 'Reading file %s' % l1uiucfn
    #print 'Created %s' % datetime_created

    # Take the midpoint of start and end.
    icon_alt = (icon_alt_start+icon_alt_end)/2
    icon_lat = (icon_lat_start+icon_lat_end)/2
    icon_lon = circular_mean(icon_lon_start, icon_lon_end)
    mighti_ecef_vectors = (mighti_ecef_vectors_start + mighti_ecef_vectors_end)/2 # ny by 3
    tang_alts = (tang_alt_start + tang_alt_end)/2
    tang_lats = (tang_lat_start + tang_lat_end)/2
    tang_lons = circular_mean(tang_lon_start, tang_lon_end)
    icon_ecef_ram_vector = (icon_ecef_ram_vector_start + icon_ecef_ram_vector_end)/2
    icon_velocity = (icon_velocity_start+icon_velocity_end)/2


    Ntheta = shape(Ir)[0]
    npixel = shape(Ir)[1]

    params = MIGHTI.get_instrument_constants()
    emission = MIGHTI.get_emission_constants()[emission_color]

    I         = (Ir + 1j*Ii) * exp(1j*phase_offset)
    theta     = MIGHTI.tanht2angle(tang_alts, icon_alt)
    RE        = 6371 # TODO
    startpath = params['startpath']
    endpath   = params['endpath']
    sigma     = 1.0/emission['lam']
    
    
    
    # Create a column vector with projected satellite velocity. Remember, we are ignoring horizontal extent for now.
    proj_icon_vel = zeros(Ntheta)
    for i in range(Ntheta):
        look_ecef = mighti_ecef_vectors[i,:] # look direction of this pixel in ECEF
        proj_icon_vel[i] = icon_velocity * np.dot(icon_ecef_ram_vector, look_ecef)

    # Transform velocity to phase 
    # dphi = 2*pi*OPD*sigma*v/c (Eqn 1 of Englert et al 2007 Appl Opt.)
    # Use mean(opd) for now, but eventually it will be actual OPD once we include horizontal extent.
    opd = linspace(startpath,endpath,npixel)
    icon_vel_phase = 2*pi*mean(opd[Nignore:-Nignore])*sigma*proj_icon_vel/c

    # Subtract phase from the interferogram (Change this when horizontal extent is included)
    corr = exp(-1j*icon_vel_phase)
    for jj in range(npixel):
        I[:,jj] = I[:,jj]*corr
        
 
    # Define grid. Bottom of each layer is defined by tangent height of observation.
    rbottom = MIGHTI.angle2tanht(theta, icon_alt)
    # Define top of each layer.
    rtop = rbottom.copy()
    rtop[:-1] = rbottom[1:]
    rtop[-1] = rbottom[-1] + (rtop[1]-rbottom[1])
    # Define midpt of each layer
    rmid = (rbottom + rtop)/2

    # Build observation matrix
    D = zeros((Ntheta, len(rmid)))
    for i in range(Ntheta):
        for j in range(len(rmid)):
            th = theta[i]
            rb = rbottom[j]
            rt = rtop[j]
            sb = cos(th)**2 - 1 + ((RE+rb)/(RE+icon_alt))**2
            st = cos(th)**2 - 1 + ((RE+rt)/(RE+icon_alt))**2
            if sb < 0: # there is no intersection of LOS with altitude rb. Set term to 0.
                # Note: this might be due to numerical rounding for tangent altitude. 
                # Do the same thing either way.
                sb = 0.
            if st < 0: # there is no intersection of LOS with altitude rt. Set term to 0.
                st = 0.
            D[i,j] = 2*(RE+icon_alt) * ( sqrt(st) - sqrt(sb) )    

    # Quick check to see if phase is near the pi/-pi crossover
    # TODO: revamp this, including zero-wind removal
    init_phase = zeros((Ntheta))
    for j in range(Ntheta):
        irow = I[j,:]
        phase = angle(irow)
        phaseu = unwrap(phase[Nignore:-Nignore])
        init_phase[j] = phaseu[0]
        ampl = abs(irow)
        if max(ampl) < min_amp:
            init_phase[j] = nan
    if any(abs(init_phase) > 0.8*pi):
        print 'WARNING: phase near pi/-pi crossover. Consider changing phase_offset'
    if any(diff(init_phase) > 0.8*2*pi):
        print 'WARNING: phase pi/-pi crossover detected. Consider changing phase_offset'


    P = nan*zeros(len(rmid)) # analyzed phase of each shell
    A = nan*zeros(len(rmid)) # analyzed intensity of each shell
    Ip = nan*zeros(shape(I),dtype=complex) # matrix to hold onion-peeled, distance-normalized interferogram

    # Calculate local-horizontal projection factors
    B = nan*zeros(shape(D)) # matrix to hold cosine correction factors
    for i in range(Ntheta):
        for j in range(len(rmid)):
            # Calculate b_ij = cos(angle between ray and shell tangent)
            th = theta[i]
            r = rmid[j]
            B[i,j] = (RE+icon_alt)/(RE+r) * sin(th) # this ought to be <= 1, or there's no intersection
            #B[i,j] = 1        
        
    # Onion-peel interferogram
    Ip = linalg.solve(D,I)
    for j in range(Ntheta):
        r = rmid[j]
        irow = Ip[j,:]
        phase = angle(irow)
        ampl  = abs(irow)
        if nanmax(ampl) > min_amp:

            # average phase and then take delta. Need unwrapping for this.
            phaseu = unwrap(phase[Nignore:-Nignore])
            #phaseu = unwrap(phaseu)
            meanphase = mean(phaseu)
            dphase = meanphase - zero_phase
            opd = linspace(startpath,endpath,npixel)[Nignore:-Nignore]

            # Method B: multiply by mean(1/opd). These are different, but only slightly 
            #mean_recip_opd = mean(1/opd)
            #vj = c * dphase / (2*pi*sigma) * mean_recip_opd

            P[j] = dphase
            A[j] = ampl[Nignore:-Nignore].mean()        

    # Convert phase to velocity
    opd = linspace(startpath,endpath,npixel)
    meanopd = mean(opd[Nignore:-Nignore])
    V = c * P / (2*pi*sigma) * 1/meanopd        

    V[rmid < min_alt[emission_color]] = nan
    V[rmid > max_alt[emission_color]] = nan
        
    # Calculate local azimuth angles (i.e., at tangent point)
    satlatlonalt = array([icon_lat, icon_lon, icon_alt])

    local_az = zeros(Ntheta)
    for i in range(Ntheta):
        look_ecef = mighti_ecef_vectors[i,:] # look direction of this pixel in ECEF
        look_az, look_ze = ICON.ecef_to_azze(satlatlonalt, look_ecef) # look direction in az, ze
        tang_latlonalt = ICON.tangent_point(satlatlonalt, look_az, look_ze) # tangent point of observation
        loc_az, loc_ze = ICON.ecef_to_azze(tang_latlonalt, look_ecef) # local (tangent point) direction in az, ze
        local_az[i] = loc_az

    if show_plot:
        plot(V, rmid, '%s-' % emission_color[0])
        xlabel('Velocity [m/s]')
        ylabel('Altitude [km]')
        
    if l21fn:
        np.savez(l21fn,
                 tang_alt_start=  tang_alt_start,
                 tang_alt_end  =  tang_alt_end,
                 tang_alt      =  tang_alts,
                 tang_lat_start=  tang_lat_start,
                 tang_lat_end  =  tang_lat_end,
                 tang_lat      =  tang_lats,
                 tang_lon_start=  tang_lon_start,
                 tang_lon_end  =  tang_lon_end,
                 tang_lon      =  tang_lons,
                 emission_color=  emission_color,
                 icon_alt_start=  icon_alt_start,
                 icon_alt_end=    icon_alt_end,
                 icon_alt      =  icon_alt,
                 icon_lat_start=  icon_lat_start,
                 icon_lat_end=    icon_lat_end,
                 icon_lat      =  icon_lat,
                 icon_lon_start=  icon_lon_start,
                 icon_lon_end=    icon_lon_end,
                 icon_lon      =  icon_lon,
                 mighti_ecef_vectors_start=  mighti_ecef_vectors_start,
                 mighti_ecef_vectors_end=    mighti_ecef_vectors_end,
                 mighti_ecef_vectors     =   mighti_ecef_vectors,
                 icon_ecef_ram_vector_start= icon_ecef_ram_vector_start,
                 icon_ecef_ram_vector_end=   icon_ecef_ram_vector_end,
                 icon_ecef_ram_vector     =  icon_ecef_ram_vector,
                 icon_velocity_start=        icon_velocity_start,
                 icon_velocity_end=          icon_velocity_end,
                 icon_velocity   =           icon_velocity,
                 datetime_created=           datetime.now(),
                 source_files =              [l1uiucfn],
                 time=                       time,
                 los_wind =                  V,
                 alt_los_wind =              rmid, # TEMPORARY?
                 local_az =                  local_az,
                 ) 
        
    npzfile.close()
In [484]:
uiucl1_fn = '/home/bhardin2/MIGHTI/MIGHTI_L1_UIUC_red_known_airglow.npz'
out_fn = '/home/bhardin2/MIGHTI/MIGHTI_L21_UIUC_red_known_airglow.npz'

level21(uiucl1_fn,out_fn)
In [485]:
L21_fn = '/home/bhardin2/MIGHTI/MIGHTI_L21_UIUC_red_known_airglow.npz'
truthfn = '/home/bhardin2/MIGHTI/truth_winds_known_airglow.npz'
In [486]:
npzfile = load(truthfn)
truth_alts = npzfile['tang_alts']
truth_winds = npzfile['truth_winds']
truth_amps = npzfile['truth_amps']
npzfile.close()

subplot(1,2,1)
plot(truth_winds, truth_alts, 'k-', label='Truth')
subplot(1,2,2)
plot(truth_amps, truth_alts, 'k-', label='Truth')
xlabel('True VER')
xticks(xticks()[0][::2])

fn = L21_fn
npzfile = load(fn)
wind = npzfile['los_wind']
alt_of_wind = npzfile['alt_los_wind']
npzfile.close()
subplot(1,2,1)
plot(wind, alt_of_wind, 'r.', markersize=3, label='Recon.')
ylabel('Altitude [km]')
xlabel('LoS Velocity [m/s]')
legend(loc='best',numpoints=1,prop={'size':8},fancybox=True)
tight_layout()
In [487]:
wind_naive = wind.copy()

Modified inversion using known airglow

In [488]:
l1uiucfn = '/home/bhardin2/MIGHTI/MIGHTI_L1_UIUC_red_known_airglow.npz'
l21fn = '/home/bhardin2/MIGHTI/MIGHTI_L21_UIUC_red_known_airglow.npz'
show_plot = True
In [489]:
#def level21(l1uiucfn, l21fn, show_plot=True):

'''
This attemps to do everything in the notebook above. Set l21fn='' to not save anything
'''

Nignore = 20 # ignore first and last few samples due to wraparound ringing (Can this be fixed by zero-padding?)
# ^ This is replicated in L1 code, but that's ok. As long as this one is larger.
phase_offset = -pi # This constant is added to all phases so that there is no chance of a pi/-pi crossover.
                  # In practice, we'll have to see how the interferometer turns out before deciding on this.
c = 299792458.0 # m/s, speed of light
# Specify ranges for plotting (even though the whole image is analyzed)
min_alt = {'red': 100, 'green': 87}
max_alt = {'red': 400, 'green': 200}
min_amp = 0.0 # if amplitude of fringe is below this, don't even try to analyze it

# Zero wind/phase (TODO: how will this be done for real, and what's the best way to simulate it?)
zero_phase = 252.07801858 + phase_offset# - 0.00196902 # last term added 2015-05-18 for vertical wind study

npzfile = load(l1uiucfn)

# Extract all information from the UIUC L1 file
Ii = npzfile['Ii']
Ir = npzfile['Ir']
emission_color = str(npzfile['emission_color'])
icon_alt_end = npzfile['icon_alt_end']
icon_alt_start = npzfile['icon_alt_start']
icon_lat_end = npzfile['icon_lat_end']
icon_lat_start = npzfile['icon_lat_start']
icon_lon_end = npzfile['icon_lon_end']
icon_lon_start = npzfile['icon_lon_start']
icon_velocity_start = npzfile['icon_velocity_start']
icon_velocity_end = npzfile['icon_velocity_end']
icon_ecef_ram_vector_start = npzfile['icon_ecef_ram_vector_start']
icon_ecef_ram_vector_end = npzfile['icon_ecef_ram_vector_end']
mighti_ecef_vectors_end = npzfile['mighti_ecef_vectors_end']
mighti_ecef_vectors_start = npzfile['mighti_ecef_vectors_start']
tang_alt_end = npzfile['tang_alt_end']
tang_alt_start = npzfile['tang_alt_start']
tang_lat_end = npzfile['tang_lat_end']
tang_lat_start = npzfile['tang_lat_start']
tang_lon_end = npzfile['tang_lon_end']
tang_lon_start = npzfile['tang_lon_start']
datetime_created = npzfile['datetime_created']
source_files = npzfile['source_files']
time = npzfile['time'] # we should really have start time and end time
#print 'Reading file %s' % l1uiucfn
#print 'Created %s' % datetime_created

# Take the midpoint of start and end.
icon_alt = (icon_alt_start+icon_alt_end)/2
icon_lat = (icon_lat_start+icon_lat_end)/2
icon_lon = circular_mean(icon_lon_start, icon_lon_end)
mighti_ecef_vectors = (mighti_ecef_vectors_start + mighti_ecef_vectors_end)/2 # ny by 3
tang_alts = (tang_alt_start + tang_alt_end)/2
tang_lats = (tang_lat_start + tang_lat_end)/2
tang_lons = circular_mean(tang_lon_start, tang_lon_end)
icon_ecef_ram_vector = (icon_ecef_ram_vector_start + icon_ecef_ram_vector_end)/2
icon_velocity = (icon_velocity_start+icon_velocity_end)/2


Ntheta = shape(Ir)[0]
npixel = shape(Ir)[1]

params = MIGHTI.get_instrument_constants()
emission = MIGHTI.get_emission_constants()[emission_color]

I         = (Ir + 1j*Ii) * exp(1j*phase_offset)
theta     = MIGHTI.tanht2angle(tang_alts, icon_alt)
RE        = 6371 # TODO
startpath = params['startpath']
endpath   = params['endpath']
sigma     = 1.0/emission['lam']



# Create a column vector with projected satellite velocity. Remember, we are ignoring horizontal extent for now.
proj_icon_vel = zeros(Ntheta)
for i in range(Ntheta):
    look_ecef = mighti_ecef_vectors[i,:] # look direction of this pixel in ECEF
    proj_icon_vel[i] = icon_velocity * np.dot(icon_ecef_ram_vector, look_ecef)

# Transform velocity to phase 
# dphi = 2*pi*OPD*sigma*v/c (Eqn 1 of Englert et al 2007 Appl Opt.)
# Use mean(opd) for now, but eventually it will be actual OPD once we include horizontal extent.
opd = linspace(startpath,endpath,npixel)
icon_vel_phase = 2*pi*mean(opd[Nignore:-Nignore])*sigma*proj_icon_vel/c

# Subtract phase from the interferogram (Change this when horizontal extent is included)
corr = exp(-1j*icon_vel_phase)
for jj in range(npixel):
    I[:,jj] = I[:,jj]*corr


# Define grid. Bottom of each layer is defined by tangent height of observation.
rbottom = MIGHTI.angle2tanht(theta, icon_alt)
# Define top of each layer.
rtop = rbottom.copy()
rtop[:-1] = rbottom[1:]
rtop[-1] = rbottom[-1] + (rtop[1]-rbottom[1])
# Define midpt of each layer
rmid = (rbottom + rtop)/2

# Build observation matrix
D = zeros((Ntheta, len(rmid)))
for i in range(Ntheta):
    for j in range(len(rmid)):
        th = theta[i]
        rb = rbottom[j]
        rt = rtop[j]
        sb = cos(th)**2 - 1 + ((RE+rb)/(RE+icon_alt))**2
        st = cos(th)**2 - 1 + ((RE+rt)/(RE+icon_alt))**2
        if sb < 0: # there is no intersection of LOS with altitude rb. Set term to 0.
            # Note: this might be due to numerical rounding for tangent altitude. 
            # Do the same thing either way.
            sb = 0.
        if st < 0: # there is no intersection of LOS with altitude rt. Set term to 0.
            st = 0.
        D[i,j] = 2*(RE+icon_alt) * ( sqrt(st) - sqrt(sb) )    
In [490]:
def distance_to_shell(icon_alt, th, r, RE, intersection='first'):
    '''
    Return the distance from the satellite to the altitude r [km], along
    the ray with zenith angle th [rad]. RE is the effective earth radius [km].
    Return nan if the intersection doesn't exist.
    Specify whether to return the first or second intersection along the ray.
    '''
    sin_alpha = (RE + icon_alt)/(RE + r) * sin(th)
    if sin_alpha > 1.0:
        return np.nan
    s_first = (-sqrt(1-sin_alpha**2) - sin_alpha/tan(th))*(RE+r)
    s_second = (sqrt(1-sin_alpha**2) - sin_alpha/tan(th))*(RE+r)
    if intersection=='first':
        return s_first
    else:
        return s_second
        
    
def project_along_ray(satlatlonalt, az, ze, s):
    '''
    Starting at the satellite location, looking in a direction (az,ze [deg]), 
    trace along the ray for a distance s [km]. Return lat, lon, alt of that point
    '''

    # Convert to radians
    zer = ze*np.pi/180.
    azr = az*np.pi/180.

    # Create unit vector describing the look direction in Vertical-East-North (VEN) coordinates
    lookven = np.array([np.cos(zer), np.sin(zer)*np.sin(azr), np.sin(zer)*np.cos(azr)])
    # Convert satellite location to ecef
    satxyz = ICON.wgs84_to_ecef(satlatlonalt)
    # Convert look direction to ecef. This is a unit vector.
    lookxyz = ICON.ven_to_ecef(satlatlonalt, lookven)
    # Step along this line of sight
    xyz = satxyz + s*lookxyz
    latlonalt = ICON.ecef_to_wgs84(xyz)  

    return latlonalt
    
In [491]:
# Calculate known airglow emission rate (VER) for each entry of the matrix.
# There are some notes on this in my ICON notebook (2015-08-25)
icon_latlonalt = np.array([icon_lat, icon_lon, icon_alt])
VER = zeros((Ntheta,len(rmid)))
for i in range(Ntheta):
    for j in range(len(rmid)):
        th = theta[i]
        ze = th*180./pi
        rb = rbottom[j]
        rt = rtop[j]
        s_first_top = distance_to_shell(icon_alt, th, rt, RE, 'first')
        s_second_top = distance_to_shell(icon_alt, th, rt, RE, 'second')
        s_first_bot = distance_to_shell(icon_alt, th, rb, RE, 'first')
        s_second_bot = distance_to_shell(icon_alt, th, rb, RE, 'second')
        #print '%.2f   %.2f   %.2f   %.2f   %.2f   %.2f' % (rt, rb, s_first_top, s_second_top, \
        #                                                   s_first_bot, s_second_bot)
        if not isnan(s_first_top): # then we know this shell is intersected. Two cases:
            if not isnan(s_first_bot): # then this shell is interesected twice.
                s_first = (s_first_top + s_first_bot)/2
                s_second = (s_second_top + s_second_bot)/2
                latlonalt_first = project_along_ray(icon_latlonalt, az, ze, s_first)
                latlonalt_second = project_along_ray(icon_latlonalt, az, ze, s_second)
                pt_first = pyglow.Point(dt, latlonalt_first[0], latlonalt_first[1], latlonalt_first[2])
                ver_first = get_airglow_VER(pt_first)
                pt_second = pyglow.Point(dt, latlonalt_second[0], latlonalt_second[1], latlonalt_second[2])
                ver_second = get_airglow_VER(pt_second)
                VER[i,j] = ver_first + ver_second
                

            else: # then this shell is intersected once
                s = (s_first_top + s_second_top)/2
                latlonalt = project_along_ray(icon_latlonalt, az, ze, s)
                pt = pyglow.Point(dt, latlonalt[0], latlonalt[1], latlonalt[2])
                ver = get_airglow_VER(pt)
                VER[i,j]= ver
                
        #if not isnan(s_first_top): # then we know this shell is intersected

        #else:
         #   pass # ?
In [492]:
imshow(D,interpolation='none')
Out[492]:
<matplotlib.image.AxesImage at 0x7fdd67364a50>
In [493]:
imshow(VER,interpolation='none')
Out[493]:
<matplotlib.image.AxesImage at 0x7fdd671ff450>
In [494]:
Dprime = VER*D # element-wise multiplication
imshow(Dprime, interpolation='none')
Out[494]:
<matplotlib.image.AxesImage at 0x7fdd6732e890>
In [495]:
# Quick check to see if phase is near the pi/-pi crossover
# TODO: revamp this, including zero-wind removal
init_phase = zeros((Ntheta))
for j in range(Ntheta):
    irow = I[j,:]
    phase = angle(irow)
    phaseu = unwrap(phase[Nignore:-Nignore])
    init_phase[j] = phaseu[0]
    ampl = abs(irow)
    if max(ampl) < min_amp:
        init_phase[j] = nan
if any(abs(init_phase) > 0.8*pi):
    print 'WARNING: phase near pi/-pi crossover. Consider changing phase_offset'
if any(diff(init_phase) > 0.8*2*pi):
    print 'WARNING: phase pi/-pi crossover detected. Consider changing phase_offset'


P = nan*zeros(len(rmid)) # analyzed phase of each shell
A = nan*zeros(len(rmid)) # analyzed intensity of each shell
Ip = nan*zeros(shape(I),dtype=complex) # matrix to hold onion-peeled, distance-normalized interferogram

# Calculate local-horizontal projection factors
# This isn't used in the standard processing
B = nan*zeros(shape(D)) # matrix to hold cosine correction factors
for i in range(Ntheta):
    for j in range(len(rmid)):
        # Calculate b_ij = cos(angle between ray and shell tangent)
        th = theta[i]
        r = rmid[j]
        B[i,j] = (RE+icon_alt)/(RE+r) * sin(th) # this ought to be <= 1, or there's no intersection
        #B[i,j] = 1       
        

# Onion-peel interferogram
Ip = linalg.solve(D,I)
Ipprime = linalg.solve(Dprime,I)
In [496]:
pcolormesh(real(Ip))
Out[496]:
<matplotlib.collections.QuadMesh at 0x7fdd66f1dc90>
In [497]:
pcolormesh(real(Ipprime))
Out[497]:
<matplotlib.collections.QuadMesh at 0x7fdd66ebfa90>
In [498]:
# Analyze onion-peeled interferogram to get phase and amplitude
for j in range(Ntheta):
    r = rmid[j]
    irow = Ipprime[j,:]
    phase = angle(irow)
    ampl  = abs(irow)
    if nanmax(ampl) > min_amp:

        # average phase and then take delta. Need unwrapping for this.
        phaseu = unwrap(phase[Nignore:-Nignore])
        #phaseu = unwrap(phaseu)
        meanphase = mean(phaseu)
        dphase = meanphase - zero_phase
        opd = linspace(startpath,endpath,npixel)[Nignore:-Nignore]

        # Method B: multiply by mean(1/opd). These are different, but only slightly 
        #mean_recip_opd = mean(1/opd)
        #vj = c * dphase / (2*pi*sigma) * mean_recip_opd

        P[j] = dphase
        A[j] = ampl[Nignore:-Nignore].mean()        

# Convert phase to velocity
opd = linspace(startpath,endpath,npixel)
meanopd = mean(opd[Nignore:-Nignore])
V = c * P / (2*pi*sigma) * 1/meanopd        

V[rmid < min_alt[emission_color]] = nan
V[rmid > max_alt[emission_color]] = nan

# Calculate local azimuth angles (i.e., at tangent point)
satlatlonalt = array([icon_lat, icon_lon, icon_alt])

local_az = zeros(Ntheta)
for i in range(Ntheta):
    look_ecef = mighti_ecef_vectors[i,:] # look direction of this pixel in ECEF
    look_az, look_ze = ICON.ecef_to_azze(satlatlonalt, look_ecef) # look direction in az, ze
    tang_latlonalt = ICON.tangent_point(satlatlonalt, look_az, look_ze) # tangent point of observation
    loc_az, loc_ze = ICON.ecef_to_azze(tang_latlonalt, look_ecef) # local (tangent point) direction in az, ze
    local_az[i] = loc_az

if show_plot:
    plot(V, rmid, '%s-' % emission_color[0])
    xlabel('Velocity [m/s]')
    ylabel('Altitude [km]')

if l21fn:
    np.savez(l21fn,
             tang_alt_start=  tang_alt_start,
             tang_alt_end  =  tang_alt_end,
             tang_alt      =  tang_alts,
             tang_lat_start=  tang_lat_start,
             tang_lat_end  =  tang_lat_end,
             tang_lat      =  tang_lats,
             tang_lon_start=  tang_lon_start,
             tang_lon_end  =  tang_lon_end,
             tang_lon      =  tang_lons,
             emission_color=  emission_color,
             icon_alt_start=  icon_alt_start,
             icon_alt_end=    icon_alt_end,
             icon_alt      =  icon_alt,
             icon_lat_start=  icon_lat_start,
             icon_lat_end=    icon_lat_end,
             icon_lat      =  icon_lat,
             icon_lon_start=  icon_lon_start,
             icon_lon_end=    icon_lon_end,
             icon_lon      =  icon_lon,
             mighti_ecef_vectors_start=  mighti_ecef_vectors_start,
             mighti_ecef_vectors_end=    mighti_ecef_vectors_end,
             mighti_ecef_vectors     =   mighti_ecef_vectors,
             icon_ecef_ram_vector_start= icon_ecef_ram_vector_start,
             icon_ecef_ram_vector_end=   icon_ecef_ram_vector_end,
             icon_ecef_ram_vector     =  icon_ecef_ram_vector,
             icon_velocity_start=        icon_velocity_start,
             icon_velocity_end=          icon_velocity_end,
             icon_velocity   =           icon_velocity,
             datetime_created=           datetime.now(),
             source_files =              [l1uiucfn],
             time=                       time,
             los_wind =                  V,
             alt_los_wind =              rmid, # TEMPORARY?
             local_az =                  local_az,
             ) 

npzfile.close()
In [499]:
L21_fn = '/home/bhardin2/MIGHTI/MIGHTI_L21_UIUC_red_known_airglow.npz'
truthfn = '/home/bhardin2/MIGHTI/truth_winds_known_airglow.npz'
In [500]:
npzfile = load(truthfn)
truth_alts = npzfile['tang_alts']
truth_winds = npzfile['truth_winds']
truth_amps = npzfile['truth_amps']
npzfile.close()

subplot(1,2,1)
plot(truth_winds, truth_alts, 'k-', label='Truth')
subplot(1,2,2)
plot(truth_amps, truth_alts, 'k-', label='Truth')
xlabel('True VER')
xticks(xticks()[0][::2])

fn = L21_fn
npzfile = load(fn)
wind = npzfile['los_wind']
alt_of_wind = npzfile['alt_los_wind']
npzfile.close()
subplot(1,2,1)
plot(wind, alt_of_wind, 'r.', markersize=3, label='Recon.')
ylabel('Altitude [km]')
xlabel('LoS Velocity [m/s]')
legend(loc='best',numpoints=1,prop={'size':8},fancybox=True)
tight_layout()

Test using:

  • Spherically symmetric VER, winds, and temperatures
  • Treat airglow as known in the inversion
In [501]:
npzfile = load(truthfn)
truth_alts = npzfile['tang_alts']
truth_winds = npzfile['truth_winds']
truth_amps = npzfile['truth_amps']
npzfile.close()

plot(truth_winds, truth_alts, 'k-', label='Truth')


fn = L21_fn
npzfile = load(fn)
wind = npzfile['los_wind']
alt_of_wind = npzfile['alt_los_wind']
npzfile.close()

plot(wind_naive, alt_of_wind, 'b.-', markersize=3, label='Orig Recon.')
plot(wind, alt_of_wind, 'r.-', markersize=3, label='New Recon.')
ylabel('Altitude [km]')
xlabel('LoS Velocity [m/s]')
legend(loc='best',numpoints=1,prop={'size':6},fancybox=True)
tight_layout()

Test using non-spherically symmetric VER

But spherically symmetric winds & temps

In [502]:
# Ignore satellite motion for now. (Include velocity in phase, though).
dt = datetime(2013,9,23,2,0,0) # UT
icon_alt = 550.
icon_lat = 20.5
icon_lon = 180. # daytime
icon_velocity = 0.0 # this will likely not be implemented
az = 90. # so that we're only measuring zonal wind
ze_top = 103
ze_bot = 109
ny = 50
nk = 101
#ny = 50
#nk = 501
impose_spherical_symmetry_winds = True
impose_spherical_symmetry_temps = True
impose_spherical_symmetry_ver = False


satlla = [icon_lat, icon_lon, icon_alt]
# Let's say that ICON is going purely east. This shouldn't matter, since velocity is zero.
icon_ecef_ram_vector = ICON.ven_to_ecef(satlla, [0, 1, 0])


# LOAD MIGHTI CONSTANTS/PARAMETERS
emission_color = 'red' # 'red' or 'green'
day = True

emissions =  MIGHTI.get_emission_constants()
true_params = MIGHTI.get_instrument_constants()

true_params['exptime'] =        60.   # sec
true_params['lam'] =            emissions[emission_color]['lam']
true_params['frq'] =            emissions[emission_color]['frq']
true_params['mass'] =           emissions[emission_color]['mass']
if day: # 10% reduction in aperture size during "day", and shorter exposure time
    true_params['aperture_area'] =  true_params['aperture_area'] * 0.1
    true_params['exptime'] =        30.


ray_length = 6000./nk
print '%.2f km steps' % ray_length

tanlla_top = ICON.tangent_point(satlla, az, ze_top)
tanlla_bot = ICON.tangent_point(satlla, az, ze_bot)

zevec = linspace(ze_bot, ze_top, ny)
mighti_ecef_vectors = zeros((ny,3))
alts_along_ray = zeros((nk,ny))
lats_along_ray = zeros((nk,ny))
lons_along_ray = zeros((nk,ny))
tang_alts      = zeros(ny)
tang_lats      = zeros(ny)
tang_lons      = zeros(ny)

for i in range(ny):
    ze = zevec[i]
    # Determine tangent point
    tang_pt = ICON.tangent_point(satlla, az, ze)
    tang_alts[i] = tang_pt[2]
    tang_lats[i] = tang_pt[0]
    tang_lons[i] = tang_pt[1]
    # Determine the ECEF look direction and record it
    mighti_ecef_vectors[i,:] = ICON.azze_to_ecef(satlla, az, ze)
    # Project the line of sight and record winds, temps, and amplitudes along the ray
    xyz_proj,lla_proj = ICON.project_line_of_sight(satlla, az, ze, \
                                                   step_size = ray_length, total_distance=ray_length*nk)
    alts_along_ray[:,i] = lla_proj[2,:]
    lats_along_ray[:,i] = lla_proj[0,:]
    lons_along_ray[:,i] = lla_proj[1,:]

print '%.1f - %.1f km' % (tang_alts[-1], tang_alts[0])
59.41 km steps
372.4 - 172.4 km
In [503]:
i = 0
figure(figsize=(9,3))
subplot(1,3,1)
plot(lats_along_ray[:,i],'k')
ylabel('lats')
subplot(1,3,2)
plot(lons_along_ray[:,i],'k')
ylabel('lons')
subplot(1,3,3)
plot(alts_along_ray[:,i],'k')
ylabel('alts')
tight_layout()

Create functions that know about wind, temperature, and VER at every point

In [504]:
def get_airglow_VER(pt):
    #VER = exp(-0.5*((altk - 250)/(50))**2)
    VER = MIGHTI.get_redline_airglow(pt)
    if pt.lon > 202.:
        VER = 0.01
    return VER

def get_wind(pt):
    pt.run_hwm14()
    return pt.u,pt.v,0.0

def get_temperature(pt):
    pt.run_msis()
    return pt.Tn_msis
In [505]:
# Build the three quantities needed by forward model: VER, LoS wind, and temp
emission_along_ray_r = zeros((nk,ny))
u_along_ray = zeros((nk,ny))
v_along_ray = zeros((nk,ny))
w_along_ray = zeros((nk,ny))
winds_along_ray = zeros((nk,ny)) # LoS
temps_along_ray = zeros((nk,ny))
ven_along_ray = zeros((nk,ny,3))

for i in range(ny):
    look_xyz = mighti_ecef_vectors[i,:]
    for k in range(nk):
        latk = lats_along_ray[k,i]
        lonk = lons_along_ray[k,i]
        altk = alts_along_ray[k,i]
        
        ##### local VEN
        ven_look = ICON.ecef_to_ven([latk, lonk, altk], look_xyz)

        
        ##### winds
        if impose_spherical_symmetry_winds:
            pt = pyglow.Point(dt, icon_lat, icon_lon, altk)
        else:
            pt = pyglow.Point(dt, latk, lonk, altk)
        u,v,w = get_wind(pt)
        u_along_ray[k,i] = u
        v_along_ray[k,i] = v
        w_along_ray[k,i] = w
        winds_along_ray[k,i] = dot(ven_look,[w,u,v])
        
        
        ##### temperature
        if impose_spherical_symmetry_temps:
            pt = pyglow.Point(dt, icon_lat, icon_lon, altk)
        else:
            pt = pyglow.Point(dt, latk, lonk, altk)
        T = get_temperature(pt)
        temps_along_ray[k,i] = T
        
        
        ##### VER
        if impose_spherical_symmetry_ver:
            pt = pyglow.Point(dt, icon_lat, icon_lon, altk)
        else:
            pt = pyglow.Point(dt, latk, lonk, altk)
        VER = get_airglow_VER(pt)
        emission_along_ray_r[k,i] = VER*ray_length*1e3*1e6/1e10 # cm, km, 10^10 ph
        
       
    clear_output(wait=True)
    time_module.sleep(0.01)
    print '%03i/%03i' % (i+1,ny)
    sys.stdout.flush()
050/050

Test using:

  • Spherically symmetric winds and temps
  • Terminator-like VER distribution
  • Treat airglow as known in the inversion
In [506]:
i = 2
figure(figsize=(9,3))
subplot(1,3,1)
plot(winds_along_ray[:,i],'k')
ylabel('LoS wind [m/s]')
xlabel('Steps along ray')
subplot(1,3,2)
plot(temps_along_ray[:,i],'k')
ylabel('Temperature [K]')
xlabel('Steps along ray')
subplot(1,3,3)
plot(emission_along_ray_r[:,i],'k')
ylabel('VER [ph/cm^3/s]')
xlabel('Steps along ray')
tight_layout()

Generate interferogram

In [507]:
nx = true_params['npixelx']
ny = shape(winds_along_ray)[1] # number of rows of interferogram
nk = shape(winds_along_ray)[0] # number of steps along ray

Iraw = np.zeros((ny,nx))

for i in range(ny):
    for k in range(nk):

        amp = emission_along_ray_r[k,i]
        vel = winds_along_ray[k,i]
        temp = temps_along_ray[k,i]      

        true_params['V'] = vel
        true_params['T'] = temp
        true_params['I'] = amp

        ccdslice = MIGHTI.interferogram(true_params)

        Iraw[i,:] += ccdslice
        

Add Noise (skipped for now)

In [508]:
#Inoisy = MIGHTI.add_noise(Iraw, true_params) # make sure units are right
Inoisy = Iraw.copy()

L1 processing

In [509]:
Ir = zeros(shape(Inoisy))
Ii = zeros(shape(Inoisy))
for i in range(shape(Inoisy)[0]):
    # Filter with Hann window (in frequency) surrounding the peak
    f = Inoisy[i,:]
    F = np.fft.fft(f)
    N = len(F)
    n = arange(N)
    # Create filter as per Ken Marr's email 2013/10/29
    peaki = abs(F[5:floor(N/2)]).argmax() + 5
    width1 = 20 # width of Hann window
    width2 = 5 # width of plateau
    hann = np.hanning(width1)
    # Create full filter
    H = hstack((zeros(peaki-width1/2-(width2-1)/2), 
                hann[:width1/2], 
                ones(width2), 
                hann[width1/2:], 
                zeros(N - peaki - width1/2 - (width2-1)/2 - 1)))
    ap = hanning(N)
    f = f - f.mean()
    fap = f*ap
    F = np.fft.fft(fap)
    F2 = F * H
    f2 = np.fft.ifft(F2)
    Ir[i,:] = np.real(f2)
    Ii[i,:] = np.imag(f2)
In [510]:
# Save L1 UIUC file
# Assume no change from start to end of exposure.
fn = '/home/bhardin2/MIGHTI/MIGHTI_L1_UIUC_red_known_airglow.npz'
np.savez(fn,
         Ir=Ir, 
         Ii=Ii, 
         tang_alt_start=  tang_alts,
         tang_alt_end  =  tang_alts,
         tang_lat_start=  tang_lats,
         tang_lat_end  =  tang_lats,
         tang_lon_start=  tang_lons,
         tang_lon_end  =  tang_lons,
         emission_color=  'red',
         icon_alt_start=  icon_alt,
         icon_alt_end=    icon_alt,
         icon_lat_start=  icon_lat,
         icon_lat_end=    icon_lat,
         icon_lon_start=  icon_lon,
         icon_lon_end=    icon_lon,
         mighti_ecef_vectors_start=  mighti_ecef_vectors,
         mighti_ecef_vectors_end=    mighti_ecef_vectors,
         icon_ecef_ram_vector_start= icon_ecef_ram_vector,
         icon_ecef_ram_vector_end=   icon_ecef_ram_vector,
         icon_velocity_start=        icon_velocity,
         icon_velocity_end=          icon_velocity,
         datetime_created=           datetime.now(),
         source_files =              [],
         time =                      dt,)
print 'Saved to %s' % fn
Saved to /home/bhardin2/MIGHTI/MIGHTI_L1_UIUC_red_known_airglow.npz

Obtain truth los winds and save to separate file

In [511]:
# Create a column vector with projected satellite velocity. 
# Remember, we are ignoring horizontal extent for now.
proj_icon_vel = zeros(ny)
for i in range(ny):
    look_ecef = mighti_ecef_vectors[i,:] # look direction of this pixel in ECEF
    proj_icon_vel[i] = icon_velocity * np.dot(icon_ecef_ram_vector, look_ecef)
    
tang_point_along_ray = zeros(ny)
for i in range(ny):
    kstar = argmin(alts_along_ray[:,i])
    tang_point_along_ray[i] = kstar
    
truth_winds = zeros(ny)
truth_amps = zeros(ny)
for i in range(ny):
    kstar = tang_point_along_ray[i]
    truth_winds[i] = winds_along_ray[kstar,i] - proj_icon_vel[i]
    truth_amps[i] = emission_along_ray_r[kstar,i]
subplot(1,2,1)
plot(truth_winds,tang_alts,'k.-')
subplot(1,2,2)
plot(truth_amps,tang_alts,'k')

tfn = '/home/bhardin2/MIGHTI/truth_winds_known_airglow.npz'
np.savez(tfn,
         tang_alts=tang_alts,truth_winds=truth_winds,\
         truth_amps=truth_amps)
print 'Saved to %s' % tfn
Saved to /home/bhardin2/MIGHTI/truth_winds_known_airglow.npz

Simple inversion assuming unknown and sph sym airglow

In [512]:
from Scientific.IO.NetCDF import NetCDFFile as Dataset
from datetime import datetime, timedelta
from pyglow import pyglow
from mpl_toolkits.basemap import Basemap
import os
import glob
from scipy.interpolate import griddata
import subprocess
import MIGHTI
import ICON

def unwrap(x):
    '''
    Phase-unwrap a signal in [-pi,pi] which is increasing
    '''
    dx = diff(x)
    xnew = zeros(len(x))
    xnew[0] = x[0]
    idx = dx < 0 # 0, or -pi ???
    dx[idx] = dx[idx] + 2*pi
    xnew[1:] = xnew[0] + cumsum(dx)
    return xnew

def circular_mean(angle0,angle1):
    '''
    Find the mean angle, taking into account 0/360 crossover.
    Input and output angles in degrees.
    '''
    return 180/pi*angle((exp(1j*angle0*pi/180.) + exp(1j*angle1*pi/180.))/2)

def level21(l1uiucfn, l21fn, show_plot=True):
    '''
    This attemps to do everything in the notebook above. Set l21fn='' to not save anything
    '''

    Nignore = 20 # ignore first and last few samples due to wraparound ringing (Can this be fixed by zero-padding?)
    # ^ This is replicated in L1 code, but that's ok. As long as this one is larger.
    phase_offset = -pi # This constant is added to all phases so that there is no chance of a pi/-pi crossover.
                      # In practice, we'll have to see how the interferometer turns out before deciding on this.
    c = 299792458.0 # m/s, speed of light
    # Specify ranges for plotting (even though the whole image is analyzed)
    min_alt = {'red': 100, 'green': 87}
    max_alt = {'red': 400, 'green': 200}
    min_amp = 0.0 # if amplitude of fringe is below this, don't even try to analyze it

    # Zero wind/phase (TODO: how will this be done for real, and what's the best way to simulate it?)
    zero_phase = 252.07801858 + phase_offset# - 0.00196902 # last term added 2015-05-18 for vertical wind study

    npzfile = load(l1uiucfn)

    # Extract all information from the UIUC L1 file
    Ii = npzfile['Ii']
    Ir = npzfile['Ir']
    emission_color = str(npzfile['emission_color'])
    icon_alt_end = npzfile['icon_alt_end']
    icon_alt_start = npzfile['icon_alt_start']
    icon_lat_end = npzfile['icon_lat_end']
    icon_lat_start = npzfile['icon_lat_start']
    icon_lon_end = npzfile['icon_lon_end']
    icon_lon_start = npzfile['icon_lon_start']
    icon_velocity_start = npzfile['icon_velocity_start']
    icon_velocity_end = npzfile['icon_velocity_end']
    icon_ecef_ram_vector_start = npzfile['icon_ecef_ram_vector_start']
    icon_ecef_ram_vector_end = npzfile['icon_ecef_ram_vector_end']
    mighti_ecef_vectors_end = npzfile['mighti_ecef_vectors_end']
    mighti_ecef_vectors_start = npzfile['mighti_ecef_vectors_start']
    tang_alt_end = npzfile['tang_alt_end']
    tang_alt_start = npzfile['tang_alt_start']
    tang_lat_end = npzfile['tang_lat_end']
    tang_lat_start = npzfile['tang_lat_start']
    tang_lon_end = npzfile['tang_lon_end']
    tang_lon_start = npzfile['tang_lon_start']
    datetime_created = npzfile['datetime_created']
    source_files = npzfile['source_files']
    time = npzfile['time'] # we should really have start time and end time
    #print 'Reading file %s' % l1uiucfn
    #print 'Created %s' % datetime_created

    # Take the midpoint of start and end.
    icon_alt = (icon_alt_start+icon_alt_end)/2
    icon_lat = (icon_lat_start+icon_lat_end)/2
    icon_lon = circular_mean(icon_lon_start, icon_lon_end)
    mighti_ecef_vectors = (mighti_ecef_vectors_start + mighti_ecef_vectors_end)/2 # ny by 3
    tang_alts = (tang_alt_start + tang_alt_end)/2
    tang_lats = (tang_lat_start + tang_lat_end)/2
    tang_lons = circular_mean(tang_lon_start, tang_lon_end)
    icon_ecef_ram_vector = (icon_ecef_ram_vector_start + icon_ecef_ram_vector_end)/2
    icon_velocity = (icon_velocity_start+icon_velocity_end)/2


    Ntheta = shape(Ir)[0]
    npixel = shape(Ir)[1]

    params = MIGHTI.get_instrument_constants()
    emission = MIGHTI.get_emission_constants()[emission_color]

    I         = (Ir + 1j*Ii) * exp(1j*phase_offset)
    theta     = MIGHTI.tanht2angle(tang_alts, icon_alt)
    RE        = 6371 # TODO
    startpath = params['startpath']
    endpath   = params['endpath']
    sigma     = 1.0/emission['lam']
    
    
    
    # Create a column vector with projected satellite velocity. Remember, we are ignoring horizontal extent for now.
    proj_icon_vel = zeros(Ntheta)
    for i in range(Ntheta):
        look_ecef = mighti_ecef_vectors[i,:] # look direction of this pixel in ECEF
        proj_icon_vel[i] = icon_velocity * np.dot(icon_ecef_ram_vector, look_ecef)

    # Transform velocity to phase 
    # dphi = 2*pi*OPD*sigma*v/c (Eqn 1 of Englert et al 2007 Appl Opt.)
    # Use mean(opd) for now, but eventually it will be actual OPD once we include horizontal extent.
    opd = linspace(startpath,endpath,npixel)
    icon_vel_phase = 2*pi*mean(opd[Nignore:-Nignore])*sigma*proj_icon_vel/c

    # Subtract phase from the interferogram (Change this when horizontal extent is included)
    corr = exp(-1j*icon_vel_phase)
    for jj in range(npixel):
        I[:,jj] = I[:,jj]*corr
        
 
    # Define grid. Bottom of each layer is defined by tangent height of observation.
    rbottom = MIGHTI.angle2tanht(theta, icon_alt)
    # Define top of each layer.
    rtop = rbottom.copy()
    rtop[:-1] = rbottom[1:]
    rtop[-1] = rbottom[-1] + (rtop[1]-rbottom[1])
    # Define midpt of each layer
    rmid = (rbottom + rtop)/2

    # Build observation matrix
    D = zeros((Ntheta, len(rmid)))
    for i in range(Ntheta):
        for j in range(len(rmid)):
            th = theta[i]
            rb = rbottom[j]
            rt = rtop[j]
            sb = cos(th)**2 - 1 + ((RE+rb)/(RE+icon_alt))**2
            st = cos(th)**2 - 1 + ((RE+rt)/(RE+icon_alt))**2
            if sb < 0: # there is no intersection of LOS with altitude rb. Set term to 0.
                # Note: this might be due to numerical rounding for tangent altitude. 
                # Do the same thing either way.
                sb = 0.
            if st < 0: # there is no intersection of LOS with altitude rt. Set term to 0.
                st = 0.
            D[i,j] = 2*(RE+icon_alt) * ( sqrt(st) - sqrt(sb) )    

    # Quick check to see if phase is near the pi/-pi crossover
    # TODO: revamp this, including zero-wind removal
    init_phase = zeros((Ntheta))
    for j in range(Ntheta):
        irow = I[j,:]
        phase = angle(irow)
        phaseu = unwrap(phase[Nignore:-Nignore])
        init_phase[j] = phaseu[0]
        ampl = abs(irow)
        if max(ampl) < min_amp:
            init_phase[j] = nan
    if any(abs(init_phase) > 0.8*pi):
        print 'WARNING: phase near pi/-pi crossover. Consider changing phase_offset'
    if any(diff(init_phase) > 0.8*2*pi):
        print 'WARNING: phase pi/-pi crossover detected. Consider changing phase_offset'


    P = nan*zeros(len(rmid)) # analyzed phase of each shell
    A = nan*zeros(len(rmid)) # analyzed intensity of each shell
    Ip = nan*zeros(shape(I),dtype=complex) # matrix to hold onion-peeled, distance-normalized interferogram

    # Calculate local-horizontal projection factors
    B = nan*zeros(shape(D)) # matrix to hold cosine correction factors
    for i in range(Ntheta):
        for j in range(len(rmid)):
            # Calculate b_ij = cos(angle between ray and shell tangent)
            th = theta[i]
            r = rmid[j]
            B[i,j] = (RE+icon_alt)/(RE+r) * sin(th) # this ought to be <= 1, or there's no intersection
            #B[i,j] = 1        
        
    # Onion-peel interferogram
    Ip = linalg.solve(D,I)
    for j in range(Ntheta):
        r = rmid[j]
        irow = Ip[j,:]
        phase = angle(irow)
        ampl  = abs(irow)
        if nanmax(ampl) > min_amp:

            # average phase and then take delta. Need unwrapping for this.
            phaseu = unwrap(phase[Nignore:-Nignore])
            #phaseu = unwrap(phaseu)
            meanphase = mean(phaseu)
            dphase = meanphase - zero_phase
            opd = linspace(startpath,endpath,npixel)[Nignore:-Nignore]

            # Method B: multiply by mean(1/opd). These are different, but only slightly 
            #mean_recip_opd = mean(1/opd)
            #vj = c * dphase / (2*pi*sigma) * mean_recip_opd

            P[j] = dphase
            A[j] = ampl[Nignore:-Nignore].mean()        

    # Convert phase to velocity
    opd = linspace(startpath,endpath,npixel)
    meanopd = mean(opd[Nignore:-Nignore])
    V = c * P / (2*pi*sigma) * 1/meanopd        

    V[rmid < min_alt[emission_color]] = nan
    V[rmid > max_alt[emission_color]] = nan
        
    # Calculate local azimuth angles (i.e., at tangent point)
    satlatlonalt = array([icon_lat, icon_lon, icon_alt])

    local_az = zeros(Ntheta)
    for i in range(Ntheta):
        look_ecef = mighti_ecef_vectors[i,:] # look direction of this pixel in ECEF
        look_az, look_ze = ICON.ecef_to_azze(satlatlonalt, look_ecef) # look direction in az, ze
        tang_latlonalt = ICON.tangent_point(satlatlonalt, look_az, look_ze) # tangent point of observation
        loc_az, loc_ze = ICON.ecef_to_azze(tang_latlonalt, look_ecef) # local (tangent point) direction in az, ze
        local_az[i] = loc_az

    if show_plot:
        plot(V, rmid, '%s-' % emission_color[0])
        xlabel('Velocity [m/s]')
        ylabel('Altitude [km]')
        
    if l21fn:
        np.savez(l21fn,
                 tang_alt_start=  tang_alt_start,
                 tang_alt_end  =  tang_alt_end,
                 tang_alt      =  tang_alts,
                 tang_lat_start=  tang_lat_start,
                 tang_lat_end  =  tang_lat_end,
                 tang_lat      =  tang_lats,
                 tang_lon_start=  tang_lon_start,
                 tang_lon_end  =  tang_lon_end,
                 tang_lon      =  tang_lons,
                 emission_color=  emission_color,
                 icon_alt_start=  icon_alt_start,
                 icon_alt_end=    icon_alt_end,
                 icon_alt      =  icon_alt,
                 icon_lat_start=  icon_lat_start,
                 icon_lat_end=    icon_lat_end,
                 icon_lat      =  icon_lat,
                 icon_lon_start=  icon_lon_start,
                 icon_lon_end=    icon_lon_end,
                 icon_lon      =  icon_lon,
                 mighti_ecef_vectors_start=  mighti_ecef_vectors_start,
                 mighti_ecef_vectors_end=    mighti_ecef_vectors_end,
                 mighti_ecef_vectors     =   mighti_ecef_vectors,
                 icon_ecef_ram_vector_start= icon_ecef_ram_vector_start,
                 icon_ecef_ram_vector_end=   icon_ecef_ram_vector_end,
                 icon_ecef_ram_vector     =  icon_ecef_ram_vector,
                 icon_velocity_start=        icon_velocity_start,
                 icon_velocity_end=          icon_velocity_end,
                 icon_velocity   =           icon_velocity,
                 datetime_created=           datetime.now(),
                 source_files =              [l1uiucfn],
                 time=                       time,
                 los_wind =                  V,
                 alt_los_wind =              rmid, # TEMPORARY?
                 local_az =                  local_az,
                 ) 
        
    npzfile.close()
In [513]:
uiucl1_fn = '/home/bhardin2/MIGHTI/MIGHTI_L1_UIUC_red_known_airglow.npz'
out_fn = '/home/bhardin2/MIGHTI/MIGHTI_L21_UIUC_red_known_airglow.npz'

level21(uiucl1_fn,out_fn)
In [514]:
L21_fn = '/home/bhardin2/MIGHTI/MIGHTI_L21_UIUC_red_known_airglow.npz'
truthfn = '/home/bhardin2/MIGHTI/truth_winds_known_airglow.npz'
In [515]:
npzfile = load(truthfn)
truth_alts = npzfile['tang_alts']
truth_winds = npzfile['truth_winds']
truth_amps = npzfile['truth_amps']
npzfile.close()

subplot(1,2,1)
plot(truth_winds, truth_alts, 'k-', label='Truth')
subplot(1,2,2)
plot(truth_amps, truth_alts, 'k-', label='Truth')
xlabel('True VER')
xticks(xticks()[0][::2])

fn = L21_fn
npzfile = load(fn)
wind = npzfile['los_wind']
alt_of_wind = npzfile['alt_los_wind']
npzfile.close()
subplot(1,2,1)
plot(wind, alt_of_wind, 'r.', markersize=3, label='Recon.')
ylabel('Altitude [km]')
xlabel('LoS Velocity [m/s]')
legend(loc='best',numpoints=1,prop={'size':8},fancybox=True)
tight_layout()
In [516]:
wind_naive = wind.copy()

Modified inversion using known airglow

In [517]:
l1uiucfn = '/home/bhardin2/MIGHTI/MIGHTI_L1_UIUC_red_known_airglow.npz'
l21fn = '/home/bhardin2/MIGHTI/MIGHTI_L21_UIUC_red_known_airglow.npz'
show_plot = True
In [518]:
#def level21(l1uiucfn, l21fn, show_plot=True):

'''
This attemps to do everything in the notebook above. Set l21fn='' to not save anything
'''

Nignore = 20 # ignore first and last few samples due to wraparound ringing (Can this be fixed by zero-padding?)
# ^ This is replicated in L1 code, but that's ok. As long as this one is larger.
phase_offset = -pi # This constant is added to all phases so that there is no chance of a pi/-pi crossover.
                  # In practice, we'll have to see how the interferometer turns out before deciding on this.
c = 299792458.0 # m/s, speed of light
# Specify ranges for plotting (even though the whole image is analyzed)
min_alt = {'red': 100, 'green': 87}
max_alt = {'red': 400, 'green': 200}
min_amp = 0.0 # if amplitude of fringe is below this, don't even try to analyze it

# Zero wind/phase (TODO: how will this be done for real, and what's the best way to simulate it?)
zero_phase = 252.07801858 + phase_offset# - 0.00196902 # last term added 2015-05-18 for vertical wind study

npzfile = load(l1uiucfn)

# Extract all information from the UIUC L1 file
Ii = npzfile['Ii']
Ir = npzfile['Ir']
emission_color = str(npzfile['emission_color'])
icon_alt_end = npzfile['icon_alt_end']
icon_alt_start = npzfile['icon_alt_start']
icon_lat_end = npzfile['icon_lat_end']
icon_lat_start = npzfile['icon_lat_start']
icon_lon_end = npzfile['icon_lon_end']
icon_lon_start = npzfile['icon_lon_start']
icon_velocity_start = npzfile['icon_velocity_start']
icon_velocity_end = npzfile['icon_velocity_end']
icon_ecef_ram_vector_start = npzfile['icon_ecef_ram_vector_start']
icon_ecef_ram_vector_end = npzfile['icon_ecef_ram_vector_end']
mighti_ecef_vectors_end = npzfile['mighti_ecef_vectors_end']
mighti_ecef_vectors_start = npzfile['mighti_ecef_vectors_start']
tang_alt_end = npzfile['tang_alt_end']
tang_alt_start = npzfile['tang_alt_start']
tang_lat_end = npzfile['tang_lat_end']
tang_lat_start = npzfile['tang_lat_start']
tang_lon_end = npzfile['tang_lon_end']
tang_lon_start = npzfile['tang_lon_start']
datetime_created = npzfile['datetime_created']
source_files = npzfile['source_files']
time = npzfile['time'] # we should really have start time and end time
#print 'Reading file %s' % l1uiucfn
#print 'Created %s' % datetime_created

# Take the midpoint of start and end.
icon_alt = (icon_alt_start+icon_alt_end)/2
icon_lat = (icon_lat_start+icon_lat_end)/2
icon_lon = circular_mean(icon_lon_start, icon_lon_end)
mighti_ecef_vectors = (mighti_ecef_vectors_start + mighti_ecef_vectors_end)/2 # ny by 3
tang_alts = (tang_alt_start + tang_alt_end)/2
tang_lats = (tang_lat_start + tang_lat_end)/2
tang_lons = circular_mean(tang_lon_start, tang_lon_end)
icon_ecef_ram_vector = (icon_ecef_ram_vector_start + icon_ecef_ram_vector_end)/2
icon_velocity = (icon_velocity_start+icon_velocity_end)/2


Ntheta = shape(Ir)[0]
npixel = shape(Ir)[1]

params = MIGHTI.get_instrument_constants()
emission = MIGHTI.get_emission_constants()[emission_color]

I         = (Ir + 1j*Ii) * exp(1j*phase_offset)
theta     = MIGHTI.tanht2angle(tang_alts, icon_alt)
RE        = 6371 # TODO
startpath = params['startpath']
endpath   = params['endpath']
sigma     = 1.0/emission['lam']



# Create a column vector with projected satellite velocity. Remember, we are ignoring horizontal extent for now.
proj_icon_vel = zeros(Ntheta)
for i in range(Ntheta):
    look_ecef = mighti_ecef_vectors[i,:] # look direction of this pixel in ECEF
    proj_icon_vel[i] = icon_velocity * np.dot(icon_ecef_ram_vector, look_ecef)

# Transform velocity to phase 
# dphi = 2*pi*OPD*sigma*v/c (Eqn 1 of Englert et al 2007 Appl Opt.)
# Use mean(opd) for now, but eventually it will be actual OPD once we include horizontal extent.
opd = linspace(startpath,endpath,npixel)
icon_vel_phase = 2*pi*mean(opd[Nignore:-Nignore])*sigma*proj_icon_vel/c

# Subtract phase from the interferogram (Change this when horizontal extent is included)
corr = exp(-1j*icon_vel_phase)
for jj in range(npixel):
    I[:,jj] = I[:,jj]*corr


# Define grid. Bottom of each layer is defined by tangent height of observation.
rbottom = MIGHTI.angle2tanht(theta, icon_alt)
# Define top of each layer.
rtop = rbottom.copy()
rtop[:-1] = rbottom[1:]
rtop[-1] = rbottom[-1] + (rtop[1]-rbottom[1])
# Define midpt of each layer
rmid = (rbottom + rtop)/2

# Build observation matrix
D = zeros((Ntheta, len(rmid)))
for i in range(Ntheta):
    for j in range(len(rmid)):
        th = theta[i]
        rb = rbottom[j]
        rt = rtop[j]
        sb = cos(th)**2 - 1 + ((RE+rb)/(RE+icon_alt))**2
        st = cos(th)**2 - 1 + ((RE+rt)/(RE+icon_alt))**2
        if sb < 0: # there is no intersection of LOS with altitude rb. Set term to 0.
            # Note: this might be due to numerical rounding for tangent altitude. 
            # Do the same thing either way.
            sb = 0.
        if st < 0: # there is no intersection of LOS with altitude rt. Set term to 0.
            st = 0.
        D[i,j] = 2*(RE+icon_alt) * ( sqrt(st) - sqrt(sb) )    
In [519]:
# Calculate known airglow emission rate (VER) for each entry of the matrix.
# There are some notes on this in my ICON notebook (2015-08-25)
icon_latlonalt = np.array([icon_lat, icon_lon, icon_alt])
VER = zeros((Ntheta,len(rmid)))
for i in range(Ntheta):
    for j in range(len(rmid)):
        th = theta[i]
        ze = th*180./pi
        rb = rbottom[j]
        rt = rtop[j]
        s_first_top = distance_to_shell(icon_alt, th, rt, RE, 'first')
        s_second_top = distance_to_shell(icon_alt, th, rt, RE, 'second')
        s_first_bot = distance_to_shell(icon_alt, th, rb, RE, 'first')
        s_second_bot = distance_to_shell(icon_alt, th, rb, RE, 'second')
        #print '%.2f   %.2f   %.2f   %.2f   %.2f   %.2f' % (rt, rb, s_first_top, s_second_top, \
        #                                                   s_first_bot, s_second_bot)
        if not isnan(s_first_top): # then we know this shell is intersected. Two cases:
            if not isnan(s_first_bot): # then this shell is interesected twice.
                s_first = (s_first_top + s_first_bot)/2
                s_second = (s_second_top + s_second_bot)/2
                latlonalt_first = project_along_ray(icon_latlonalt, az, ze, s_first)
                latlonalt_second = project_along_ray(icon_latlonalt, az, ze, s_second)
                pt_first = pyglow.Point(dt, latlonalt_first[0], latlonalt_first[1], latlonalt_first[2])
                ver_first = get_airglow_VER(pt_first)
                pt_second = pyglow.Point(dt, latlonalt_second[0], latlonalt_second[1], latlonalt_second[2])
                ver_second = get_airglow_VER(pt_second)
                VER[i,j] = ver_first + ver_second
                

            else: # then this shell is intersected once
                s = (s_first_top + s_second_top)/2
                latlonalt = project_along_ray(icon_latlonalt, az, ze, s)
                pt = pyglow.Point(dt, latlonalt[0], latlonalt[1], latlonalt[2])
                ver = get_airglow_VER(pt)
                VER[i,j]= ver
                
        #if not isnan(s_first_top): # then we know this shell is intersected

        #else:
         #   pass # ?
In [520]:
imshow(VER,interpolation='none')
Out[520]:
<matplotlib.image.AxesImage at 0x7fdd66e12550>
In [521]:
Dprime = VER*D # element-wise multiplication
imshow(Dprime, interpolation='none')
Out[521]:
<matplotlib.image.AxesImage at 0x7fdd66c5ba90>
In [522]:
cond(Dprime)
Out[522]:
679.93663146008032
In [523]:
# Quick check to see if phase is near the pi/-pi crossover
# TODO: revamp this, including zero-wind removal
init_phase = zeros((Ntheta))
for j in range(Ntheta):
    irow = I[j,:]
    phase = angle(irow)
    phaseu = unwrap(phase[Nignore:-Nignore])
    init_phase[j] = phaseu[0]
    ampl = abs(irow)
    if max(ampl) < min_amp:
        init_phase[j] = nan
if any(abs(init_phase) > 0.8*pi):
    print 'WARNING: phase near pi/-pi crossover. Consider changing phase_offset'
if any(diff(init_phase) > 0.8*2*pi):
    print 'WARNING: phase pi/-pi crossover detected. Consider changing phase_offset'


P = nan*zeros(len(rmid)) # analyzed phase of each shell
A = nan*zeros(len(rmid)) # analyzed intensity of each shell
Ip = nan*zeros(shape(I),dtype=complex) # matrix to hold onion-peeled, distance-normalized interferogram

# Calculate local-horizontal projection factors
# This isn't used in the standard processing
B = nan*zeros(shape(D)) # matrix to hold cosine correction factors
for i in range(Ntheta):
    for j in range(len(rmid)):
        # Calculate b_ij = cos(angle between ray and shell tangent)
        th = theta[i]
        r = rmid[j]
        B[i,j] = (RE+icon_alt)/(RE+r) * sin(th) # this ought to be <= 1, or there's no intersection
        #B[i,j] = 1       
        

# Onion-peel interferogram
Ip = linalg.solve(D,I)
Ipprime = linalg.solve(Dprime,I)
In [524]:
# Analyze onion-peeled interferogram to get phase and amplitude
for j in range(Ntheta):
    r = rmid[j]
    irow = Ipprime[j,:]
    phase = angle(irow)
    ampl  = abs(irow)
    if nanmax(ampl) > min_amp:

        # average phase and then take delta. Need unwrapping for this.
        phaseu = unwrap(phase[Nignore:-Nignore])
        #phaseu = unwrap(phaseu)
        meanphase = mean(phaseu)
        dphase = meanphase - zero_phase
        opd = linspace(startpath,endpath,npixel)[Nignore:-Nignore]

        # Method B: multiply by mean(1/opd). These are different, but only slightly 
        #mean_recip_opd = mean(1/opd)
        #vj = c * dphase / (2*pi*sigma) * mean_recip_opd

        P[j] = dphase
        A[j] = ampl[Nignore:-Nignore].mean()        

# Convert phase to velocity
opd = linspace(startpath,endpath,npixel)
meanopd = mean(opd[Nignore:-Nignore])
V = c * P / (2*pi*sigma) * 1/meanopd        

V[rmid < min_alt[emission_color]] = nan
V[rmid > max_alt[emission_color]] = nan

# Calculate local azimuth angles (i.e., at tangent point)
satlatlonalt = array([icon_lat, icon_lon, icon_alt])

local_az = zeros(Ntheta)
for i in range(Ntheta):
    look_ecef = mighti_ecef_vectors[i,:] # look direction of this pixel in ECEF
    look_az, look_ze = ICON.ecef_to_azze(satlatlonalt, look_ecef) # look direction in az, ze
    tang_latlonalt = ICON.tangent_point(satlatlonalt, look_az, look_ze) # tangent point of observation
    loc_az, loc_ze = ICON.ecef_to_azze(tang_latlonalt, look_ecef) # local (tangent point) direction in az, ze
    local_az[i] = loc_az

if show_plot:
    plot(V, rmid, '%s-' % emission_color[0])
    xlabel('Velocity [m/s]')
    ylabel('Altitude [km]')

if l21fn:
    np.savez(l21fn,
             tang_alt_start=  tang_alt_start,
             tang_alt_end  =  tang_alt_end,
             tang_alt      =  tang_alts,
             tang_lat_start=  tang_lat_start,
             tang_lat_end  =  tang_lat_end,
             tang_lat      =  tang_lats,
             tang_lon_start=  tang_lon_start,
             tang_lon_end  =  tang_lon_end,
             tang_lon      =  tang_lons,
             emission_color=  emission_color,
             icon_alt_start=  icon_alt_start,
             icon_alt_end=    icon_alt_end,
             icon_alt      =  icon_alt,
             icon_lat_start=  icon_lat_start,
             icon_lat_end=    icon_lat_end,
             icon_lat      =  icon_lat,
             icon_lon_start=  icon_lon_start,
             icon_lon_end=    icon_lon_end,
             icon_lon      =  icon_lon,
             mighti_ecef_vectors_start=  mighti_ecef_vectors_start,
             mighti_ecef_vectors_end=    mighti_ecef_vectors_end,
             mighti_ecef_vectors     =   mighti_ecef_vectors,
             icon_ecef_ram_vector_start= icon_ecef_ram_vector_start,
             icon_ecef_ram_vector_end=   icon_ecef_ram_vector_end,
             icon_ecef_ram_vector     =  icon_ecef_ram_vector,
             icon_velocity_start=        icon_velocity_start,
             icon_velocity_end=          icon_velocity_end,
             icon_velocity   =           icon_velocity,
             datetime_created=           datetime.now(),
             source_files =              [l1uiucfn],
             time=                       time,
             los_wind =                  V,
             alt_los_wind =              rmid, # TEMPORARY?
             local_az =                  local_az,
             ) 

npzfile.close()
In [525]:
L21_fn = '/home/bhardin2/MIGHTI/MIGHTI_L21_UIUC_red_known_airglow.npz'
truthfn = '/home/bhardin2/MIGHTI/truth_winds_known_airglow.npz'
In [526]:
npzfile = load(truthfn)
truth_alts = npzfile['tang_alts']
truth_winds = npzfile['truth_winds']
truth_amps = npzfile['truth_amps']
npzfile.close()

subplot(1,2,1)
plot(truth_winds, truth_alts, 'k-', label='Truth')
subplot(1,2,2)
plot(truth_amps, truth_alts, 'k-', label='Truth')
xlabel('True VER')
xticks(xticks()[0][::2])

fn = L21_fn
npzfile = load(fn)
wind = npzfile['los_wind']
alt_of_wind = npzfile['alt_los_wind']
npzfile.close()
subplot(1,2,1)
plot(wind, alt_of_wind, 'r.', markersize=3, label='Recon.')
ylabel('Altitude [km]')
xlabel('LoS Velocity [m/s]')
legend(loc='best',numpoints=1,prop={'size':8},fancybox=True)
tight_layout()
In [533]:
npzfile = load(truthfn)
truth_alts = npzfile['tang_alts']
truth_winds = npzfile['truth_winds']
truth_amps = npzfile['truth_amps']
npzfile.close()

plot(truth_winds, truth_alts, 'k-', label='Truth')


fn = L21_fn
npzfile = load(fn)
wind = npzfile['los_wind']
alt_of_wind = npzfile['alt_los_wind']
npzfile.close()

plot(wind_naive, alt_of_wind, 'b.-', markersize=3, label='Orig Recon.')
plot(wind, alt_of_wind, 'r.-', markersize=3, label='New Recon.')
ylabel('Altitude [km]')
xlabel('LoS Velocity [m/s]')
legend(loc='best',numpoints=1,prop={'size':6},fancybox=True)
tight_layout()

#xlim((-80,-65))

Major problems under certain conditions (not shown).

In [532]:
import subprocess
import shutil
nb_name = 'MIGHTI_Known_VER_20150826.ipynb' # There's probably a way to get this programmatically
cmd = ['ipython', 'nbconvert', '--to', 'slides', nb_name, \
       '--reveal-prefix', '"http://cdn.jsdelivr.net/reveal.js/2.6.2"',\
      '--template','output_toggle']
# The --template command points to a custom template which opens and closes the input code cells with a click
subprocess.call(cmd)
html_name = '%s.slides.html' % (nb_name.split('.')[0])
shutil.copy(html_name, '/home/bhardin2/public_html/slideshows/')
In [ ]:
 
In [ ]: