Mon, 26 Feb 2018 18:08:00 +0200
Updates to raymetrics reading routines.
--- a/atmospheric_lidar/generic.py Fri Feb 23 14:45:36 2018 +0200 +++ b/atmospheric_lidar/generic.py Mon Feb 26 18:08:00 2018 +0200 @@ -651,7 +651,7 @@ return [] -class LidarChannel: +class LidarChannel(object): """ This class represents a general measurement channel, independent of the input files.
--- a/atmospheric_lidar/licel.py Fri Feb 23 14:45:36 2018 +0200 +++ b/atmospheric_lidar/licel.py Mon Feb 26 18:08:00 2018 +0200 @@ -36,21 +36,21 @@ self.duration = duration self.use_id_as_name = use_id_as_name self.adcbits = int(raw_info['ADCbits']) - self.active = int(raw_info['Active']) - self.analog_photon = raw_info['AnalogPhoton'] - self.bin_width = float(raw_info['BinW']) - self.data_points = int(raw_info['DataPoints']) + self.active = int(raw_info['active']) + self.analog_photon = raw_info['analog_photon'] + self.bin_width = float(raw_info['bin_width']) + self.data_points = int(raw_info['number_of_datapoints']) self.hv = float(raw_info['HV']) self.id = raw_info['ID'] - self.laser_user = int(raw_info['LaserUsed']) - self.number_of_shots = int(raw_info['NShots']) - self.wavelength_str = raw_info['Wavelength'] + self.laser_used = int(raw_info['laser_used']) + self.number_of_shots = int(raw_info['number_of_shots']) + self.wavelength_str = raw_info['wavelength'] if self.is_analog: - self.discriminator = float(raw_info['Discriminator']) * 1000 # Analog range in mV + self.discriminator = float(raw_info['discriminator']) * 1000 # Analog range in mV else: - self.discriminator = float(raw_info['Discriminator']) + self.discriminator = float(raw_info['discriminator']) @property def wavelength(self): @@ -112,9 +112,9 @@ data = self.raw_data norm = data / float(self.number_of_shots) - dz = float(self.raw_info['BinW']) + dz = self.bin_width - if self.raw_info['AnalogPhoton'] == '0': + if self.is_analog: # If the channel is in analog mode ADCrange = self.discriminator # Discriminator value already in mV channel_data = norm * ADCrange / ((2 ** self.adcbits) - 1) # TODO: Check this. Agrees with Licel docs, but differs from their LabView code. @@ -142,14 +142,14 @@ return self.analog_photon == '0' -class LicelFile: +class LicelFile(object): """ A class representing a single binary Licel file. """ - licel_file_header_format = ['Filename', - 'StartDate StartTime EndDate EndTime Altitude Longtitude Latitude ZenithAngle', + licel_file_header_format = ['filename', + 'start_date start_time end_date end_time altitude longitude latitude zenith_angle', # Appart from Site that is read manually - 'LS1 Rate1 LS2 Rate2 DataSets', ] - licel_file_channel_format = 'Active AnalogPhoton LaserUsed DataPoints 1 HV BinW Wavelength d1 d2 d3 d4 ADCbits NShots Discriminator ID' + 'LS1 rate_1 LS2 rate_2 number_of_datasets', ] + licel_file_channel_format = 'active analog_photon laser_used number_of_datapoints 1 HV bin_width wavelength d1 d2 d3 d4 ADCbits number_of_shots discriminator ID' file_channel_class = LicelFileChannel @@ -182,7 +182,7 @@ channel.calculate_physical() def _import_file(self, file_path): - """ Read the content of the Licel file. + """ Read the header info and data of the Licel file. Parameters ---------- @@ -200,7 +200,7 @@ # Import the data for current_channel_info in self.channel_info: - raw_data = np.fromfile(f, 'i4', int(current_channel_info['DataPoints'])) + raw_data = np.fromfile(f, 'i4', int(current_channel_info['number_of_datapoints'])) a = np.fromfile(f, 'b', 1) b = np.fromfile(f, 'b', 1) @@ -239,8 +239,8 @@ raw_info.update(self._read_rest_of_header(f)) # Update the object properties based on the raw info - start_string = '%s %s' % (raw_info['StartDate'], raw_info['StartTime']) - stop_string = '%s %s' % (raw_info['EndDate'], raw_info['EndTime']) + start_string = '%s %s' % (raw_info['start_date'], raw_info['start_time']) + stop_string = '%s %s' % (raw_info['end_date'], raw_info['end_time']) date_format = '%d/%m/%Y %H:%M:%S' try: @@ -257,16 +257,26 @@ self.start_time = local_start_time.astimezone(pytz.utc) self.stop_time = local_stop_time.astimezone(pytz.utc) - self.latitude = float(raw_info['Latitude']) - self.longitude = float(raw_info['Longtitude']) + self.latitude = float(raw_info['latitude']) + self.longitude = float(raw_info['longitude']) # Read the rest of the header. - for c1 in range(int(raw_info['DataSets'])): + for c1 in range(int(raw_info['number_of_datasets'])): channel_info.append(self.match_lines(f.readline(), self.licel_file_channel_format)) self.raw_info = raw_info self.channel_info = channel_info + self._assign_properties() + + def _assign_properties(self): + """ Assign properties from the raw_info dictionary. """ + self.number_of_datasets = int(self.raw_info['number_of_datasets']) + self.altitude = float(self.raw_info['altitude']) + self.longitude = float(self.raw_info['longitude']) + self.latitude = float(self.raw_info['latitude']) + self.zenith_angle = float(self.raw_info['zenith_angle']) + def _read_second_header_line(self, f): """ Read the second line of a licel file. """ raw_info = {} @@ -280,7 +290,7 @@ site_name = second_line.split('/')[0][:-2] clean_site_name = site_name.strip() - raw_info['Site'] = clean_site_name + raw_info['site'] = clean_site_name raw_info.update(self.match_lines(second_line[len(clean_site_name) + 1:], self.licel_file_header_format[1])) return raw_info @@ -309,9 +319,10 @@ list2 = f2.split() if len(list1) != len(list2): - logging.debug("Channel parameter list has different length from licel specifications.") + logging.debug("Channel parameter list has different length from LICEL specifications.") logging.debug("List 1: %s" % list1) logging.debug("List 2: %s" % list2) + combined = zip(list2, list1) combined = dict(combined) return combined @@ -358,8 +369,7 @@ self._assign_unique_property('points', file_channel.data_points) self._assign_unique_property('adcbits', file_channel.adcbits) self._assign_unique_property('active', file_channel.active) - self._assign_unique_property('laser_user', file_channel.laser_user) - self._assign_unique_property('adcbints', file_channel.adcbits) + self._assign_unique_property('laser_used', file_channel.laser_used) self._assign_unique_property('analog_photon_string', file_channel.analog_photon_string) def _assign_unique_property(self, property_name, value):
--- a/atmospheric_lidar/raymetrics.py Fri Feb 23 14:45:36 2018 +0200 +++ b/atmospheric_lidar/raymetrics.py Mon Feb 26 18:08:00 2018 +0200 @@ -2,6 +2,7 @@ import datetime import logging +import numpy as np import pytz from .licel import LicelFile, LicelLidarMeasurement, LicelChannel @@ -31,12 +32,12 @@ class ScanningFile(LicelFile): - licel_file_header_format = ['Filename', - 'StartDate StartTime EndDate EndTime Altitude Longtitude Latitude ZenithAngle Temperature Pressure', # Appart from Site that is read manually - 'azimuth_start azimuth_finish azimuth_step zenith_start zenith_finish zenith_step azimuth_offset', - 'LS1 Rate1 LS2 Rate2 DataSets', ] - licel_file_channel_format = 'Active AnalogPhoton LaserUsed DataPoints 1 HV BinW Wavelength d1 d2 d3 d4 ADCbits NShots Discriminator ID' - + licel_file_header_format = ['filename', + 'start_date start_time end_date end_time altitude longitude latitude zenith_angle azimuth_angle temperature pressure', + # Appart from Site that is read manually + 'azimuth_start azimuth_stop azimuth_step zenith_start zenith_finish zenith_step azimuth_offset', + 'LS1 rate_1 LS2 rate_2 number_of_datasets', ] + licel_file_channel_format = 'active analog_photon laser_used number_of_datapoints 1 HV bin_width wavelength d1 d2 d3 d4 ADCbits number_of_shots discriminator ID' def _read_rest_of_header(self, f): """ Read the rest of the header lines, after line 2. """ @@ -49,6 +50,33 @@ raw_info.update(self.match_lines(fourth_line, self.licel_file_header_format[3])) return raw_info + def _assign_properties(self): + super(ScanningFile, self)._assign_properties() + self.azimuth_angle = float(self.raw_info['altitude']) + self.temperature = float(self.raw_info['temperature']) + self.pressure = float(self.raw_info['pressure']) + self.azimuth_start = float(self.raw_info['azimuth_start']) + self.azimuth_stop = float(self.raw_info['azimuth_stop']) + self.azimuth_step = float(self.raw_info['azimuth_step']) + self.zenith_start = float(self.raw_info['zenith_start']) + self.zenith_finish = float(self.raw_info['zenith_finish']) + self.zenith_step = float(self.raw_info['zenith_step']) + self.azimuth_offset = float(self.raw_info['azimuth_offset']) + + +class ScanningChannel(LicelChannel): + + def __init__(self): + self.azimuth_start = None + self.azimuth_stop = None + self.azimuth_step = None + self.zenith_start = None + self.zenith_finish = None + self.zenith_step = None + self.azimuth_offset = None + super(ScanningChannel, self).__init__() + + class ScanningLidarMeasurement(RaymetricsLidarMeasurement): file_class = ScanningFile @@ -64,6 +92,36 @@ self._assign_unique_property('wavelength', file_channel.wavelength) self._assign_unique_property('adcbits', file_channel.adcbits) self._assign_unique_property('active', file_channel.active) - self._assign_unique_property('laser_user', file_channel.laser_user) - self._assign_unique_property('adcbints', file_channel.adcbits) - self._assign_unique_property('analog_photon_string', file_channel.analog_photon_string) \ No newline at end of file + self._assign_unique_property('laser_used', file_channel.laser_used) + self._assign_unique_property('adcbits', file_channel.adcbits) + self._assign_unique_property('analog_photon_string', file_channel.analog_photon_string) + + # def calculate_physical(self): + # """ Calculate physically-meaningful data from photodiode channels: + # + # * In case of analog signals, the data are converted to mV. + # * In case of photon counting signals, data are stored as number of photons. + # + # In addition, some ancillary variables are also calculated (z, dz, number_of_bins). + # """ + # data = self.raw_data + # + # norm = data / float(self.number_of_shots) + # dz = self.bin_width + # + # if self.is_analog: + # # If the channel is in analog mode + # ADCrange = self.discriminator # Discriminator value already in mV + # if self.adcbits == 0: + # logger.warning("Changing adcbits to 1. This is a bug in current licel format.") + # channel_data = norm * ADCrange / ((2 ** self.adcbits) ) + # else: + # channel_data = norm * ADCrange / ((2 ** self.adcbits) - 1) + # + # else: + # channel_data = norm * self.number_of_shots + # + # # Calculate Z + # self.z = np.array([dz * bin_number + dz / 2.0 for bin_number in range(self.data_points)]) + # self.dz = dz + # self.data = channel_data \ No newline at end of file