| |
1 import numpy as np |
| |
2 import datetime |
| |
3 from generic import BaseLidarMeasurement, Lidar_channel |
| |
4 import musa_netcdf_parameters |
| |
5 import musa_2009_netcdf_parameters |
| |
6 |
| |
7 licel_file_header_format = ['Filename', |
| |
8 'Site StartDate StartTime EndDate EndTime Altitude Longtitude Latitude ZenithAngle', |
| |
9 'LS1 Rate1 LS2 Rate2 DataSets', ] |
| |
10 licel_file_channel_format = 'Active AnalogPhoton LaserUsed DataPoints 1 HV BinW Wavelength d1 d2 d3 d4 ADCbits NShots Discriminator ID' |
| |
11 |
| |
12 class LicelFile: |
| |
13 def __init__(self, filename): |
| |
14 self.start_time = None |
| |
15 self.stop_time = None |
| |
16 self.import_file(filename) |
| |
17 self.calculate_physical() |
| |
18 self.filename = filename |
| |
19 |
| |
20 def calculate_physical(self): |
| |
21 for channel in self.channels.itervalues(): |
| |
22 channel.calculate_physical() |
| |
23 |
| |
24 def import_file(self, filename): |
| |
25 """Imports a licel file. |
| |
26 Input: filename |
| |
27 Output: object """ |
| |
28 |
| |
29 raw_info = {} |
| |
30 channels = {} |
| |
31 channel_info = [] |
| |
32 |
| |
33 f = open(filename, 'r') |
| |
34 |
| |
35 #Read the first 3 lines of the header |
| |
36 raw_info = {} |
| |
37 for c1 in range(3): |
| |
38 raw_info.update(match_lines(f.readline(), licel_file_header_format[c1])) |
| |
39 |
| |
40 start_string = '%s %s' % (raw_info['StartDate'], raw_info['StartTime']) |
| |
41 stop_string = '%s %s' % (raw_info['EndDate'], raw_info['EndTime']) |
| |
42 date_format = '%d/%m/%Y %H:%M:%S' |
| |
43 self.start_time = datetime.datetime.strptime(start_string, date_format) |
| |
44 self.stop_time = datetime.datetime.strptime(stop_string, date_format) |
| |
45 self.latitude = float(raw_info['Latitude']) |
| |
46 self.longitude = float(raw_info['Longtitude']) |
| |
47 |
| |
48 # Read the rest of the header. |
| |
49 for c1 in range(int(raw_info['DataSets'])): |
| |
50 channel_info.append(match_lines(f.readline(), licel_file_channel_format)) |
| |
51 |
| |
52 # Check the complete header is read |
| |
53 a = f.readline() |
| |
54 |
| |
55 # Import the data |
| |
56 for current_channel_info in channel_info: |
| |
57 raw_data = np.fromfile(f, 'l', int(current_channel_info['DataPoints'])) |
| |
58 a = np.fromfile(f, 'b', 1) |
| |
59 b = np.fromfile(f, 'b', 1) |
| |
60 if (a[0] != 13) | (b[0] != 10): |
| |
61 print "Warning: No end of line found after record. File could be corrupt" |
| |
62 channel = LicelFileChannel(current_channel_info, raw_data) |
| |
63 |
| |
64 channel_name = channel.channel_name |
| |
65 if channel_name in channels.keys(): |
| |
66 # If the analog/photon naming scheme is not enough, find a new one! |
| |
67 raise IOError('Trying to import two channels with the same name') |
| |
68 |
| |
69 channels[channel_name] = channel |
| |
70 f.close() |
| |
71 |
| |
72 self.raw_info = raw_info |
| |
73 self.channels = channels |
| |
74 |
| |
75 |
| |
76 |
| |
77 class LicelFileChannel: |
| |
78 |
| |
79 def __init__(self, raw_info = None, raw_data = None): |
| |
80 self.raw_info = raw_info |
| |
81 self.raw_data = raw_data |
| |
82 |
| |
83 @property |
| |
84 def wavelength(self): |
| |
85 if self.raw_info is not None: |
| |
86 wave_str = self.raw_info['Wavelength'] |
| |
87 wavelength = wave_str.split('.')[0] |
| |
88 return int(wavelength) |
| |
89 else: |
| |
90 return None |
| |
91 |
| |
92 @property |
| |
93 def channel_name(self): |
| |
94 ''' |
| |
95 Construct the channel name adding analog photon info to avoid duplicates |
| |
96 ''' |
| |
97 acquisition_type = self.analog_photon_string(self.raw_info['AnalogPhoton']) |
| |
98 channel_name = "%s_%s" % (self.raw_info['Wavelength'], acquisition_type) |
| |
99 return channel_name |
| |
100 |
| |
101 def analog_photon_string(self, analog_photon_number): |
| |
102 if analog_photon_number == '0': |
| |
103 string = 'an' |
| |
104 else: |
| |
105 string = 'ph' |
| |
106 return string |
| |
107 |
| |
108 def calculate_physical(self): |
| |
109 data = self.raw_data |
| |
110 |
| |
111 number_of_shots = float(self.raw_info['NShots']) |
| |
112 norm = data/number_of_shots |
| |
113 dz = float(self.raw_info['BinW']) |
| |
114 |
| |
115 if self.raw_info['AnalogPhoton']=='0': |
| |
116 # If the channel is in analog mode |
| |
117 ADCb = int(self.raw_info['ADCbits']) |
| |
118 ADCrange = float(self.raw_info['Discriminator'])*1000 # Value in mV |
| |
119 channel_data = norm*ADCrange/((2**ADCb)-1) |
| |
120 |
| |
121 # print ADCb, ADCRange,cdata,norm |
| |
122 else: |
| |
123 # If the channel is in photoncounting mode |
| |
124 # Frequency deduced from range resolution! (is this ok?) |
| |
125 # c = 300 # The result will be in MHZ |
| |
126 # SR = c/(2*dz) # To account for pulse folding |
| |
127 # channel_data = norm*SR |
| |
128 # CHANGE: |
| |
129 # For the SCC the data are needed in photons |
| |
130 channel_data = norm*number_of_shots |
| |
131 #print res,c,cdata,norm |
| |
132 |
| |
133 #Calculate Z |
| |
134 number_of_bins = int(self.raw_info['DataPoints']) |
| |
135 self.z = np.array([dz*bin_number + dz/2.0 for bin_number in range(number_of_bins)]) |
| |
136 self.dz = dz |
| |
137 self.number_of_bins = number_of_bins |
| |
138 self.data = channel_data |
| |
139 |
| |
140 class LicelLidarMeasurement(BaseLidarMeasurement): |
| |
141 ''' |
| |
142 |
| |
143 ''' |
| |
144 |
| |
145 extra_netcdf_parameters = musa_netcdf_parameters |
| |
146 |
| |
147 def import_file(self, filename): |
| |
148 if filename in self.files: |
| |
149 print "File has been imported already:" + filename |
| |
150 else: |
| |
151 current_file = LicelFile(filename) |
| |
152 for channel_name, channel in current_file.channels.items(): |
| |
153 if channel_name not in self.channels: |
| |
154 self.channels[channel_name] = Licel_channel(channel) |
| |
155 self.channels[channel_name].data[current_file.start_time] = channel.data |
| |
156 self.files.append(current_file.filename) |
| |
157 |
| |
158 def append(self, other): |
| |
159 |
| |
160 self.start_times.extend(other.start_times) |
| |
161 self.stop_times.extend(other.stop_times) |
| |
162 |
| |
163 for channel_name, channel in self.channels.items(): |
| |
164 channel.append(other.channels[channel_name]) |
| |
165 |
| |
166 |
| |
167 class Licel_channel(Lidar_channel): |
| |
168 |
| |
169 def __init__(self, channel_file): |
| |
170 c = 299792458.0 #Speed of light |
| |
171 self.wavelength = channel_file.wavelength |
| |
172 self.name = channel_file.channel_name |
| |
173 self.binwidth = channel_file.dz * 2 / c # in microseconds |
| |
174 self.data = {} |
| |
175 self.resolution = channel_file.dz |
| |
176 self.z = np.arange(channel_file.number_of_bins) *self.resolution + self.resolution/2.0 #Change: add half bin in the z |
| |
177 self.points = channel_file.number_of_bins |
| |
178 self.rc = [] |
| |
179 self.duration = 60 |
| |
180 |
| |
181 def append(self, other): |
| |
182 if self.info != other.info: |
| |
183 raise ValueError('Channel info are different. Data can not be combined.') |
| |
184 |
| |
185 self.data = np.vstack([self.data, other.data]) |
| |
186 |
| |
187 def __unicode__(self): |
| |
188 return "<Licel channel: %s>" % self.info['Wavelength'] |
| |
189 |
| |
190 def __str__(self): |
| |
191 return unicode(self).encode('utf-8') |
| |
192 |
| |
193 class Licel2009LidarMeasurement(LicelLidarMeasurement): |
| |
194 extra_netcdf_parameters = musa_2009_netcdf_parameters |
| |
195 |
| |
196 def match_lines(f1,f2): |
| |
197 list1 = f1.split() |
| |
198 list2 = f2.split() |
| |
199 |
| |
200 if len(list1) != len(list2): |
| |
201 print "Warning: Combining lists of different lengths." |
| |
202 print "List 1: %s" % list1 |
| |
203 print "List 2: %s" % list2 |
| |
204 combined = zip(list2,list1) |
| |
205 combined = dict(combined) |
| |
206 return combined |
| |
207 |