nod2fits.py 11.2 KB
#!/usr/bin/env python

import sys, os
import struct
import scipy
import pyfits
import pylab as plt
import numpy as np
import datetime as DT
from fortranfile import FortranFile

RAD = np.pi/180.0
GRD = 180.0/np.pi

def nint(x):
    if x > 0: return int(x+0.5)
    else: return int(x-0.5)

def get_title(x, OS):
    title = ""
    for i in range(7, 17, 1):
        if x[i] != 0.0:
           if OS == "other":
              if i == 8:
                 title += np.array(x[i]).tostring()[::-1].strip()
              else:
                 title += np.array(x[i]).tostring()[::-1].strip() + "#"
           elif OS == "unix":
              title += np.array(x[i]).tostring().strip() + "#"
        else:
           title += " "
    title = title.replace("#", "", 1)
    title = title.replace("##", " ").replace("#", " ")
    source = title.split()[0].strip().rstrip()
    try:
       scannum = np.array(x[10]).tostring()[::-1][:4]
    except:
       try:
          scannum = title.split()[2].strip().rstrip()
       except:
          scannum = 0
    try: 
        s = int(scannum)
    except:
        scannum = None
    return title, source, scannum

def read_rawdata(filename, OS):
    if OS == "other":
       f = FortranFile(filename, endian=">")
    elif OS == "unix":
       f = FortranFile(filename, endian="<")
    go = True
    records = []
    while go:
          try: records.append(list(f.readReals('d')))
          except: go = False
    f.close()
    title, source, scannum = get_title(records[0], OS)
    if scannum != None:
       outfile = "nod3_"+scannum.rstrip()+"_"+source.rstrip()+".fits"
    else:
       outfile = "nod3_"+source+".fits"
    return outfile, records

def mapin(records, mapno, os='other'):
    x = records[0]
    if os in ('other', 'unix'):
       title = get_title(x, os)
       start = nint(x[1]) - 1
       cols = nint(x[2])
       rows = nint(x[3])
       length = 0
       n = 0
       dat = []
       while length < start+cols*rows:
             length += len(records[n])
             dat += records[n]
             n += 1
       for i in range(n):
           records.pop(0)
       header = dat[:start]
       data = np.array(dat[start:start+cols*rows]).reshape((rows, cols))
    return tuple(header), data


def rotmat(l, b, a):

    cosl = np.cos(l*RAD)
    sinl = np.sin(l*RAD)
    cosb = np.cos(b*RAD)
    sinb = np.sin(b*RAD)
    cosa = np.cos(a*RAD)
    sina = np.sin(a*RAD)

    rotmat = []
    rotmat.append(cosb*cosl)
    rotmat.append(-sina*sinb*cosl - cosa*sinl)
    rotmat.append(-cosa*sinb*cosl + sina*sinl)
    rotmat.append(cosb*sinl)
    rotmat.append(-sina*sinb*sinl + cosa*cosl)
    rotmat.append(-cosa*sinb*sinl - sina*cosl)
    rotmat.append(sinb)
    rotmat.append(sina*cosb)
    rotmat.append(cosa*cosb)

    return np.reshape(rotmat, (3,3))


