Source code for pyspeckit.spectrum.readers.read_class

"""
------------------------
GILDAS CLASS file reader
------------------------

Read a CLASS file into an :class:`pyspeckit.spectrum.ObsBlock`
"""
from __future__ import print_function
from six.moves import xrange
from six import iteritems
import six
import astropy.io.fits as pyfits
import numpy
import numpy as np
from numpy import pi
from astropy import log
# from astropy.time import Time
from astropy import units as u
import pyspeckit
import sys
import re
try:
    from astropy.utils.console import ProgressBar
except ImportError:
    ProgressBar = lambda x: None
    ProgressBar.update = lambda x: None
import struct

import time

# 'range' is needed as a keyword
irange = range



[docs]def ensure_bytes(string): """ Ensure a given string is in byte form """ if six.PY3: return bytes(string, 'utf-8') else: return str(string)
""" Specification: http://iram.fr/IRAMFR/GILDAS/doc/html/class-html/node58.html """ filetype_dict = {'1A ':'Multiple_IEEE', '1 ':'Multiple_Vax', '1B ':'Multiple_EEEI', '2A ':'v2', '2 ':'v2', '2B ':'v2', '9A ':'Single_IEEE', '9 ':'Single_Vax', '9B ':'Single_EEEI'} for key in list(filetype_dict.keys()): filetype_dict[ensure_bytes(key)] = filetype_dict[key] fileversion_dict = {'1A ':'v1', '2A ':'v2', '9A ':'v1', # untested } for key in list(fileversion_dict.keys()): fileversion_dict[ensure_bytes(key)] = fileversion_dict[key] record_lengths = {'1A': 512, '2A': 1024*4} header_id_numbers = {0: 'USER CODE', -1: 'COMMENT', -2: 'GENERAL', -3: 'POSITION', -4: 'SPECTRO', -5: 'BASELINE', -6: 'HISTORY', -7: 'UNKNOWN-APEX', # -8: 'SWITCH', -9: 'GAUSSFIT', # "private"; see class-interfaces-private.f90 -10: 'DRIFT', -11: 'BEAMSWITCH', # "private"; see class-interfaces-private.f90 -12: 'SHELLFIT', # "private"; see class-interfaces-private.f90 -13: 'NH3FIT', # "private"; see class-interfaces-private.f90 -14: 'CALIBRATION', -18: 'ABSFIT', # "private"; see class-interfaces-private.f90 } header_id_lengths = {-2: 9, # may really be 10? -3: 17, -4: 17, -5: None, # variable length -6: 3, # variable length -14: 25, } # from packages/classic/lib/classic_mod.f90 filedescv2_nw1=14 """ GENERAL integer(kind=obsnum_length) :: num ! [ ] Observation number integer(kind=4) :: ver ! [ ] Version number integer(kind=4) :: teles(3) ! [ ] Telescope name integer(kind=4) :: dobs ! [MJD-60549] Date of observation integer(kind=4) :: dred ! [MJD-60549] Date of reduction integer(kind=4) :: typec ! [ code] Type of coordinates integer(kind=4) :: kind ! [ code] Type of data integer(kind=4) :: qual ! [ code] Quality of data integer(kind=4) :: subscan ! [ ] Subscan number integer(kind=obsnum_length) :: scan ! [ ] Scan number ! Written in the entry real(kind=8) :: ut ! 1-2 [ rad] UT of observation real(kind=8) :: st ! 3-4 [ rad] LST of observation real(kind=4) :: az ! 5 [ rad] Azimuth real(kind=4) :: el ! 6 [ rad] Elevation real(kind=4) :: tau ! 7 [neper] Opacity real(kind=4) :: tsys ! 8 [ K] System temperature real(kind=4) :: time ! 9 [ s] Integration time ! Not in this section in file integer(kind=4) :: xunit ! [ code] X unit (if X coordinates section is present) ! NOT in data --- character(len=12) :: cdobs ! [string] Duplicate of dobs character(len=12) :: cdred ! [string] Duplicate of dred """ keys_lengths = { 'unknown': [ ('NUM' ,1,'int32'), # Observation number ('VER' ,1,'int32'), # Version number ('TELES' ,3,'|S12') , # Telescope name ('DOBS' ,1,'int32'), # Date of observation ('DRED' ,1,'int32'), # Date of reduction ('TYPEC' ,1,'int32'), # Type of coordinates ('KIND' ,1,'int32'), # Type of data ('QUAL' ,1,'int32'), # Quality of data ('SCAN' ,1,'int32'), # Scan number ('SUBSCAN' ,1,'int32'), # Subscan number ], 'COMMENT': [ # -1 ('LTEXT',1,'int32'), # integer(kind=4) :: ltext ! Length of comment ('CTEXT',1024//4,'|S1024'), # character ctext*1024 ! Comment string ], 'GENERAL': [ # -2 ('UT' ,2,'float64'), # rad UT of observation ('ST' ,2,'float64'), # rad LST of observation ('AZ' ,1,'float32'), # rad Azimuth ('EL' ,1,'float32'), # rad Elevation ('TAU' ,1,'float32'), # neper Opacity ('TSYS' ,1,'float32'), # K System temperature ('TIME' ,1,'float32'), # s Integration time # XUNIT should not be there? #( 'XUNIT' ,1,'int32'), # code X unit (if xcoord_sec is present) ] , 'POSITION': [ # -3 ('SOURC',3,'|S12') , # [ ] Source name ('EPOCH',1,'float32'), # [ ] Epoch of coordinates ('LAM' ,2,'float64'), #[rad] Lambda ('BET' ,2,'float64'), #[rad] Beta ('LAMOF',1,'float32'), # [rad] Offset in Lambda ('BETOF',1,'float32'), # [rad] Offset in Beta ('PROJ' ,1,'int32') , # [rad] Projection system ('SL0P' ,1,'float64'), # lambda of descriptive system # MAY NOT EXIST IN OLD CLASS ('SB0P' ,1,'float64'), # beta of descriptive system # MAY NOT EXIST IN OLD CLASS ('SK0P' ,1,'float64'), # angle of descriptive system # MAY NOT EXIST IN OLD CLASS ], 'SPECTRO': [ # -4 #('align' ,1,'int32'), # [ ] Alignment padding ('LINE' ,3,'|S12'), # [ ] Line name ('RESTF' ,2,'float64'), # [ MHz] Rest frequency ('NCHAN' ,1,'int32'), # [ ] Number of channels ('RCHAN' ,1,'float32'), # [ ] Reference channels ('FRES' ,1,'float32'), # [ MHz] Frequency resolution ('FOFF' ,1,'float32'), # [ MHz] Frequency offset ('VRES' ,1,'float32'), # [km/s] Velocity resolution ('VOFF' ,1,'float32'), # [km/s] Velocity at reference channel ('BAD' ,1,'float32'), # [ ] Blanking value #('ALIGN_1',1,'int32'), # [ ] Alignment padding ('IMAGE' ,2,'float64'), # [ MHz] Image frequency #('ALIGN_2',1,'int32'), # [ ] Alignment padding ('VTYPE' ,1,'int32'), # [code] Type of velocity ('DOPPLER',2,'float64'), # [ ] Doppler factor = -V/c (CLASS convention) ], 'CALIBRATION': [ # -14 ('ALIGN',1,'int32'), # BUFFER (it's a zero - it is not declared in the docs!!!!) ('BEEFF',1,'float32'), # [ ] Beam efficiency ('FOEFF',1,'float32'), # [ ] Forward efficiency ('GAINI',1,'float32'), # [ ] Image/Signal gain ratio ('H2OMM',1,'float32'), # [ mm] Water vapor content ('PAMB',1,'float32'), # [ hPa] Ambient pressure ('TAMB',1,'float32'), # [ K] Ambient temperature ('TATMS',1,'float32'), # [ K] Atmosphere temp. in signal band ('TCHOP',1,'float32'), # [ K] Chopper temperature ('TCOLD',1,'float32'), # [ K] Cold load temperature ('TAUS',1,'float32'), # [neper] Opacity in signal band ('TAUI',1,'float32'), # [neper] Opacity in image band ('TATMI',1,'float32'), # [ K] Atmosphere temp. in image band ('TREC',1,'float32'), # [ K] Receiver temperature ('CMODE',1,'int32'), # [ code] Calibration mode ('ATFAC',1,'float32'), # [ ] Applied calibration factor ('ALTI',1,'float32'), # [ m] Site elevation ('COUNT',3,'3float32'), # [count] Power of Atm., Chopp., Cold ('LCALOF',1,'float32'), # [ rad] Longitude offset for sky measurement ('BCALOF',1,'float32'), # [ rad] Latitude offset for sky measurement ('GEOLONG',1,'float64'), # [ rad] Geographic longitude of observatory # MAY NOT EXIST IN OLD CLASS ('GEOLAT',1,'float64'), # [ rad] Geographic latitude of observatory # MAY NOT EXIST IN OLD CLASS ], 'BASELINE':[ ('DEG',1,'int32'), #! [ ] Degree of last baseline ('SIGFI',1,'float32'), #! [Int. unit] Sigma ('AIRE',1,'float32'), #! [Int. unit] Area under windows ('NWIND',1,'int32'), #! [ ] Number of line windows # WARNING: These should probably have 'n', the second digit, = NWIND # The docs are really unclear about this, they say "W1(MWIND)" ('W1MWIND',1,'float32'), #! [km/s] Lower limits of windows ('W2MWIND',1,'float32'), #! [km/s] Upper limits of windows ('SINUS',3,'float32'), #![] Sinus baseline results ], 'DRIFT':[ # 16? ('FREQ',1,'float64') , #! [ MHz] Rest frequency real(kind=8) :: ('WIDTH',1,'float32'), #! [ MHz] Bandwidth real(kind=4) :: ('NPOIN',1,'int32') , #! [ ] Number of data points integer(kind=4) :: ('RPOIN',1,'float32'), #! [ ] Reference point real(kind=4) :: ('TREF',1,'float32') , #! [ ?] Time at reference real(kind=4) :: ('AREF',1,'float32') , #! [ rad] Angular offset at ref. real(kind=4) :: ('APOS',1,'float32') , #! [ rad] Position angle of drift real(kind=4) :: ('TRES',1,'float32') , #! [ ?] Time resolution real(kind=4) :: ('ARES',1,'float32') , #! [ rad] Angular resolution real(kind=4) :: ('BAD',1,'float32') , #! [ ] Blanking value real(kind=4) :: ('CTYPE',1,'int32') , #! [code] Type of offsets integer(kind=4) :: ('CIMAG',1,'float64'), #! [ MHz] Image frequency real(kind=8) :: ('COLLA',1,'float32'), #! [ ?] Collimation error Az real(kind=4) :: ('COLLE',1,'float32'), #! [ ?] Collimation error El real(kind=4) :: ], } def _read_bytes(f, n): '''Read the next `n` bytes (from idlsave)''' return f.read(n) """ Warning: UNCLEAR what endianness should be! Numpy seemed to get it right, and I think numpy assumes NATIVE endianness """ def _read_byte(f): '''Read a single byte (from idlsave)''' return numpy.uint8(struct.unpack('=B', f.read(4)[:1])[0]) def _read_int16(f): '''Read a signed 16-bit integer (from idlsave)''' return numpy.int16(struct.unpack('=h', f.read(4)[2:4])[0]) def _read_int32(f): '''Read a signed 32-bit integer (from idlsave)''' return numpy.int32(struct.unpack('=i', f.read(4))[0]) def _read_int64(f): '''Read a signed 64-bit integer ''' return numpy.int64(struct.unpack('=q', f.read(8))[0]) def _read_float32(f): '''Read a 32-bit float (from idlsave)''' return numpy.float32(struct.unpack('=f', f.read(4))[0]) def _align_32(f): '''Align to the next 32-bit position in a file (from idlsave)''' pos = f.tell() if pos % 4 != 0: f.seek(pos + 4 - pos % 4) return def _read_word(f,length): if length > 0: chars = _read_bytes(f, length) _align_32(f) else: chars = None return chars def _read_int(f): return struct.unpack('i',f.read(4))
[docs]def is_ascii(s): """Check if there are non-ascii characters in Unicode string Parameters ---------- s : str The string to be checked Returns ------- is_ascii : bool Returns True if all characters in the string are ascii. False otherwise. """ return len(s) == len(s.decode('ascii').encode('utf-8'))
def is_all_null(s): return all(x=='\x00' for x in s) or all(x==b'\x00' for x in s) """ from clic_file.f90: v1, v2 integer(kind=4) :: bloc ! 1 : observation address [records] integer(kind=8) :: bloc ! 1- 2: observation address [records] integer(kind=4) :: bloc ! 1 : block read from index integer(kind=4) :: num ! 2 : observation number integer(kind=4) :: word ! 3 : address offset [4-bytes] integer(kind=4) :: num ! 2 : number read integer(kind=4) :: ver ! 3 : observation version integer(kind=4) :: ver ! 4 : observation version integer(kind=4) :: ver ! 3 : version read from index integer(kind=4) :: sourc(3) ! 4- 6: source name integer(kind=8) :: num ! 5- 6: observation number character(len=12) :: csour ! 4- 6: source read from index integer(kind=4) :: line(3) ! 7- 9: line name integer(kind=4) :: sourc(3) ! 7- 9: source name character(len=12) :: cline ! 7- 9: line read from index integer(kind=4) :: teles(3) ! 10-12: telescope name integer(kind=4) :: line(3) ! 10-12: line name character(len=12) :: ctele ! 10-12: telescope read from index integer(kind=4) :: dobs ! 13 : observation date [class_date] integer(kind=4) :: teles(3) ! 13-15: telescope name integer(kind=4) :: dobs ! 13 : date obs. read from index integer(kind=4) :: dred ! 14 : reduction date [class_date] integer(kind=4) :: dobs ! 16 : observation date [class_date] integer(kind=4) :: dred ! 14 : date red. read from index real(kind=4) :: off1 ! 15 : lambda offset [radian] integer(kind=4) :: dred ! 17 : reduction date [class_date] real(kind=4) :: off1 ! 15 : read offset 1 real(kind=4) :: off2 ! 16 : beta offset [radian] real(kind=4) :: off1 ! 18 : lambda offset [radian] real(kind=4) :: off2 ! 16 : read offset 2 integer(kind=4) :: typec ! 17 : coordinates types real(kind=4) :: off2 ! 19 : beta offset [radian] integer(kind=4) :: type ! 17 : type of read offsets integer(kind=4) :: kind ! 18 : data kind integer(kind=4) :: typec ! 20 : coordinates types integer(kind=4) :: kind ! 18 : type of observation integer(kind=4) :: qual ! 19 : data quality integer(kind=4) :: kind ! 21 : data kind integer(kind=4) :: qual ! 19 : Quality read from index integer(kind=4) :: scan ! 20 : scan number integer(kind=4) :: qual ! 22 : data quality integer(kind=4) :: scan ! 20 : Scan number read from index integer(kind=4) :: proc ! 21 : procedure type integer(kind=4) :: scan ! 23 : scan number real(kind=4) :: posa ! 21 : Position angle integer(kind=4) :: itype ! 22 : observation type integer(kind=4) :: proc ! 24 : procedure type integer(kind=4) :: subscan ! 22 : Subscan number real(kind=4) :: houra ! 23 : hour angle [radian] integer(kind=4) :: itype ! 25 : observation type integer(kind=4) :: pad(10) ! 23-32: Pad to 32 words integer(kind=4) :: project ! 24 : project name real(kind=4) :: houra ! 26 : hour angle [radian] integer(kind=4) :: pad1 ! 25 : unused word integer(kind=4) :: project(2) ! 27 : project name integer(kind=4) :: bpc ! 26 : baseline bandpass cal status integer(kind=4) :: bpc ! 29 : baseline bandpass cal status integer(kind=4) :: ic ! 27 : instrumental cal status integer(kind=4) :: ic ! 30 : instrumental cal status integer(kind=4) :: recei ! 28 : receiver number integer(kind=4) :: recei ! 31 : receiver number real(kind=4) :: ut ! 29 : UT [s] real(kind=4) :: ut ! 32 : UT [s] integer(kind=4) :: pad2(3) ! 30-32: padding to 32 4-bytes word equivalently integer(kind=obsnum_length) :: num ! [ ] Observation number integer(kind=4) :: ver ! [ ] Version number integer(kind=4) :: teles(3) ! [ ] Telescope name integer(kind=4) :: dobs ! [MJD-60549] Date of observation integer(kind=4) :: dred ! [MJD-60549] Date of reduction integer(kind=4) :: typec ! [ code] Type of coordinates integer(kind=4) :: kind ! [ code] Type of data integer(kind=4) :: qual ! [ code] Quality of data integer(kind=4) :: subscan ! [ ] Subscan number integer(kind=obsnum_length) :: scan ! [ ] Scan number """ """ index.f90: call conv%read%i8(data(1), indl%bloc, 1) ! bloc call conv%read%i4(data(3), indl%word, 1) ! word call conv%read%i8(data(4), indl%num, 1) ! num call conv%read%i4(data(6), indl%ver, 1) ! ver call conv%read%cc(data(7), indl%csour, 3) ! csour call conv%read%cc(data(10),indl%cline, 3) ! cline call conv%read%cc(data(13),indl%ctele, 3) ! ctele call conv%read%i4(data(16),indl%dobs, 1) ! dobs call conv%read%i4(data(17),indl%dred, 1) ! dred call conv%read%r4(data(18),indl%off1, 1) ! off1 call conv%read%r4(data(19),indl%off2, 1) ! off2 call conv%read%i4(data(20),indl%type, 1) ! type call conv%read%i4(data(21),indl%kind, 1) ! kind call conv%read%i4(data(22),indl%qual, 1) ! qual call conv%read%r4(data(23),indl%posa, 1) ! posa call conv%read%i8(data(24),indl%scan, 1) ! scan call conv%read%i4(data(26),indl%subscan,1) ! subscan if (isv3) then call conv%read%r8(data(27),indl%ut, 1) ! ut else """ def _read_indices(f, file_description): #if file_description['version'] in (1,2): # extension_positions = (file_description['aex']-1)*file_description['reclen']*4 # all_indices = {extension: # [_read_index(f, # filetype=file_description['version'], # entry=ii, # #position=position, # ) # for ii in range(file_description['lex1'])] # for extension,position in enumerate(extension_positions) # if position > 0 # } #elif file_description['version'] == 1: extension_positions = ((file_description['aex'].astype('int64')-1) *file_description['reclen']*4) all_indices = [_read_index(f, filetype=file_description['version'], # 1-indexed files entry_number=ii+1, file_description=file_description, ) for ii in range(file_description['xnext']-1)] #else: # raise ValueError("Invalid file version {0}".format(file_description['version'])) return all_indices def _find_index(entry_number, file_description, return_position=False): if file_description['gex'] == 10: kex=(entry_number-1)//file_description['lex1'] + 1 else: # exponential growth: #kex = gi8_dicho(file_description['nex'], file_description['lexn'], entry_number) - 1 kex = len([xx for xx in file_description['lexn'] if xx<entry_number]) ken = entry_number - file_description['lexn'][kex-1] #! Find ken (relative entry number in the extension, starts from 1) #ken = entry_num - file%desc%lexn(kex-1) kb = ((ken-1)*file_description['lind'])//file_description['reclen'] #kb = ((ken-1)*file%desc%lind)/file%desc%reclen ! In the extension, the # ! relative record position (as an offset, starts from 0) where the # ! Entry Index starts. NB: there can be a non-integer number of Entry # ! Indexes per record # Subtract 1: 'aex' is 1-indexed kbl = (file_description['aex'][kex-1]+kb)-1 # kbl = file%desc%aex(kex)+kb ! The absolute record number where the Entry Index goes k = ((ken-1)*file_description['lind']) % file_description['reclen'] #k = mod((ken-1)*file%desc%lind,file%desc%reclen)+1 ! = in the record, the # ! first word of the Entry Index of the entry number 'entry_num' if return_position: return (kbl*file_description['reclen']+k)*4 else: return kbl,k def _read_index(f, filetype='v1', DEBUG=False, clic=False, position=None, entry_number=None, file_description=None): if position is not None: f.seek(position) if entry_number is not None: indpos = _find_index(entry_number, file_description, return_position=True) f.seek(indpos) x0 = f.tell() if filetype in ('1A ','v1', 1): log.debug('Index filetype 1A') index = { "XBLOC":_read_int32(f), "XNUM":_read_int32(f), "XVER":_read_int32(f), "XSOURC":_read_word(f,12), "XLINE":_read_word(f,12), "XTEL":_read_word(f,12), "XDOBS":_read_int32(f), "XDRED":_read_int32(f), "XOFF1":_read_float32(f),# first offset (real, radians) "XOFF2":_read_float32(f),# second offset (real, radians) "XTYPE":_read_int32(f),# coordinate system ('EQ'', 'GA', 'HO') "XKIND":_read_int32(f),# Kind of observation (0: spectral, 1: continuum, ) "XQUAL":_read_int32(f),# Quality (0-9) "XSCAN":_read_int32(f),# Scan number } index['BLOC'] = index['XBLOC'] # v2 compatibility index['WORD'] = 1 # v2 compatibility index['SOURC'] = index['CSOUR'] = index['XSOURC'] index['DOBS'] = index['CDOBS'] = index['XDOBS'] index['CTELE'] = index['XTEL'] index['LINE'] = index['XLINE'] index['OFF1'] = index['XOFF1'] index['OFF2'] = index['XOFF2'] index['QUAL'] = index['XQUAL'] index['SCAN'] = index['XSCAN'] index['KIND'] = index['XKIND'] if clic: # use header set up in clic nextchunk = { "XPROC":_read_int32(f),# "procedure type" "XITYPE":_read_int32(f),# "XHOURANG":_read_float32(f),# "XPROJNAME":_read_int32(f),# "XPAD1":_read_int32(f), "XBPC" :_read_int32(f), "XIC" :_read_int32(f), "XRECEI" :_read_int32(f), "XUT":_read_float32(f), "XPAD2":numpy.fromfile(f,count=3,dtype='int32') # BLANK is NOT ALLOWED!!! It is a special KW } else: nextchunk = {"XPOSA":_read_float32(f), "XSUBSCAN":_read_int32(f), 'XPAD2': numpy.fromfile(f,count=10,dtype='int32'), } nextchunk['SUBSCAN'] = nextchunk['XSUBSCAN'] nextchunk['POSA'] = nextchunk['XPOSA'] index.update(nextchunk) if (f.tell() - x0 != 128): missed_bits = (f.tell()-x0) X = f.read(128-missed_bits) if DEBUG: print("read_index missed %i bits: %s" % (128-missed_bits,X)) #raise IndexError("read_index did not successfully read 128 bytes at %i. Read %i bytes." % (x0,f.tell()-x0)) if any(not is_ascii(index[x]) for x in ('XSOURC','XLINE','XTEL')): raise ValueError("Invalid index read from {0}.".format(x0)) elif filetype in ('2A ','v2', 2): log.debug('Index filetype 2A') index = { "BLOC" : _read_int64(f) , #(data(1), 1) ! bloc "WORD" : _read_int32(f) , #(data(3), 1) ! word "NUM" : _read_int64(f) , #(data(4), 1) ! num "VER" : _read_int32(f) , #(data(6), 1) ! ver "CSOUR" : _read_word(f,12), #(data(7), 3) ! csour "CLINE" : _read_word(f,12), #(data(10), 3) ! cline "CTELE" : _read_word(f,12), #(data(13), 3) ! ctele "DOBS" : _read_int32(f) , #(data(16), 1) ! dobs "DRED" : _read_int32(f) , #(data(17), 1) ! dred "OFF1" : _read_float32(f), #(data(18), 1) ! off1 "OFF2" : _read_float32(f), #(data(19), 1) ! off2 "TYPE" : _read_int32(f) , #(data(20), 1) ! type "KIND" : _read_int32(f) , #(data(21), 1) ! kind "QUAL" : _read_int32(f) , #(data(22), 1) ! qual "POSA" : _read_float32(f), #(data(23), 1) ! posa "SCAN" : _read_int64(f) , #(data(24), 1) ! scan "SUBSCAN": _read_int32(f) , #(data(26), 1) ! subscan } #last24bits = f.read(24) #log.debug("Read 24 bits: '{0}'".format(last24bits)) if any((is_all_null(index[x]) or not is_ascii(index[x])) for x in ('CSOUR','CLINE','CTELE')): raise ValueError("Invalid index read from {0}.".format(x0)) index['SOURC'] = index['XSOURC'] = index['CSOUR'] index['LINE'] = index['XLINE'] = index['CLINE'] index['XKIND'] = index['KIND'] try: index['DOBS'] = index['XDOBS'] = index['CDOBS'] except KeyError: index['CDOBS'] = index['XDOBS'] = index['DOBS'] else: raise NotImplementedError("Filetype {0} not implemented.".format(filetype)) # from kernel/lib/gsys/date.f90: gag_julda index['MJD'] = index['DOBS'] + 60549 class_dobs = index['DOBS'] index['DOBS'] = ((class_dobs + 365*2025)/365.2425 + 1) # SLOW #index['DATEOBS'] = Time(index['DOBS'], format='jyear') #index['DATEOBSS'] = index['DATEOBS'].iso log.debug("Indexing finished at {0}".format(f.tell())) return index def _read_header(f, type=0, position=None): """ Read a header entry from a CLASS file (helper function) """ if position is not None: f.seek(position) if type in keys_lengths: hdrsec = [(x[0],numpy.fromfile(f,count=1,dtype=x[2])[0]) for x in keys_lengths[type]] return dict(hdrsec) else: return {} raise ValueError("Unrecognized type {0}".format(type)) def _read_first_record(f): f.seek(0) filetype = f.read(4) if fileversion_dict[filetype] == 'v1': return _read_first_record_v1(f) elif fileversion_dict[filetype] == 'v2': return _read_first_record_v2(f) else: raise ValueError("Unrecognized filetype {0}".format(filetype)) def _read_first_record_v1(f, record_length_words=128): r""" Position & Parameter & Fortran Kind & Purpose \\ \hline 1 & {\tt code} & Character*4 & File code \\ 2 & {\tt next} & Integer*4 & Next free record \\ 3 & {\tt lex} & Integer*4 & Length of first extension (number of entries) \\ 4 & {\tt nex} & Integer*4 & Number of extensions \\ 5 & {\tt xnext} & Integer*4 & Next available entry number \\ 6:2*{\tt reclen} & {\tt ex(:)} & Integer*4 & Array of extension addresses from classic_mod.f90: integer(kind=4) :: code ! 1 File code integer(kind=4) :: next ! 2 Next free record integer(kind=4) :: lex ! 3 Extension length (number of entries) integer(kind=4) :: nex ! 4 Number of extensions integer(kind=4) :: xnext ! 5 Next available entry number integer(kind=4) :: aex(mex_v1) ! 6:256 Extension addresses from old (<dec2013) class, file.f90: read(ilun,rec=1,err=11,iostat=ier) ibx%code,ibx%next, & & ibx%ilex,ibx%imex,ibx%xnext also uses filedesc_v1tov2 from classic/lib/file.f90 """ # OLD NOTES # hdr = header # hdr.update(obshead) # re-overwrite things # hdr.update({'OBSNUM':obsnum,'RECNUM':spcount}) # hdr.update({'RA':hdr['LAM']/pi*180,'DEC':hdr['BET']/pi*180}) # hdr.update({'RAoff':hdr['LAMOF']/pi*180,'DECoff':hdr['BETOF']/pi*180}) # hdr.update({'OBJECT':hdr['SOURC'].strip()}) # hdr.update({'BUNIT':'Tastar'}) # hdr.update({'EXPOSURE':hdr['TIME']}) f.seek(0) file_description = { 'code': f.read(4), 'next': _read_int32(f), 'lex': _read_int32(f), 'nex': _read_int32(f), 'xnext': _read_int32(f), 'gex': 10., 'vind': 1, # classic_vind_v1 packages/classic/lib/classic_mod.f90 'version': 1, 'nextrec': 3, 'nextword': 1, 'lind': 32, #classic_lind_v1 packages/classic/lib/classic_mod.f90 'kind': 'unknown', 'flags': 0, } file_description['reclen'] = record_length_words # should be 128w = 512 bytes ex = np.fromfile(f, count=(record_length_words*2-5), dtype='int32') file_description['ex'] = ex[ex!=0] file_description['nextrec'] = file_description['next'] # this can't be... file_description['lex1'] = file_description['lex'] # number of entries file_description['lexn'] = (np.arange(file_description['nex']+1) * file_description['lex1']) file_description['nentries'] = np.sum(file_description['lexn']) file_description['aex'] = file_description['ex'][:file_description['nex']] #file_description['version'] = fileversion_dict[file_description['code']] assert f.tell() == 1024 # Something is not quite right with the 'ex' parsing #assert len(file_description['ex']) == file_description['nex'] return file_description def _read_first_record_v2(f): r""" packages/classic/lib/file.f90 Position & Parameter & Fortran Kind & Purpose & Unit \\ \hline 1 & {\tt code} & Character*4 & File code & - \\ 2 & {\tt reclen} & Integer*4 & Record length & words \\ 3 & {\tt kind} & Integer*4 & File kind & - \\ 4 & {\tt vind} & Integer*4 & Index version & - \\ 5 & {\tt lind} & Integer*4 & Index length & words \\ 6 & {\tt flags} & Integer*4 & Bit flags. \#1: single or multiple, & - \\ & & & \#2-32: provision (0-filled) & \\ \hline 7:8 & {\tt xnext} & Integer*8 & Next available entry number & - \\ 9:10 & {\tt nextrec} & Integer*8 & Next record which contains free space & record \\ 11 & {\tt nextword} & Integer*4 & Next free word in this record & word \\ \hline 12 & {\tt lex1} & Integer*4 & Length of first extension index & entries \\ 13 & {\tt nex} & Integer*4 & Number of extensions & - \\ 14 & {\tt gex} & Integer*4 & Extension growth rule & - \\ 15:{\tt reclen} & {\tt aex(:)} & Integer*8 & Array of extension addresses & record """ f.seek(0) file_description = { 'code': f.read(4), 'reclen': _read_int32(f), 'kind': _read_int32(f), 'vind': _read_int32(f), 'lind': _read_int32(f), 'flags': _read_int32(f), 'xnext': _read_int64(f), 'nextrec': _read_int64(f), 'nextword': _read_int32(f), 'lex1': _read_int32(f), 'nex': _read_int32(f), 'gex': _read_int32(f), } file_description['lexn'] = [0] if file_description['gex'] == 10: for ii in range(1, file_description['nex']+1): file_description['lexn'].append(file_description['lexn'][-1]+file_description['lex1']) else: #! Exponential growth. Only growth with mantissa 2.0 is supported for ii in range(1, file_description['nex']): # I don't know what the fortran does here!!! # ahh, maybe 2_8 means int(2, dtype='int64') nent = int(file_description['lex1'] * 2**(ii-1)) #nent = int(file%desc%lex1,kind=8) * 2_8**(iex-1) file_description['lexn'].append(file_description['lexn'][-1]+nent) #file%desc%lexn(iex) = file%desc%lexn(iex-1) + nent file_description['nentries'] = np.sum(file_description['lexn']) record_length_words = file_description['reclen'] aex = numpy.fromfile(f, count=(record_length_words-15)//2, dtype='int64') file_description['aex'] = aex[aex!=0] assert len(file_description['aex']) == file_description['nex'] file_description['version'] = 2 return file_description
[docs]def gi8_dicho(ninp,lexn,xval,ceil=True): """ ! @ public ! Find ival such as ! X(ival-1) < xval <= X(ival) (ceiling mode) ! or ! X(ival) <= xval < X(ival+1) (floor mode) ! for input data ordered. Use a dichotomic search for that. call gi8_dicho(nex,file%desc%lexn,entry_num,.true.,kex,error) """ #integer(kind=size_length), intent(in) :: np ! Number of input points #integer(kind=8), intent(in) :: x(np) ! Input ordered Values #integer(kind=8), intent(in) :: xval ! The value we search for #logical, intent(in) :: ceil ! Ceiling or floor mode? #integer(kind=size_length), intent(out) :: ival ! Position in the array #logical, intent(inout) :: error ! Logical error flag iinf = 1 isup = ninp #! Ceiling mode while isup > (iinf+1): imid = int(np.floor((isup + iinf)/2.)) if (lexn[imid-1] < xval): iinf = imid else: isup = imid ival = isup return ival
def _read_obshead(f, file_description, position=None, verbose=False): if file_description['version'] == 1: return _read_obshead_v1(f, position=position, verbose=verbose) if file_description['version'] == 2: return _read_obshead_v2(f, position=position) else: raise ValueError("Invalid file version {0}.". format(file_description['version'])) def _read_obshead_v2(f, position=None): """ ! Version 2 (public) integer(kind=4), parameter :: entrydescv2_nw1=11 ! Number of words, in 1st part integer(kind=4), parameter :: entrydescv2_nw2=5 ! Number of words for 1 section in 2nd part type classic_entrydesc_t sequence integer(kind=4) :: code ! 1 : code observation icode integer(kind=4) :: version ! 2 : observation version integer(kind=4) :: nsec ! 3 : number of sections integer(kind=4) :: pad1 ! - : memory padding (not in data) integer(kind=8) :: nword ! 4- 5: number of words integer(kind=8) :: adata ! 6- 7: data address integer(kind=8) :: ldata ! 8- 9: data length integer(kind=8) :: xnum ! 10-11: entry number ! Out of the 'sequence' block: integer(kind=4) :: msec ! Not in data: maximum number of sections the ! Observation Index can hold integer(kind=4) :: pad2 ! Memory padding for 8 bytes alignment integer(kind=4) :: seciden(classic_maxsec) ! Section Numbers (on disk: 1 to ed%nsec) integer(kind=8) :: secleng(classic_maxsec) ! Section Lengths (on disk: 1 to ed%nsec) integer(kind=8) :: secaddr(classic_maxsec) ! Section Addresses (on disk: 1 to ed%nsec) end type classic_entrydesc_t """ if position is not None: f.seek(position) else: position = f.tell() IDcode = f.read(4) if IDcode.strip() != b'2': raise IndexError("Observation Header reading failure at {0}. " "Record does not appear to be an observation header.". format(position)) f.seek(position) entrydescv2_nw1 = 11 entrydescv2_nw2 = 5 obshead = { 'CODE': f.read(4), 'VERSION': _read_int32(f), 'NSEC': _read_int32(f), #'_blank': _read_int32(f), 'NWORD': _read_int64(f), 'ADATA': _read_int64(f), 'LDATA': _read_int64(f), 'XNUM': _read_int64(f), #'MSEC': _read_int32(f), #'_blank2': _read_int32(f), } section_numbers = np.fromfile(f, count=obshead['NSEC'], dtype='int32') section_lengths = np.fromfile(f, count=obshead['NSEC'], dtype='int64') section_addresses = np.fromfile(f, count=obshead['NSEC'], dtype='int64') return obshead['XNUM'],obshead,dict(zip(section_numbers,section_addresses)) def _read_obshead_v1(f, position=None, verbose=False): """ Read the observation header of a CLASS file (helper function for read_class; should not be used independently) """ if position is not None: f.seek(position) IDcode = f.read(4) if IDcode.strip() != b'2': raise IndexError("Observation Header reading failure at {0}. " "Record does not appear to be an observation header.". format(f.tell() - 4)) (nblocks, nbyteob, data_address, nheaders, data_length, obindex, nsec, obsnum) = numpy.fromfile(f, count=8, dtype='int32') if verbose: print("nblocks,nbyteob,data_address,data_length,nheaders,obindex,nsec,obsnum",nblocks,nbyteob,data_address,data_length,nheaders,obindex,nsec,obsnum) print("DATA_LENGTH: ",data_length) seccodes = numpy.fromfile(f,count=nsec,dtype='int32') # Documentation says addresses then length: It is apparently wrong seclen = numpy.fromfile(f,count=nsec,dtype='int32') secaddr = numpy.fromfile(f,count=nsec,dtype='int32') if verbose: print("Section codes, addresses, lengths: ",seccodes,secaddr,seclen) hdr = {'NBLOCKS':nblocks, 'NBYTEOB':nbyteob, 'DATAADDR':data_address, 'DATALEN':data_length, 'NHEADERS':nheaders, 'OBINDEX':obindex, 'NSEC':nsec, 'OBSNUM':obsnum} #return obsnum,seccodes return obsnum,hdr,dict(zip(seccodes,secaddr)) # THIS IS IN READ_OBSHEAD!!! # def _read_preheader(f): # """ # Not entirely clear what this is, but it is stuff that precedes the actual data # # Looks something like this: # array([ 1, -2, -3, -4, -14, # 9, 17, 18, 25, 55, # 64, 81, 99, -1179344801, 979657591, # # -2, -3, -4, -14 indicate the 4 header types # 9,17,18,25 *MAY* indicate the number of bytes in each # # # HOW is it indicated how many entries there are? # """ # # 13 comes from counting 1, -2,....99 above # numbers = np.fromfile(f, count=13, dtype='int32') # sections = [n for n in numbers if n in header_id_numbers] # return sections
[docs]def downsample_1d(myarr,factor,estimator=np.mean, weight=None): """ Downsample a 1D array by averaging over *factor* pixels. Crops right side if the shape is not a multiple of factor. This code is pure numpy and should be fast. keywords: estimator - default to mean. You can downsample by summing or something else if you want a different estimator (e.g., downsampling error: you want to sum & divide by sqrt(n)) weight: np.ndarray An array of weights to use for the downsampling. If None, assumes uniform 1 """ if myarr.ndim != 1: raise ValueError("Only works on 1d data. Says so in the title.") xs = myarr.size crarr = myarr[:xs-(xs % int(factor))] if weight is None: dsarr = estimator(np.concatenate([[crarr[i::factor] for i in range(factor)]]),axis=0) else: dsarr = estimator(np.concatenate([[crarr[i::factor]*weight[i::factor] for i in range(factor)]]),axis=0) warr = estimator(np.concatenate([[weight[i::factor] for i in range(factor)]]),axis=0) dsarr = dsarr/warr return dsarr
# unit test def test_downsample1d(): data = np.arange(10) weight = np.ones(10) weight[5]=0 assert np.all(downsample_1d(data, 2, weight=weight, estimator=np.mean) == np.array([0.5, 2.5, 4.0, 6.5, 8.5])) def read_observation(f, obsid, file_description=None, indices=None, my_memmap=None, memmap=True, verbose=False): if isinstance(f, str): f = open(f,'rb') opened = True if memmap: my_memmap = numpy.memmap(f, offset=0, dtype='float32', mode='r') else: my_memmap = None elif my_memmap is None and memmap: raise ValueError("Must pass in a memmap object if passing in a file object.") else: opened = False if file_description is None: file_description = _read_first_record(f) if indices is None: indices = _read_indices(f, file_description) index = indices[obsid] obs_position = (index['BLOC']-1)*file_description['reclen']*4 + (index['WORD']-1)*4 log.debug("Reading observation at position {0}".format(obs_position)) obsnum,obshead,sections = _read_obshead(f, file_description, position=obs_position, verbose=verbose) header = obshead datastart = 0 for section_id,section_address in iteritems(sections): # Section addresses are 1-indexed byte addresses # in the current "block" sec_position = obs_position + (section_address-1)*4 temp_hdr = _read_header(f, type=header_id_numbers[section_id], position=sec_position) header.update(temp_hdr) datastart = max(datastart,f.tell()) hdr = header hdr.update(obshead) # re-overwrite things hdr.update({'OBSNUM':obsnum,'RECNUM':obsid}) hdr.update({'RA':hdr['LAM']/pi*180,'DEC':hdr['BET']/pi*180}) hdr.update({'RAoff':hdr['LAMOF']/pi*180,'DECoff':hdr['BETOF']/pi*180}) hdr.update({'OBJECT':hdr['SOURC'].strip()}) hdr.update({'BUNIT':'Tastar'}) hdr.update({'EXPOSURE':float(hdr['TIME'])}) hdr['HDRSTART'] = obs_position hdr['DATASTART'] = datastart hdr.update(indices[obsid]) # Define MJD as mid-exposure time in MJD hdr.update({'OBSDATE': hdr['MJD'] + hdr['UT']/2./pi}) # Apparently the data are still valid in this case? #if hdr['XNUM'] != obsid+1: # log.error("The spectrum read was {0} but {1} was requested.". # format(hdr['XNUM']-1, obsid)) if hdr['KIND'] == 1: # continuum nchan = hdr['NPOIN'] elif 'NCHAN' in hdr: nchan = hdr['NCHAN'] else: log.error("No NCHAN in header. This is not a spectrum.") import ipdb; ipdb.set_trace() # There may be a 1-channel offset? CHECK!!! # (changed by 1 pixel - October 14, 2014) # (changed back - October 21, 2014 - I think the ends are just bad, but not # zero.) f.seek(datastart-1) spec = _read_spectrum(f, position=datastart-1, nchan=nchan, memmap=memmap, my_memmap=my_memmap) if opened: f.close() return spec, hdr def _read_spectrum(f, position, nchan, my_memmap=None, memmap=True): if position != f.tell(): log.warning("Reading data from {0}, but the file is wound " "to {1}.".format(position, f.tell())) if memmap: here = position #spectrum = numpy.memmap(filename, offset=here, dtype='float32', # mode='r', shape=(nchan,)) spectrum = my_memmap[here//4:here//4+nchan] f.seek(here+nchan*4) else: f.seek(position) spectrum = numpy.fromfile(f,count=nchan,dtype='float32') return spectrum def _spectrum_from_header(fileobj, header, memmap=None): return _read_spectrum(fileobj, position=header['DATASTART'], nchan=header['NCHAN'] if 'NCHAN' in hdr else hdr['NPOIN'], my_memmap=memmap) def clean_header(header): newheader = {} for k in header: if not isinstance(header[k], (int, float, str)): if isinstance(header[k], np.ndarray) and header[k].size > 1: if header[k].size > 10: raise ValueError("Large array being put in header. That's no good. key={0}".format(k)) for ii,val in enumerate(header[k]): newheader[k[:7]+str(ii)] = val else: newheader[k[:8]] = str(header[k]) else: newheader[k[:8]] = header[k] return newheader class ClassObject(object): def __init__(self, filename, verbose=False): t0 = time.time() self._file = open(filename, 'rb') self.file_description = _read_first_record(self._file) self.allind = _read_indices(self._file, self.file_description) self._data = np.memmap(self._file, dtype='float32', mode='r') if verbose: log.info("Setting _spectra") self._spectra = LazyItem(self) t1 = time.time() if verbose: log.info("Setting posang. t={0}".format(t1-t0)) self.set_posang() t2 = time.time() if verbose: log.info("Identifying otf scans. t={0}".format(t2-t1)) self._identify_otf_scans(verbose=verbose) t3 = time.time() #self._load_all_spectra() if verbose: log.info("Loaded CLASS object with {3} indices. Time breakdown:" " {0}s for indices, " "{1}s for posang, and {2}s for OTF scan identification" .format(t1-t0, t2-t1, t3-t2, len(self.allind))) def __repr__(self): s = "\n".join(["{k}: {v}".format(k=k,v=v) for k,v in iteritems(self.getinfo())]) return "ClassObject({id}) with {nspec} entries\n".format(id=id(self), nspec=len(self.allind)) + s def getinfo(self, allsources=False): info = dict( tels = self.tels, lines = self.lines, scans = self.scans, sources = self.sources if allsources else self.sci_sources, ) return info def set_posang(self): h0 = self.headers[0] for h in self.headers: dx = h['OFF1'] - h0['OFF1'] dy = h['OFF2'] - h0['OFF2'] h['COMPPOSA'] = np.arctan2(dy,dx)*180/np.pi h0 = h def _identify_otf_scans(self, verbose=False): h0 = self.allind[0] st = 0 otfscan = 0 posangs = [h['COMPPOSA'] for h in self.allind] if verbose: pb = ProgressBar(len(self.allind)) for ii,h in enumerate(self.allind): if (h['SCAN'] != h0['SCAN'] or h['SOURC'] != h0['SOURC']): h0['FIRSTSCAN'] = st cpa = np.median(posangs[st:ii]) for hh in self.allind[st:ii]: hh['SCANPOSA'] = cpa % 180 st = ii if h['SCAN'] == h0['SCAN']: h0['OTFSCAN'] = otfscan otfscan += 1 h['OTFSCAN'] = otfscan else: otfscan = 0 h['OTFSCAN'] = otfscan else: h['OTFSCAN'] = otfscan if verbose: pb.update(ii) def listscans(self, source=None, telescope=None, out=sys.stdout): minid=0 scan = -1 sourc = "" #tel = '' minoff1,maxoff1 = np.inf,-np.inf minoff2,maxoff2 = np.inf,-np.inf ttlangle,nangle = 0.0,0 print("{entries:15s} {SOURC:12s} {XTEL:12s} {SCAN:>8s} {SUBSCAN:>8s} " "[ {RAmin:>12s}, {RAmax:>12s} ] " "[ {DECmin:>12s}, {DECmax:>12s} ] " "{angle:>12s} {SCANPOSA:>12s} {OTFSCAN:>8s} {TSYS:>8s} {UTD:>12s}" .format(entries='Scans', SOURC='Source', XTEL='Telescope', SCAN='Scan', SUBSCAN='Subscan', RAmin='min(RA)', RAmax='max(RA)', DECmin='min(DEC)', DECmax='max(DEC)', SCANPOSA='Scan PA', angle='Angle', OTFSCAN='OTFscan', TSYS='TSYS', UTD='UTD'), file=out) data_rows = [] for ii,row in enumerate(self.headers): if (row['SCAN'] == scan and row['SOURC'] == sourc #and row['XTEL'] == tel ): minoff1 = min(minoff1, row['OFF1']) maxoff1 = max(maxoff1, row['OFF1']) minoff2 = min(minoff2, row['OFF2']) maxoff2 = max(maxoff2, row['OFF2']) ttlangle += np.arctan2(row['OFF2'] - prevrow['OFF2'], row['OFF1'] - prevrow['OFF1'])%np.pi nangle += 1 prevrow = row else: if scan == -1: scan = row['SCAN'] sourc = row['SOURC'] #tel = row['XTEL'] prevrow = row continue ok = True if source is not None: if isinstance(source, (list,tuple)): ok = ok and any(re.search((s), prevrow['SOURC']) for s in source) else: ok = ok and re.search((source), prevrow['SOURC']) if telescope is not None: ok = ok and re.search((telescope), prevrow['XTEL']) if ok: data = dict(RAmin=minoff1*180/np.pi*3600, RAmax=maxoff1*180/np.pi*3600, DECmin=minoff2*180/np.pi*3600, DECmax=maxoff2*180/np.pi*3600, angle=(ttlangle/nangle)*180/np.pi if nangle>0 else 0, e0=minid, e1=ii-1, #TSYS=row['TSYS'] if 'TSYS' in row else '--', UTD=row['DOBS']+row['UT'] if 'UT' in row else -99, **prevrow) print("{e0:7d}-{e1:7d} {SOURC:12s} {XTEL:12s} {SCAN:8d} {SUBSCAN:8d} " "[ {RAmin:12f}, {RAmax:12f} ] " "[ {DECmin:12f}, {DECmax:12f} ] " "{angle:12.1f} {SCANPOSA:12.1f} {OTFSCAN:8d}" " {TSYS:>8.1f} {UTD:12f}". format(**data), file=out) data_rows.append(data) minoff1,maxoff1 = np.inf,-np.inf minoff2,maxoff2 = np.inf,-np.inf ttlangle,nangle = 0.0,0 scan = row['SCAN'] sourc = row['SOURC'] #tel = row['XTEL'] minid = ii return data @property def tels(self): if hasattr(self,'_tels'): return self._tels else: self._tels = set([h['XTEL'] for h in self.allind]) return self._tels @property def sources(self): if hasattr(self,'_source'): return self._source else: self._source = set([h['SOURC'] for h in self.allind]) return self._source @property def scans(self): if hasattr(self,'_scan'): return self._scan else: self._scan = set([h['SCAN'] for h in self.allind]) return self._scan @property def sci_sources(self): return set([s for s in self.sources if s[:4] not in ('SKY-', 'TSYS', 'TCAL', 'TREC', 'HOT-', 'COLD')]) @property def lines(self): if hasattr(self,'_lines'): return self._lines else: self._lines = set([h['LINE'] for h in self.allind]) return self._lines def _load_all_spectra(self, indices=None): if indices is None: indices = range(self.file_description['xnext']-1) if hasattr(self, '_loaded_indices'): indices_set = set(indices) indices_to_load = (indices_set.difference(self._loaded_indices)) self._loaded_indices = self._loaded_indices.union(indices_set) if any(indices_to_load): pb = ProgressBar(len(indices_to_load)) for ii,k in enumerate(xrange(indices_to_load)): self._spectra[k] pb.update(ii) else: self._loaded_indices = set(indices) self._spectra.load_all() @property def spectra(self): return [x[0] for x in self._spectra] @property def headers(self): return [self._spectra[ii][1] if ii in self._spectra else x for ii,x in enumerate(self.allind)] def select_spectra(self, all=None, line=None, linere=None, linereflags=re.IGNORECASE, number=None, scan=None, offset=None, source=None, sourcere=None, sourcereflags=re.IGNORECASE, range=None, quality=None, telescope=None, telescopere=None, telescopereflags=re.IGNORECASE, subscan=None, entry=None, posang=None, #observed=None, #reduced=None, frequency=None, section=None, user=None, include_old_versions=False, ): """ Parameters ---------- include_old_versions: bool Include spectra with XVER numbers <0? These are CLASS spectra that have been "overwritten" (re-reduced?) """ if entry is not None and len(entry)==2: return irange(entry[0], entry[1]) if frequency is not None: self._load_all_spectra() sel = [(re.search(re.escape(ensure_bytes(line)), h['LINE'], re.IGNORECASE) if line is not None else True) and (re.search(ensure_bytes(linere), h['LINE'], linereflags) if linere is not None else True) and (h['SCAN'] == scan if scan is not None else True) and ((h['OFF1'] == offset or h['OFF2'] == offset) if offset is not None else True) and (re.search(re.escape(ensure_bytes(source)), h['CSOUR'], re.IGNORECASE) if source is not None else True) and (re.search(ensure_bytes(sourcere), h['CSOUR'], sourcereflags) if sourcere is not None else True) and (h['OFF1']>range[0] and h['OFF1'] < range[1] and h['OFF2']>range[2] and h['OFF2'] < range[3] if range is not None and len(range)==4 else True) and (h['QUAL'] == quality if quality is not None else True) and (re.search(re.escape(ensure_bytes(telescope)), h['CTELE'], re.IGNORECASE) if telescope is not None else True) and (re.search(ensure_bytes(telescopere), h['CTELE'], telescopereflags) if telescopere is not None else True) and (h['SUBSCAN']==subscan if subscan is not None else True) and (h['NUM'] >= number[0] and h['NUM'] < number[1] if number is not None else True) and ('RESTF' in h and # Need to check that it IS a spectrum: continuum data can't be accessed this way h['RESTF'] > frequency[0] and h['RESTF'] < frequency[1] if frequency is not None and len(frequency)==2 else True) and (h['COMPPOSA']%180 > posang[0] and h['COMPPOSA']%180 < posang[1] if posang is not None and len(posang)==2 else True) and # 1A uses XVER, 2A uses VER. If neither are present, it's # probably not a valid spectrum? (h.get('XVER', h.get('VER', -999)) > 0 if not include_old_versions else True) for h in self.headers ] return [ii for ii,k in enumerate(sel) if k] def get_spectra(self, progressbar=True, **kwargs): selected_indices = self.select_spectra(**kwargs) if not any(selected_indices): raise ValueError("Selection yielded empty.") self._spectra.load(selected_indices, progressbar=progressbar) return [self._spectra[ii] for ii in selected_indices] def get_pyspeckit_spectra(self, progressbar=True, **kwargs): spdata = self.get_spectra(progressbar=progressbar, **kwargs) spectra = [pyspeckit.Spectrum(data=data, xarr=make_axis(header), header=clean_header(header)) for data,header in spdata] return spectra def read_observations(self, observation_indices, progressbar=True): self._spectra.load(observation_indices, progressbar=progressbar) return [self._spectra[ii] for ii in observation_indices]
[docs]@print_timing def read_class(filename, downsample_factor=None, sourcename=None, telescope=None, line=None, posang=None, verbose=False, flag_array=None): """ Read a binary class file. Based on the `GILDAS CLASS file type Specification <http://iram.fr/IRAMFR/GILDAS/doc/html/class-html/node58.html>`_ Parameters ---------- filename: str downsample_factor: None or int Factor by which to downsample data by averaging. Useful for overresolved data. sourcename: str or list of str Source names to match to the data (uses regex) telescope: str or list of str 'XTEL' or 'TELE' parameters: the telescope & instrument line: str or list of str The line name posang: tuple of 2 floats The first float is the minimum value for the position angle. The second float is the maximum value for the position angle. verbose: bool Log messages with severity INFO flag_array: np.ndarray An array with the same shape as the data used to flag out (remove) data when downsampling. True = flag out """ classobj = ClassObject(filename) if not isinstance(sourcename, (list,tuple)): sourcename = [sourcename] if not isinstance(telescope, (list,tuple)): telescope = [telescope] if not isinstance(line, (list,tuple)): line = [line] spectra,headers = [],[] if verbose: log.info("Reading...") selection = [ii for source in sourcename for tel in telescope for li in line for ii in classobj.select_spectra(sourcere=source, telescope=tel, line=li, posang=posang)] sphdr = classobj.read_observations(selection) if len(sphdr) == 0: return None spec,hdr = zip(*sphdr) spectra += spec headers += hdr indexes = headers weight = ~flag_array if flag_array is not None else None if downsample_factor is not None: if verbose: log.info("Downsampling...") spectra = [downsample_1d(spec, downsample_factor, weight=weight) for spec in ProgressBar(spectra)] headers = [downsample_header(h, downsample_factor) for h in ProgressBar(headers)] for hdr in headers: stringify_header(hdr) return spectra,headers,indexes
def stringify_header(header): from six import string_types, integer_types import string FITS_allowed_types = (string_types + integer_types + (float, complex, bool, np.floating, np.integer, np.complexfloating, np.bool_)) bad_chars = string.printable[96:] badcharre = re.compile("[{0}]".format(bad_chars)) for key, value in header.items(): if isinstance(value, bytes): header[key] = value.decode() elif not isinstance(value, FITS_allowed_types): header[key] = badcharre.sub("", str(header[key])) def downsample_header(hdr, downsample_factor): for k in ('NCHAN','NPOIN','DATALEN'): if k in hdr: hdr[k] = int((hdr[k] / downsample_factor)) # maybe wrong? h['RCHAN'] = (h['RCHAN']-1) / downsample_factor + 1 scalefactor = 1./downsample_factor hdr['RCHAN'] = (hdr['RCHAN']-1)*scalefactor + 0.5 + scalefactor/2. for kw in ['FRES','VRES']: if kw in hdr: hdr[kw] *= downsample_factor return hdr
[docs]def make_axis(header,imagfreq=False): """ Create a :class:`pyspeckit.spectrum.units.SpectroscopicAxis` from the CLASS "header" """ from .. import units rest_frequency = header.get('RESTF') xunits = 'MHz' nchan = header.get('NCHAN') voff = header.get('VOFF') foff = header.get('FOFF') doppler = header.get('DOPPLER') fres = header.get('FRES') refchan = header.get('RCHAN') imfreq = header.get('IMAGE') if foff in (None, 0.0) and voff not in (None, 0.0): # Radio convention foff = -voff/2.997924580e5 * rest_frequency if not imagfreq: xarr = rest_frequency + foff + (numpy.arange(1, nchan+1) - refchan) * fres XAxis = units.SpectroscopicAxis(xarr,unit='MHz',refX=rest_frequency*u.MHz) else: xarr = imfreq - (numpy.arange(1, nchan+1) - refchan) * fres XAxis = units.SpectroscopicAxis(xarr,unit='MHz',refX=imfreq*u.MHz) return XAxis
[docs]@print_timing def class_to_obsblocks(filename, telescope, line, datatuple=None, source=None, imagfreq=False, DEBUG=False, **kwargs): """ Load an entire CLASS observing session into a list of ObsBlocks based on matches to the 'telescope', 'line' and 'source' names Parameters ---------- filename : string The Gildas CLASS data file to read the spectra from. telescope : list List of telescope names to be matched. line : list List of line names to be matched. source : list (optional) List of source names to be matched. Defaults to None. imagfreq : bool Create a SpectroscopicAxis with the image frequency. """ if datatuple is None: spectra,header,indexes = read_class(filename, **kwargs) else: spectra,header,indexes = datatuple obslist = [] lastscannum = -1 spectrumlist = None for sp,hdr,ind in zip(spectra,header,indexes): hdr.update(ind) # this is slow but necessary... H = pyfits.Header() for k,v in iteritems(hdr): if hasattr(v,"__len__") and not isinstance(v,str): # make an array of header entries, but this # supports only up to 10 of them... if len(v) > 1: if len(v) < 10: for ii,vv in enumerate(v): newkey = k[:7]+str(ii) H[newkey] = vv elif len(v) < 100: for ii,vv in enumerate(v): newkey = k[:6]+str(ii) H[newkey] = vv else: raise ValueError("Too many entries for {0}".format(k)) else: H[k] = v[0] #elif not any(x in str(v).lower() for x in ('comment', 'end', 'history')): # # do not try to add comments... # This commented out block used to attempt to reject comments # using a private regex in the old pyfits which no longer exists. # I don't know if it was necessary. else: H[k] = v scannum = hdr['SCAN'] if 'XTEL' in hdr and hdr['XTEL'].strip() not in telescope: continue if hdr['LINE'].strip() not in line: continue if (source is not None) and (hdr['SOURC'].strip() not in source): continue hdr['RESTFREQ'] = hdr.get('RESTF') H['RESTFREQ'] = hdr.get('RESTF') #print "Did not skip %s,%s. Scannum, last: %i,%i" % (hdr['XTEL'],hdr['LINE'],scannum,lastscannum) if scannum != lastscannum: lastscannum = scannum if spectrumlist is not None: obslist.append(pyspeckit.ObsBlock(spectrumlist)) xarr = make_axis(hdr,imagfreq=imagfreq) spectrumlist = [( pyspeckit.Spectrum(xarr=xarr, header=H, data=sp))] else: spectrumlist.append( pyspeckit.Spectrum(xarr=xarr, header=H, data=sp)) return obslist
[docs]class LazyItem(object): """ Simple lazy spectrum-retriever wrapper """ def __init__(self, parent): self.parent = parent self.sphdr = {} self.nind = len(self.parent.allind) self.nloaded = 0 def __repr__(self): return ("Set of {0} spectra & headers, {1} loaded" " ({2:0.2f}%)".format(self.nind, self.nloaded, (float(self.nloaded)/self.nind)*100)) def load_all(self, progressbar=True): self.load(range(self.nind)) def load(self, indices, progressbar=True): pb = ProgressBar(len(indices)) counter = 0 for k in indices: self[k] counter += 1 pb.update(counter) def __getitem__(self, key): if key in self.sphdr: return self.sphdr[key] elif isinstance(key, slice): return [self[k] for k in xrange(key.start or 0, key.end or len(self.parent.allind), key.step or 1)] else: sphd = read_observation(self.parent._file, key, file_description=self.parent.file_description, indices=self.parent.allind, my_memmap=self.parent._data) # Update the header with OTFSCAN and POSANG info sphd[1].update(self.parent.allind[key]) self.sphdr[key] = sphd self.nloaded += 1 return sphd def __iter__(self): return self.next() def __next__(self): for k in self.spheader: yield self.spheader[k] def __contains__(self, key): return key in self.sphdr
[docs]@print_timing def class_to_spectra(filename, datatuple=None, **kwargs): """ Load each individual spectrum within a CLASS file into a list of Spectrum objects """ if datatuple is None: spectra,header,indexes = read_class(filename, **kwargs) else: spectra,header,indexes = datatuple spectrumlist = [] for sp,hdr,ind in zip(spectra,header,indexes): hdr.update(ind) xarr = make_axis(hdr) spectrumlist.append( pyspeckit.Spectrum(xarr=xarr, header=hdr, data=sp)) return pyspeckit.Spectra(spectrumlist)
[docs]def tests(): """ Tests are specific to the machine on which this code was developed. """ fn1 = '/Users/adam/work/bolocam/hht/class_003.smt' #fn1 = '/Users/adam/work/bolocam/hht/class_001.smt' #fn1 = '/Users/adam/work/bolocam/hht/test_SMT-F1M-VU-20824-073.cls' #fn2 = '/Users/adam/work/bolocam/hht/test_SMT-F1M-VU-79472+203.cls' #F1 = read_class(fn1)#,DEBUG=True) #F2 = read_class(fn2) n2hp = class_to_obsblocks(fn1,telescope=['SMT-F1M-HU','SMT-F1M-VU'],line=['N2HP(3-2)','N2H+(3-2)']) hcop = class_to_obsblocks(fn1,telescope=['SMT-F1M-HL','SMT-F1M-VL'],line=['HCOP(3-2)','HCO+(3-2)'])