atmospheric_lidar/licel_depol.py

Fri, 22 Sep 2017 15:02:53 +0300

author
Victor Nicolae <victor.nicolae@inoe.ro>
date
Fri, 22 Sep 2017 15:02:53 +0300
changeset 76
e59cdc4fd4c0
parent 71
695e9c21a966
child 92
6d26002aaeed
permissions
-rw-r--r--

Include all parameters from the NetCDF parameters file in the final output file.

Better inform the user if any mandatory parameter misses from the file.

In case of extra (optional) parameters, allow missing values in the output file.

binietoglou@38 1 import datetime
binietoglou@38 2
binietoglou@38 3 import numpy as np
binietoglou@38 4
binietoglou@38 5 from licel import LicelLidarMeasurement
binietoglou@38 6
binietoglou@38 7
binietoglou@38 8 class LicelCalibrationMeasurement(LicelLidarMeasurement):
binietoglou@38 9
ulalume3@69 10 def __init__(self, plus45_files=None, minus45_files=None, use_id_as_name=False):
binietoglou@38 11 # Setup the empty class
ulalume3@69 12 super(LicelCalibrationMeasurement, self).__init__(use_id_as_name=use_id_as_name)
binietoglou@38 13
binietoglou@38 14 self.plus45_files = plus45_files
binietoglou@38 15 self.minus45_files = minus45_files
binietoglou@38 16
ulalume3@69 17 if plus45_files and minus45_files:
binietoglou@38 18 self.check_equal_length()
binietoglou@38 19
binietoglou@38 20 self.read_channel_data()
binietoglou@38 21 self.update()
binietoglou@38 22
binietoglou@38 23 def update(self):
binietoglou@38 24 """ Correct timescales after each update.
binietoglou@38 25 """
binietoglou@38 26 super(LicelCalibrationMeasurement, self).update()
binietoglou@38 27 self.correct_timescales()
binietoglou@38 28
binietoglou@38 29 def check_equal_length(self):
binietoglou@38 30 """
binietoglou@38 31 Check if input time series have equal lengths.
binietoglou@38 32 """
binietoglou@38 33 len_plus = len(self.plus45_files)
binietoglou@38 34 len_minus = len(self.minus45_files)
binietoglou@38 35 if len_plus != len_minus:
binietoglou@38 36 raise self.UnequalMeasurementLengthError(
binietoglou@38 37 "Input timeseries have different length: %s vs %s." % (len_plus, len_minus))
binietoglou@38 38
binietoglou@38 39 def read_channel_data(self):
binietoglou@38 40
binietoglou@38 41 # Read plus and minus 45 measurements
ulalume3@70 42 self.plus45_measurement = LicelLidarMeasurement(self.plus45_files, self.use_id_as_name)
binietoglou@38 43 self.plus45_measurement.rename_channel(suffix='_p45')
binietoglou@38 44
ulalume3@70 45 self.minus45_measurement = LicelLidarMeasurement(self.minus45_files, self.use_id_as_name)
binietoglou@38 46 self.minus45_measurement.rename_channel(suffix='_m45')
binietoglou@38 47
binietoglou@38 48 # Combine them in this object
binietoglou@38 49 self.channels = {}
binietoglou@38 50 self.channels.update(self.plus45_measurement.channels)
binietoglou@38 51 self.channels.update(self.minus45_measurement.channels)
binietoglou@38 52
binietoglou@38 53 def correct_timescales(self):
binietoglou@38 54 self.check_timescales_are_two()
binietoglou@38 55 self.combine_scales()
binietoglou@38 56
binietoglou@38 57 def check_timescales_are_two(self):
binietoglou@38 58 no_timescales = len(self.variables['Raw_Data_Start_Time'])
binietoglou@38 59 if no_timescales != 2:
binietoglou@38 60 raise self.WrongNumberOfTimescalesError("Wrong number of timescales: %s instead of 2." % no_timescales)
binietoglou@38 61
binietoglou@38 62 def combine_scales(self):
binietoglou@38 63 start_times, end_times = self.get_ordered_timescales()
binietoglou@38 64 new_start_time = start_times[0]
binietoglou@38 65 new_stop_time = end_times[1]
binietoglou@38 66 self.variables['Raw_Data_Start_Time'] = [new_start_time, ]
binietoglou@38 67 self.variables['Raw_Data_Stop_Time'] = [new_stop_time, ]
binietoglou@38 68 self.reset_timescale_id()
binietoglou@38 69
binietoglou@38 70 def reset_timescale_id(self):
binietoglou@38 71 """
binietoglou@38 72 Set all timescales to 0
binietoglou@38 73 :return:
binietoglou@38 74 """
binietoglou@38 75 timescale_dict = self.variables['id_timescale']
binietoglou@38 76 self.variables['id_timescale'] = dict.fromkeys(timescale_dict, 0)
binietoglou@38 77
binietoglou@38 78 def get_ordered_timescales(self):
binietoglou@38 79 scale_start_1, scale_start_2 = self.variables['Raw_Data_Start_Time']
binietoglou@38 80 scale_end_1, scale_end_2 = self.variables['Raw_Data_Stop_Time']
binietoglou@38 81
binietoglou@38 82 if scale_start_1[0] > scale_start_2[0]:
binietoglou@38 83 scale_start_1, scale_start_2 = scale_start_2, scale_start_1
binietoglou@38 84
binietoglou@38 85 if scale_end_1[0] > scale_end_2[0]:
binietoglou@38 86 scale_end_1, scale_end_2 = scale_end_2, scale_end_1
binietoglou@38 87
binietoglou@38 88 return (scale_start_1, scale_start_2), (scale_end_1, scale_end_2)
binietoglou@38 89
binietoglou@38 90 def add_fake_measurements(self, no_profiles, variation=0.1):
binietoglou@38 91 """
binietoglou@38 92 Add a number of fake measurements. This is done to allow testing with single analog profiles.
binietoglou@38 93
binietoglou@38 94 Adds a predefined variation in each new profile.
binietoglou@38 95 """
binietoglou@38 96 duration = self.info['duration']
binietoglou@38 97 for channel_name, channel in self.channels.items():
binietoglou@38 98 base_time = channel.data.keys()[0]
binietoglou@38 99 base_data = channel.data[base_time]
binietoglou@38 100
binietoglou@38 101 for n in range(no_profiles):
binietoglou@38 102 random_variation = base_data * (np.random.rand(len(base_data)) * 2 - 1) * variation
binietoglou@38 103
binietoglou@38 104 new_time = base_time + n * duration
binietoglou@38 105 new_data = channel.data[base_time].copy() + random_variation
binietoglou@38 106 if 'ph' in channel_name:
binietoglou@38 107 new_data = new_data.astype('int')
binietoglou@38 108 channel.data[new_time] = new_data
binietoglou@38 109
binietoglou@38 110 self.update()
binietoglou@38 111
binietoglou@38 112 def subset_photoncounting(self):
binietoglou@38 113 """
binietoglou@38 114 Subset photoncounting channels.
binietoglou@38 115 """
binietoglou@38 116 ph_channels = [channel for channel in self.channels.keys() if 'ph' in channel]
binietoglou@38 117 new_measurement = self.subset_by_channels(ph_channels)
binietoglou@38 118 return new_measurement
binietoglou@38 119
ulalume3@71 120 def _get_scc_mandatory_channel_variables(self):
binietoglou@43 121 """
binietoglou@43 122 Get a list of variables to put in the SCC.
binietoglou@43 123
binietoglou@43 124 It can be overridden e.g. in the depolarization product class.
binietoglou@43 125
binietoglou@43 126 Returns
binietoglou@43 127 -------
binietoglou@43 128
binietoglou@43 129 channel_variables: dict
binietoglou@43 130 A dictionary with channel variable specifications.
binietoglou@43 131 """
binietoglou@43 132 channel_variables = \
ulalume3@71 133 {'Background_Low': (('channels',), 'd'),
binietoglou@43 134 'Background_High': (('channels',), 'd'),
binietoglou@43 135 'LR_Input': (('channels',), 'i'),
binietoglou@43 136 'DAQ_Range': (('channels',), 'd'),
binietoglou@43 137 'Pol_Calib_Range_Min': (('channels',), 'd'),
binietoglou@43 138 'Pol_Calib_Range_Max': (('channels',), 'd'),
binietoglou@43 139 }
binietoglou@43 140 return channel_variables
binietoglou@43 141
binietoglou@38 142 class UnequalMeasurementLengthError(RuntimeError):
binietoglou@38 143 """ Raised when the plus and minus files have different length.
binietoglou@38 144 """
binietoglou@38 145 pass
binietoglou@38 146
binietoglou@38 147 class WrongNumberOfTimescalesError(RuntimeError):
binietoglou@38 148 """ Raised when timescales are not two.
binietoglou@38 149 """
binietoglou@38 150 pass

mercurial