Collection of scripts and small programs used by the EVN Support Scientists at JIVE during the regular data processing of EVN observations.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

226 lines
8.9 KiB

  1. #!/usr/bin/env python3
  2. """
  3. Python port of Bob Campbell's IDL script to make nominal tsys tables.
  4. It takes the nominal SEFD values from the sefd_values.txt table and generates an ANTAB file using
  5. these values. Gains will be set to 1/SEFD, and all Tsys to 1.0.
  6. Note that it will overwrite any existing ANTAB file in the current path.
  7. Version: 4.3
  8. Date: Sep 2018
  9. Author: Benito Marcote (marcote@jive.eu) & Jay Blanchard (blanchard@jive.eu)
  10. version 4.3 changes
  11. - Fixed issue when giving SEFD, not ignores that antenna may not be in status table
  12. version 4.2 changes
  13. - Explicit error if antenna or freq. is not known
  14. version 4.1 changes
  15. - Minor issues (ordering input arguments, version)
  16. version 4.0 changes
  17. - Major code changes for a better exception handling
  18. version 3.0 changes
  19. - new argument to set the freq. interval (-fr / --freqrange)
  20. version 2.0 changes
  21. - interactive or argument-based
  22. - documentation!!
  23. - Takes SEFD values from status table of EVN
  24. """
  25. import os
  26. import sys
  27. import argparse
  28. import datetime as dt
  29. from math import floor
  30. from collections import defaultdict
  31. __version__ = 4.2
  32. help_str = """Writes a nominal SEFD ANTAB file. Gain will be set to 1/SEFD, and all Tsys to 1.0.
  33. It will overwrite any previous antab file in the current path.
  34. antabfs_nominal.py uses the SEFD information from sefd_values.txt to compute the nominal values.
  35. Creates (or overwrites) a file called <experiment><antenna>.antabfs, where <experiment> and
  36. <antenna> are the input from the user.
  37. """
  38. parser = argparse.ArgumentParser(description=help_str, prog='antabfs_nominal.py')
  39. parser.add_argument('antenna', type=str, default=None, help='Antenna name (two-letters syntax, except for Jb1 Jb2 Ro7 Ro3)')
  40. parser.add_argument('experiment', type=str, default=None, help='Experiment name')
  41. parser.add_argument('start', type=str, default=None, help='Start time (DOY/HH:MM, YYYY/DOY/HH:MM or YYYY/MM/DD/HH:MM)')
  42. parser.add_argument('-v', '--version', action='version', version='%(prog)s {}'.format(__version__))
  43. parser.add_argument('-b', '--band', type=str, default=None, help='Observed band (in cm). REQUIRED unless SEFD provided')
  44. parser.add_argument('-d', '--duration', type=float, default=24, help='Duration of the experiment (in hours). Default: 24 h')
  45. parser.add_argument('-fr', '--freqrange', type=str, default='100,100000', help='Frequency range where the ANTAB is applicable (lower and upper limit, in MHz). Default 100,100000 (please, do not use spaces between the numbers).')
  46. parser.add_argument('-s', '--sefd', type=float, default=None, help='SEFD to be used (optional). Default values are loaded.')
  47. parser.add_argument('-i', '--interval', type=float, default=0.25, help='Interval between Tsys measurements (in min). Default: 0.5')
  48. parser.add_argument('-sb', '--subbands', type=int, default=8, help='Number of subbands in the experiment. Default: 8 = L1|R1 L2|R2 ... L8|R8')
  49. args = parser.parse_args()
  50. i_already_warn_about_seconds = False
  51. def read_sefd_table(tablename=os.path.dirname(__file__)+'/sefd_values.txt'):
  52. sefd_table = open(tablename, 'r')
  53. titles = sefd_table.readline().strip().split('|')
  54. titles = [t.strip() for t in titles]
  55. sefd_dict = defaultdict(dict)
  56. for an_ant in sefd_table.readlines():
  57. values = [t.strip() for t in an_ant.strip().split('|')]
  58. for i in range(1, len(values)):
  59. if values[i] != '':
  60. sefd_dict[values[0].lower()][titles[i]] = float(values[i])
  61. return sefd_dict
  62. def read_sefd_values(table, antenna, band):
  63. if args.sefd is not None:
  64. # Then no need of getting the information from the table
  65. return args.sefd
  66. if antenna not in table:
  67. print('ERROR: {} is not available.\n'.format(antenna))
  68. print('The available antennas are: {}'.format(' '.join(table.keys())))
  69. sys.exit(1)
  70. elif band not in table[antenna]:
  71. print('ERROR: antenna {} does not have SEFD information for {}-cm observations'.format(antenna, band))
  72. print('{} only has SEFD information for {} cm'.format(antenna, ', '.join(table[antenna].keys())))
  73. sys.exit(1)
  74. else:
  75. return table[antenna][band]
  76. def index_header():
  77. indexes = list()
  78. for i in range(1, args.subbands+1):
  79. indexes.append("'R{n}|L{n}'".format(n=i))
  80. return ','.join(indexes)
  81. def get_header(antenna, gain, freqrange):
  82. """Returns the apropiate header for the given antenna using a given gain value.
  83. Inputs:
  84. antenna : str
  85. The antenna name (two letters syntax)
  86. gain : float
  87. The gain (in 1/Jy) for this antenna
  88. """
  89. generic_header = '''!
  90. ! Nominal calibration data for {ant} created by
  91. ! antabfs_nominal.py (version {version})
  92. ! Script at JIVE done by Benito Marcote & Jay Blanchard
  93. !
  94. GAIN {ant} ELEV DPFU={gain},{gain} POLY=1.0 FREQ={freqrange}
  95. /
  96. TSYS {ant} FT=1.0 TIMEOFF=0
  97. INDEX = {indexes}
  98. /'''
  99. return generic_header.format(ant=antenna[:2].upper(), gain=gain, indexes=index_header(),
  100. version=__version__, freqrange=','.join([str(i) for i in freqrange]))
  101. def hm2hhmmss(hhmm):
  102. """Takes a time in str format HH:MM.MM and returns int(hh), int(mm), float(ss)"""
  103. try:
  104. hour, minute = hhmm.split(':')
  105. except ValueError:
  106. if not i_already_warn_about_seconds:
  107. print('WARNING: only HH:MM are read. Seconds or other smaller numbers are going to be ignored.')
  108. i_already_warn_about_seconds = True
  109. hour, minute, *others = hhmm.split(':')
  110. minute = float(minute)
  111. second = int((minute-floor(minute))*60)
  112. return int(hour), int(floor(minute)), second
  113. def date2datetime(date):
  114. """Convert the given date to DOY, HH, MM.
  115. Inputs:
  116. date : str
  117. Date in format DOY/HH:MM, YYYY/DOY/HH:MM or YYYY/MM/DD:HH:MM
  118. """
  119. if date.count('/') == 1:
  120. # Uses a fake year
  121. doy, hhmm = date.split('/')
  122. return dt.datetime(1969, 1, 1, *hm2hhmmss(hhmm)) + dt.timedelta(int(doy)-1)
  123. elif date.count('/') == 2:
  124. year, doy, hhmm = date.split('/')
  125. return dt.datetime(int(year), 1, 1, *hm2hhmmss(hhmm)) + dt.timedelta(int(doy)-1)
  126. elif date.count('/') == 3:
  127. year, month, day, hhmm = date.split('/')
  128. return dt.datetime(int(year), int(month), int(day), *hm2hhmmss(hhmm))
  129. else:
  130. print('ERROR: date must have the following format: DOY/HH:MM, YYYY/DOY/HH:MM or YYYY/MM/DD:HH:MM')
  131. raise SyntaxError
  132. def date2string(datetime):
  133. """Return a string with the date in the correct ANTAB format
  134. """
  135. return '{} {:02d}:{:05.2f}'.format(datetime.strftime('%j'), datetime.hour, datetime.minute+datetime.second/60.)
  136. #currently asks for inputs, might change this in future to read from vex...
  137. if args.experiment == None:
  138. args.experiment = raw_input("Input experiment name: ")
  139. if args.antenna == None:
  140. args.antenna = raw_input("Input antenna name (two-letter syntax (except Jb1 Jb2 Ro7 Ro3): ")
  141. if args.band == None and args.sefd == None:
  142. output = raw_input("Input frequency band (cm) or SEFD value (Jy). Write 'band VALUE' or 'sefd VALUE'").split(' ')
  143. if output[0].lower() == 'band':
  144. args.band = output[1]
  145. elif output[0].lower() == 'sefd':
  146. args.sefd = output[1]
  147. else:
  148. print('Wrong format. It must be either: "band VALUE" or "sefd VALUE"')
  149. raise ValueError
  150. if args.start == None:
  151. input_starttime = raw_input("Enter start day of the year, hour and minute (comma separated): ").split(',')
  152. args.start = '{} {}:{}'.format(*input_starttime)
  153. dur = raw_input("Enter duration (hours; enter to default): ")
  154. if dur != '':
  155. args.duration = float(dur)
  156. # Read and interpretate the freqrange.
  157. if args.freqrange.count(',') != 1:
  158. print('The frequency range (--freqrange) must contain two values (comma-separated): the lower and upper frequency limit in MHz (please, do not use spaces between the numbers).')
  159. sys.exit(1)
  160. args.freqrange = [int(i) for i in args.freqrange.split(',')]
  161. if not (args.freqrange[0] < 30*1000/float(args.band) < args.freqrange[1]):
  162. print('The provided frequency range must contain the frequency band, and this is not the case.')
  163. print('Introduced band: {} GHz'.format(30/float(args.band)))
  164. print('Introduced frequency range: {}-{} GHz'.format(args.freqrange[0]/1e3, args.freqrange[1]/1e3))
  165. sys.exit(1)
  166. sefd_info = read_sefd_table()
  167. start_time = date2datetime(args.start)
  168. end_time = start_time + dt.timedelta(args.duration/24.)
  169. a_time = date2datetime(args.start)
  170. # Creating the ANTAB file
  171. antab_file = open('{}{}.antabfs'.format(args.experiment.lower(), args.antenna.lower()[:2]), 'wt')
  172. antab_file.write(get_header(args.antenna.lower(), 1./read_sefd_values(sefd_info, args.antenna.lower(), args.band),
  173. args.freqrange)+'\n')
  174. while a_time < end_time:
  175. antab_file.write('{}{}\n'.format(date2string(a_time), ' 1.0'*args.subbands))
  176. a_time = a_time + dt.timedelta(args.interval/(60*24.))
  177. antab_file.write('/\n') # antab expects trailing /
  178. antab_file.close()
  179. print('File {}{}.antabfs created successfully.'.format(args.experiment.lower(), args.antenna.lower()[:2]))