def create_fits(filename, header, data, keys, OS, mapno=1, sidpar=False):

    m50 = [[ 0.9999257079524, -0.0111789381378, -0.0048590038154], \
           [ 0.0111789381264,  0.9999375133500, -0.0000271625947], \
           [ 0.0048590038415, -0.0000271579263,  0.9999881946024]]

    hoff = {}
    hoff[0] = [0.0, 0.0]
    hoff[4850] = [242.5, -242.5]

    dmax = np.nanmin(data)
    dmin = np.nanmax(data)
    data = np.where(data == header[5], np.nan, data)
    
    title, source, scannum = get_title(header, OS)

    hc = nint(header[30])
    if hc < 0:
       epoch = 2000.0
    else:
       epoch = 1950
    if str(hc)[-1] == '0':
       c1 = 'GLON-'
       c2 = 'GLAT-'
       sig = -1
    elif str(hc)[-1] in ('1', '3'):
       c1 = 'RA---'
       c2 = 'DEC--'
       sig = -1
    elif str(hc)[-1] == '2':
       c1 = 'ALON-'
       c2 = 'ALAT-'
       sig = 1

    try: scdir = nint(header[52])
    except: scdir = None
    if scdir == 0:
       SCANDIR = c1.replace("-", "")
       ssig = -1
    elif scdir == 1:
       SCANDIR = c2.replace("-", "")
       ssig = 1
    elif scdir == 2:
       SCANDIR = "ALON"
       ssig = 1
    elif scdir == 3:
       SCANDIR = "ALAT"
       ssig = 1
    else:
       SCANDIR = None
  
    icode = str("%4.4i" % header[30])
    if icode[-2] == '0':
       proj = 'CAR'
    elif icode[-2] == '1':
       proj = 'DES'
    elif icode[-2] == '2':
       proj = 'DES'

    freq = int(header[26])
    if freq not in hoff.keys(): freq = 0
    xoff = hoff[freq][0] / 3600
    #rxpix = (header[2] + 1) / 2.0
    #rypix = (header[3] + 1) / 2.0
    rxpix = 1+(header[28] + xoff)/header[44]
    rypix = 1-header[29]/header[45]

    if proj == 'DES':
       mat = np.array(header[31:40])
       mat = mat.reshape(3,3)
       vec = np.dot(mat, [1.0, 0.0, 0.0])
       lon = np.arctan2(vec[1], vec[0]) * GRD
       lat = np.arctan2(vec[2], np.sqrt(vec[1]**2 + vec[0]**2)) * GRD
       if lon < 0.0: lon += 360.0 
       rot = np.dot(np.transpose(rotmat(lon, lat, 0.0)), mat)
       rotx = np.arctan2(rot[2][1], rot[2][2]) * GRD
       roty = rotx
    else:
       lon = header[28] + sig*rxpix*header[44]
       lat = header[29] + rypix*header[45]
       rotx = 0.0
       roty = 0.0
    try: sdir = int(header[52])
    except: sdir = -1 
    cols = header[2]
    rows = header[3]
    if SCANDIR == "ALON":
       data = np.fliplr(data)
    hdu = pyfits.PrimaryHDU(data)
    hdu.header.set('BSCALE', 1.0, 'data scaling factor')
    hdu.header.set('BZERO', 0.0, 'data zero value')
    #hdu.header.set('BLANK', header[5], 'data blank value (dummy)')
    hdu.header.set('DATAMIN', dmin, 'minimum pixel value of image set')
    hdu.header.set('DATAMAX', dmax, 'maximum pixel value of image set')
    hdu.header.set('OBSLAT', header[27], 'latitude of observation')
    if abs(header[27] - 50.525) < 0.025:
       hdu.header.set('TELESCOP', 'Effelsberg', 'telescope used')
    hdu.header.set('OBJECT', source, 'source name')
    if scannum != None:
       hdu.header.set('SCANNUM', scannum, 'SCAN NUMBER')
    hdu.header.set('EPOCH', epoch, 'epoch of base system')
    hdu.header.set('CTYPE1', c1+proj, 'projection type')
    hdu.header.set('CRVAL1', lon, 'logitude of reference pixel')
    hdu.header.set('CDELT1', sig*header[44], 'logitude of reference pixel')
    hdu.header.set('CRPIX1', rxpix, 'reference pixel')
    hdu.header.set('CROTA1', rotx, 'rotation angle of base system')
    hdu.header.set('CTYPE2', c2+proj, 'projection type')
    hdu.header.set('CRVAL2', lat, 'latitude of reference pixel')
    hdu.header.set('CDELT2', header[45], 'latitude of reference pixel')
    hdu.header.set('CRPIX2', rypix, 'reference pixel')
    hdu.header.set('CROTA2', roty, 'rotation angle of base system')
    hdu.header.set('CTYPE3', 'FREQ', 'projection type')
    if SCANDIR != None:
       hdu.header.set('SCANDIR', SCANDIR, 'Scanning direction')
       hdu.header.set('SCANXVEL', header[53], 'Tracking rate along line')
    isodate = convert_partial_year(header[25])
    isodate = str(isodate).replace(" ", "T")
    hdu.header.set('DATE_OBS', isodate, 'Epoch of Observation')
    hdu.header.set('CRVAL3', header[26]*1e6, 'frequency [Hz]')
    try: hdu.header.set('CDELT3', header[54]*1e6, 'band width [Hz]')
    except: hdu.header.set('CDELT3', 10.0, 'band width [Hz]')
    hdu.header.set('CRPIX3', 1, 'frequency step')
    if sdir == 0: hdu.header.set('SCANDIR', 'ULON', 'Scanning direction')
    if sdir == 1: hdu.header.set('SCANDIR', 'ULAT', 'Scanning direction')
    hdu.header.set('BMAJ', header[42], 'Current longitude beamwidth')
    hdu.header.set('BMIN', header[43], 'Current latitude beamwidth')
    #hdu.header.set('BANDWID', header[54], 'Bandwidth in MHz')
    if title.upper().find("CH1") > 0:
       hdu.header.set('MAPTYPE', "I1")
    elif title.upper().find("CH2") > 0:
       hdu.header.set('MAPTYPE', "I2")
    elif title.upper().find("CH3") > 0:
       hdu.header.set('MAPTYPE', "U")
    elif title.upper().find("CH4") > 0:
       hdu.header.set('MAPTYPE', "Q")
    else:
       hdu.header.set('MAPTYPE', "I")
    if mapno < 5:
       hdu.header.set('RXHORN', 0)
       hdu.header.set('PATLONG', hoff[freq][0])
    else:
       hdu.header.set('RXHORN', 1)
       hdu.header.set('PATLONG', hoff[freq][1])
    hdu.header.set('EXTNAME', 'MAP-'+hdu.header['MAPTYPE']+'-H'+str(hdu.header['RXHORN']))
    for key in keys:
        val = str(keys[key])
        if val.isdigit(): Val = int(val)
        elif type(val) == type(" "): Val = val.split(",")
        elif not val.isalpha(): Val = float(val)
        if type(Val) == type([]):
           val = Val[(mapno-1)%len(Val)].strip().upper()
        else:
           val = Val.upper()
        if val.isdigit():
           val = int(val)
        elif val.isalnum():
           val = val
        else:
           val = float(val)
        hdu.header.set(key.upper(), val)

    #for i in range(len(header)):
    #    print i, header[i]
    #print hdu.header

    #hdu.writeto(filename, clobber=True)
    if sidpar:
       sidmap, parmap = getsidpar(header)
       hdu.header['OBJECT'] = 'Parallactic Angle Map'
       hdu.header.set('EXTNAME', "PARMAP")
       pyfits.append(filename, parmap, hdu.header)
       hdu.header['OBJECT'] = 'Sidereal Time Map'
       hdu.header.set('EXTNAME', "SIDMAP")
       pyfits.append(filename, sidmap, hdu.header)
       return

    if mapno == 1:
       if os.path.exists(filename): os.remove(filename) 
       pyfits.writeto(filename, hdu.data, hdu.header)
    else:
       pyfits.append(filename, hdu.data, hdu.header)

