licel.py

Mon, 22 Feb 2016 16:50:03 +0200

author
Ioannis <devnull@localhost>
date
Mon, 22 Feb 2016 16:50:03 +0200
changeset 35
b1146c96f727
parent 33
2984158468e6
permissions
-rwxr-xr-x

Moving in separate folder.

binietoglou@0 1 import numpy as np
binietoglou@0 2 import datetime
ulalume3@27 3 from generic import BaseLidarMeasurement, LidarChannel
binietoglou@0 4 import musa_netcdf_parameters
binietoglou@0 5 import musa_2009_netcdf_parameters
binietoglou@0 6
binietoglou@0 7 licel_file_header_format = ['Filename',
binietoglou@33 8 'StartDate StartTime EndDate EndTime Altitude Longtitude Latitude ZenithAngle', # Appart from Site that is read manually
binietoglou@0 9 'LS1 Rate1 LS2 Rate2 DataSets', ]
binietoglou@0 10 licel_file_channel_format = 'Active AnalogPhoton LaserUsed DataPoints 1 HV BinW Wavelength d1 d2 d3 d4 ADCbits NShots Discriminator ID'
binietoglou@0 11
ioannis@22 12
binietoglou@0 13 class LicelFile:
ulalume3@27 14
binietoglou@0 15 def __init__(self, filename):
binietoglou@0 16 self.start_time = None
binietoglou@0 17 self.stop_time = None
binietoglou@0 18 self.import_file(filename)
binietoglou@0 19 self.calculate_physical()
binietoglou@0 20 self.filename = filename
binietoglou@0 21
binietoglou@0 22 def calculate_physical(self):
binietoglou@0 23 for channel in self.channels.itervalues():
binietoglou@0 24 channel.calculate_physical()
binietoglou@0 25
binietoglou@0 26 def import_file(self, filename):
binietoglou@0 27 """Imports a licel file.
binietoglou@0 28 Input: filename
binietoglou@0 29 Output: object """
binietoglou@0 30
binietoglou@0 31 channels = {}
binietoglou@33 32
binietoglou@33 33 with open(filename, 'rb') as f:
binietoglou@33 34
binietoglou@33 35 self.read_header(f)
binietoglou@33 36
binietoglou@33 37 # Check the complete header is read
binietoglou@33 38 a = f.readline()
binietoglou@0 39
binietoglou@33 40 # Import the data
binietoglou@33 41 for current_channel_info in self.channel_info:
binietoglou@33 42 raw_data = np.fromfile(f, 'i4', int(current_channel_info['DataPoints']))
binietoglou@33 43 a = np.fromfile(f, 'b', 1)
binietoglou@33 44 b = np.fromfile(f, 'b', 1)
binietoglou@33 45
binietoglou@33 46 if (a[0] != 13) | (b[0] != 10):
binietoglou@33 47 print "Warning: No end of line found after record. File could be corrupt"
binietoglou@33 48 channel = LicelFileChannel(current_channel_info, raw_data, self.duration())
binietoglou@33 49
binietoglou@33 50 channel_name = channel.channel_name
binietoglou@33 51
binietoglou@33 52 if channel_name in channels.keys():
binietoglou@33 53 # If the analog/photon naming scheme is not enough, find a new one!
binietoglou@33 54 raise IOError('Trying to import two channels with the same name')
binietoglou@33 55
binietoglou@33 56 channels[channel_name] = channel
binietoglou@33 57
binietoglou@33 58 self.channels = channels
binietoglou@33 59
binietoglou@33 60
binietoglou@33 61 def read_header(self, f):
binietoglou@33 62 """ Read the header of a open file f.
binietoglou@33 63
binietoglou@33 64 Returns raw_info and channel_info. Updates some object properties. """
binietoglou@33 65
binietoglou@0 66 #Read the first 3 lines of the header
binietoglou@0 67 raw_info = {}
binietoglou@33 68 channel_info = []
binietoglou@33 69
binietoglou@33 70 # Read first line
binietoglou@33 71 raw_info['Filename'] = f.readline().strip()
binietoglou@33 72
binietoglou@33 73 # Read second line
binietoglou@33 74 second_line = f.readline()
binietoglou@33 75
binietoglou@33 76 # Many licel files don't follow the licel standard. Specifically, the
binietoglou@33 77 # measurement site is not always 8 characters, and can include white
binietoglou@33 78 # spaces. For this, the site name is detect everything before the first
binietoglou@33 79 # date. For efficiency, the first date is found by the first '/'.
binietoglou@33 80 # e.g. assuming a string like 'Site name 01/01/2010 ...'
binietoglou@33 81
binietoglou@33 82 site_name = second_line.split('/')[0][:-2]
binietoglou@33 83 clean_site_name = site_name.strip()
binietoglou@33 84 raw_info['Site'] = clean_site_name
binietoglou@33 85 raw_info.update(match_lines(second_line[len(clean_site_name) + 1:], licel_file_header_format[1]))
binietoglou@33 86
binietoglou@33 87 # Read third line
binietoglou@33 88 third_line = f.readline()
binietoglou@33 89 raw_info.update(match_lines(third_line, licel_file_header_format[2]))
binietoglou@33 90
binietoglou@33 91 # Update the object properties based on the raw info
binietoglou@0 92 start_string = '%s %s' % (raw_info['StartDate'], raw_info['StartTime'])
binietoglou@0 93 stop_string = '%s %s' % (raw_info['EndDate'], raw_info['EndTime'])
binietoglou@0 94 date_format = '%d/%m/%Y %H:%M:%S'
binietoglou@33 95
binietoglou@0 96 self.start_time = datetime.datetime.strptime(start_string, date_format)
binietoglou@0 97 self.stop_time = datetime.datetime.strptime(stop_string, date_format)
binietoglou@0 98 self.latitude = float(raw_info['Latitude'])
binietoglou@0 99 self.longitude = float(raw_info['Longtitude'])
binietoglou@33 100
binietoglou@0 101 # Read the rest of the header.
binietoglou@0 102 for c1 in range(int(raw_info['DataSets'])):
binietoglou@0 103 channel_info.append(match_lines(f.readline(), licel_file_channel_format))
binietoglou@33 104
binietoglou@33 105 self.raw_info = raw_info
binietoglou@33 106 self.channel_info = channel_info
ioannis@21 107
ulalume3@27 108 def duration(self):
ulalume3@27 109 """ Return the duration of the file. """
ulalume3@27 110 dt = self.stop_time - self.start_time
ulalume3@27 111 return dt.seconds
ulalume3@27 112
ulalume3@27 113
binietoglou@0 114 class LicelFileChannel:
binietoglou@0 115
ulalume3@27 116 def __init__(self, raw_info = None, raw_data = None, duration = None):
binietoglou@0 117 self.raw_info = raw_info
binietoglou@0 118 self.raw_data = raw_data
ulalume3@27 119 self.duration = duration
ulalume3@27 120
binietoglou@0 121 @property
binietoglou@0 122 def wavelength(self):
binietoglou@0 123 if self.raw_info is not None:
binietoglou@0 124 wave_str = self.raw_info['Wavelength']
binietoglou@0 125 wavelength = wave_str.split('.')[0]
binietoglou@0 126 return int(wavelength)
binietoglou@0 127 else:
binietoglou@0 128 return None
binietoglou@0 129
binietoglou@0 130 @property
binietoglou@0 131 def channel_name(self):
binietoglou@0 132 '''
binietoglou@0 133 Construct the channel name adding analog photon info to avoid duplicates
binietoglou@0 134 '''
binietoglou@0 135 acquisition_type = self.analog_photon_string(self.raw_info['AnalogPhoton'])
binietoglou@0 136 channel_name = "%s_%s" % (self.raw_info['Wavelength'], acquisition_type)
binietoglou@0 137 return channel_name
binietoglou@0 138
binietoglou@0 139 def analog_photon_string(self, analog_photon_number):
binietoglou@0 140 if analog_photon_number == '0':
binietoglou@0 141 string = 'an'
binietoglou@0 142 else:
binietoglou@0 143 string = 'ph'
binietoglou@0 144 return string
binietoglou@0 145
binietoglou@0 146 def calculate_physical(self):
binietoglou@0 147 data = self.raw_data
binietoglou@0 148
binietoglou@0 149 number_of_shots = float(self.raw_info['NShots'])
ulalume3@27 150 norm = data / number_of_shots
binietoglou@0 151 dz = float(self.raw_info['BinW'])
binietoglou@0 152
binietoglou@0 153 if self.raw_info['AnalogPhoton']=='0':
binietoglou@0 154 # If the channel is in analog mode
binietoglou@0 155 ADCb = int(self.raw_info['ADCbits'])
binietoglou@0 156 ADCrange = float(self.raw_info['Discriminator'])*1000 # Value in mV
binietoglou@0 157 channel_data = norm*ADCrange/((2**ADCb)-1)
binietoglou@0 158
binietoglou@0 159 # print ADCb, ADCRange,cdata,norm
binietoglou@0 160 else:
binietoglou@0 161 # If the channel is in photoncounting mode
binietoglou@0 162 # Frequency deduced from range resolution! (is this ok?)
binietoglou@0 163 # c = 300 # The result will be in MHZ
binietoglou@0 164 # SR = c/(2*dz) # To account for pulse folding
binietoglou@0 165 # channel_data = norm*SR
binietoglou@0 166 # CHANGE:
binietoglou@0 167 # For the SCC the data are needed in photons
ulalume3@27 168 channel_data = norm *number_of_shots
binietoglou@0 169 #print res,c,cdata,norm
binietoglou@0 170
binietoglou@0 171 #Calculate Z
binietoglou@0 172 number_of_bins = int(self.raw_info['DataPoints'])
binietoglou@0 173 self.z = np.array([dz*bin_number + dz/2.0 for bin_number in range(number_of_bins)])
binietoglou@0 174 self.dz = dz
binietoglou@0 175 self.number_of_bins = number_of_bins
binietoglou@0 176 self.data = channel_data
ioannis@22 177
binietoglou@0 178
binietoglou@0 179 class LicelLidarMeasurement(BaseLidarMeasurement):
binietoglou@0 180 '''
binietoglou@0 181
binietoglou@0 182 '''
binietoglou@0 183 extra_netcdf_parameters = musa_netcdf_parameters
ulalume3@27 184 raw_info = {} # Keep the raw info from the files
ulalume3@27 185 durations = {} # Keep the duration of the files
binietoglou@0 186
binietoglou@0 187 def import_file(self, filename):
binietoglou@0 188 if filename in self.files:
binietoglou@0 189 print "File has been imported already:" + filename
binietoglou@0 190 else:
binietoglou@0 191 current_file = LicelFile(filename)
ioannis@22 192 self.raw_info[current_file.filename] = current_file.raw_info
ulalume3@27 193 self.durations[current_file.filename] = current_file.duration()
ioannis@22 194
binietoglou@0 195 for channel_name, channel in current_file.channels.items():
binietoglou@0 196 if channel_name not in self.channels:
ulalume3@27 197 self.channels[channel_name] = LicelChannel(channel)
binietoglou@0 198 self.channels[channel_name].data[current_file.start_time] = channel.data
binietoglou@0 199 self.files.append(current_file.filename)
binietoglou@0 200
binietoglou@0 201 def append(self, other):
binietoglou@0 202
binietoglou@0 203 self.start_times.extend(other.start_times)
binietoglou@0 204 self.stop_times.extend(other.stop_times)
binietoglou@0 205
binietoglou@0 206 for channel_name, channel in self.channels.items():
binietoglou@0 207 channel.append(other.channels[channel_name])
binietoglou@0 208
ioannis@22 209 def _get_duration(self, raw_start_in_seconds):
ioannis@22 210 ''' Return the duration for a given time scale. If only a single
ioannis@22 211 file is imported, then this cannot be guessed from the time difference
ioannis@22 212 and the raw_info of the file are checked.
ioannis@22 213 '''
ioannis@22 214
ioannis@22 215 if len(raw_start_in_seconds) == 1: # If only one file imported
ulalume3@27 216 duration = self.durations.itervalues().next() # Get the first (and only) raw_info
ioannis@31 217 duration_sec = duration
ioannis@22 218 else:
ioannis@22 219 duration_sec = np.diff(raw_start_in_seconds)[0]
binietoglou@0 220
ioannis@22 221 return duration_sec
ioannis@22 222
ioannis@22 223
ulalume3@27 224 class LicelChannel(LidarChannel):
binietoglou@0 225
binietoglou@0 226 def __init__(self, channel_file):
binietoglou@33 227 c = 299792458.0 # Speed of light
binietoglou@0 228 self.wavelength = channel_file.wavelength
binietoglou@0 229 self.name = channel_file.channel_name
ulalume3@27 230 self.binwidth = channel_file.dz * 2 / c # in seconds
binietoglou@0 231 self.data = {}
binietoglou@0 232 self.resolution = channel_file.dz
ulalume3@27 233 self.z = np.arange(channel_file.number_of_bins) * self.resolution + self.resolution/2.0 #Change: add half bin in the z
binietoglou@0 234 self.points = channel_file.number_of_bins
binietoglou@0 235 self.rc = []
ulalume3@27 236 self.duration = channel_file.duration
binietoglou@0 237
binietoglou@0 238 def append(self, other):
binietoglou@0 239 if self.info != other.info:
binietoglou@0 240 raise ValueError('Channel info are different. Data can not be combined.')
binietoglou@0 241
binietoglou@0 242 self.data = np.vstack([self.data, other.data])
binietoglou@0 243
binietoglou@0 244 def __unicode__(self):
ioannis@26 245 return "<Licel channel: %s>" % self.name
binietoglou@0 246
binietoglou@0 247 def __str__(self):
binietoglou@0 248 return unicode(self).encode('utf-8')
binietoglou@0 249
ioannis@22 250
binietoglou@0 251 class Licel2009LidarMeasurement(LicelLidarMeasurement):
binietoglou@0 252 extra_netcdf_parameters = musa_2009_netcdf_parameters
binietoglou@0 253
ioannis@22 254
binietoglou@0 255 def match_lines(f1,f2):
binietoglou@0 256 list1 = f1.split()
binietoglou@0 257 list2 = f2.split()
binietoglou@0 258
binietoglou@0 259 if len(list1) != len(list2):
binietoglou@0 260 print "Warning: Combining lists of different lengths."
binietoglou@0 261 print "List 1: %s" % list1
binietoglou@0 262 print "List 2: %s" % list2
binietoglou@0 263 combined = zip(list2,list1)
binietoglou@0 264 combined = dict(combined)
binietoglou@0 265 return combined
binietoglou@0 266

mercurial