def getsidpar(header):
    rows, cols = int(header[3]), int(header[2])
    sidmap = np.zeros((rows, cols))
    parmap = np.zeros((rows, cols))
    off = len(header) - 4*rows
    for j in range(rows):
        i1 = off + j*4
        i3 = off + j*4 + 2
        ds = (header[i1+1] - header[i1])/float(cols-1)
        dp = (header[i3+1] - header[i3])/float(cols-1)
        for i in range(cols):
            if ds > 0:
               sidmap[j][i] = header[i1+1] - i*ds
            else:
               sidmap[j][i] = header[i1] + i*ds
            if dp > 0:
               parmap[j][i] = header[i3+1] - i*dp
            else:
               parmap[j][i] = header[i3] + i*dp
    return sidmap, parmap

def convert_partial_year(atime):
    """
    Convert atime (a float) to DT.datetime
    This is the inverse of dt2t.
    assert dt2t(t2dt(atime)) == atime
    """
    year = int(atime)
    remainder = atime - year
    boy = DT.datetime(year, 1, 1)
    eoy = DT.datetime(year + 1, 1, 1)
    seconds = remainder * (eoy - boy).total_seconds()
    return boy + DT.timedelta(seconds=seconds)


def main():

    if len(sys.argv) < 2:
       print "usage: ", sys.argv[0], " filename [os]"
       sys.exit(0)

    filename = sys.argv[1]
    if len(sys.argv) >= 3:
       if sys.argv[2].find("=") < 0:
          OS = sys.argv[2]
       else:
          OS = 'other'
    else:
       OS = 'other'
    keys = {}
    keys['dx'] = -1
    for arg in sys.argv:
        ind = arg.find("=")
        if ind > 0:
           keys[arg[:ind].upper()] = arg[ind+1:].upper()
        elif arg[:3] == "+dx":
           keys['dx'] = 1

    mapno = 1
    outfile, raw = read_rawdata(filename, OS)
    #try:
    if 1:
       while len(raw) > 0:
           header, data = mapin(raw, mapno, os=OS)
           create_fits(outfile, header, data, keys, OS, mapno=mapno)
           mapno += 1
       if len(header) - 4*header[3] > 40: 
          create_fits(outfile, header, data, keys, OS, mapno=mapno, sidpar=True)
       print mapno-1, "maps converted to FITS"
    #except:
    else:
       if mapno == 1:
          print "unknown data type"
          sys.exit(1)

if __name__ == '__main__':
    main()