atmospheric_lidar/licel_depol.py

Wed, 28 Nov 2018 22:27:28 +0200

author
Iannis <ioannis@inoe.ro>
date
Wed, 28 Nov 2018 22:27:28 +0200
changeset 168
9fed2446a59f
parent 164
e4915d84dd7d
child 170
9face926ccab
permissions
-rw-r--r--

First attempt for python 3 compatibility.

i@103 1 import logging
i@103 2
binietoglou@38 3 import numpy as np
binietoglou@38 4
ioannis@168 5 from .licel import LicelLidarMeasurement
binietoglou@38 6
i@103 7 logger = logging.getLogger(__name__)
i@103 8
binietoglou@38 9
binietoglou@38 10 class LicelCalibrationMeasurement(LicelLidarMeasurement):
binietoglou@38 11
i@164 12 def __init__(self, plus45_files=None, minus45_files=None, use_id_as_name=False, licel_timezone='UTC'):
i@164 13 """Class to handle depolarization calibration measurements according to the SCC.
i@164 14
i@164 15
i@164 16 Parameters
i@164 17 ----------
i@164 18 plus45_files : list of str
i@164 19 List of paths for the plus 45 files.
i@164 20 minus45_files : list of str
i@164 21 List of paths for the minus 45 files.
i@164 22 use_id_as_name : bool
i@164 23 Defines if channels names are descriptive or transient digitizer IDs.
i@164 24 licel_timezone : str
i@164 25 String describing the timezone according to the tz database.
i@164 26 """
binietoglou@38 27 # Setup the empty class
i@101 28 super(LicelCalibrationMeasurement, self).__init__(use_id_as_name=use_id_as_name, licel_timezone=licel_timezone)
binietoglou@38 29
binietoglou@38 30 self.plus45_files = plus45_files
binietoglou@38 31 self.minus45_files = minus45_files
binietoglou@38 32
ulalume3@69 33 if plus45_files and minus45_files:
i@160 34 self.files = plus45_files + minus45_files
i@160 35
binietoglou@38 36 self.check_equal_length()
binietoglou@38 37 self.read_channel_data()
binietoglou@38 38 self.update()
binietoglou@38 39
i@160 40 def subset_by_scc_channels(self):
i@160 41 m = super(LicelCalibrationMeasurement, self).subset_by_scc_channels()
i@160 42 m.plus45_measurement = self.plus45_measurement.subset_by_scc_channels()
i@160 43 m.minus45_measurement = self.minus45_measurement.subset_by_scc_channels()
i@160 44 return m
i@160 45
binietoglou@38 46 def update(self):
i@164 47 """
i@164 48 Correct timescales after each update.
binietoglou@38 49 """
binietoglou@38 50 super(LicelCalibrationMeasurement, self).update()
binietoglou@38 51 self.correct_timescales()
binietoglou@38 52
binietoglou@38 53 def check_equal_length(self):
binietoglou@38 54 """
binietoglou@38 55 Check if input time series have equal lengths.
binietoglou@38 56 """
binietoglou@38 57 len_plus = len(self.plus45_files)
binietoglou@38 58 len_minus = len(self.minus45_files)
binietoglou@38 59 if len_plus != len_minus:
binietoglou@38 60 raise self.UnequalMeasurementLengthError(
binietoglou@38 61 "Input timeseries have different length: %s vs %s." % (len_plus, len_minus))
binietoglou@38 62
binietoglou@38 63 def read_channel_data(self):
binietoglou@38 64 # Read plus and minus 45 measurements
i@101 65 self.plus45_measurement = LicelLidarMeasurement(self.plus45_files, self.use_id_as_name, self.licel_timezone)
i@160 66 self.plus45_measurement.extra_netcdf_parameters = self.extra_netcdf_parameters
ulalume3@92 67 self.plus45_measurement.rename_channels(suffix='_p45')
binietoglou@38 68
i@101 69 self.minus45_measurement = LicelLidarMeasurement(self.minus45_files, self.use_id_as_name, self.licel_timezone)
i@160 70 self.minus45_measurement.extra_netcdf_parameters = self.extra_netcdf_parameters
ulalume3@92 71 self.minus45_measurement.rename_channels(suffix='_m45')
binietoglou@38 72
binietoglou@38 73 # Combine them in this object
binietoglou@38 74 self.channels = {}
binietoglou@38 75 self.channels.update(self.plus45_measurement.channels)
binietoglou@38 76 self.channels.update(self.minus45_measurement.channels)
binietoglou@38 77
i@160 78 self.raw_info = self.plus45_measurement.raw_info.copy()
i@160 79 self.raw_info.update(self.minus45_measurement.raw_info)
i@160 80
binietoglou@38 81 def correct_timescales(self):
binietoglou@38 82 self.check_timescales_are_two()
binietoglou@38 83 self.combine_scales()
binietoglou@38 84
binietoglou@38 85 def check_timescales_are_two(self):
binietoglou@38 86 no_timescales = len(self.variables['Raw_Data_Start_Time'])
binietoglou@38 87 if no_timescales != 2:
binietoglou@38 88 raise self.WrongNumberOfTimescalesError("Wrong number of timescales: %s instead of 2." % no_timescales)
binietoglou@38 89
binietoglou@38 90 def combine_scales(self):
binietoglou@38 91 start_times, end_times = self.get_ordered_timescales()
binietoglou@38 92 new_start_time = start_times[0]
binietoglou@38 93 new_stop_time = end_times[1]
binietoglou@38 94 self.variables['Raw_Data_Start_Time'] = [new_start_time, ]
binietoglou@38 95 self.variables['Raw_Data_Stop_Time'] = [new_stop_time, ]
binietoglou@38 96 self.reset_timescale_id()
binietoglou@38 97
i@160 98 # def _get_custom_global_attributes(self):
i@160 99 # """
i@160 100 # NetCDF global attributes that should be included in the final NetCDF file.
i@160 101 #
i@160 102 # Using the values of just p45 measurements.
i@160 103 # """
i@160 104 # return self.plus45_measurement._get_custom_global_attributes()
i@160 105
binietoglou@38 106 def reset_timescale_id(self):
binietoglou@38 107 """
binietoglou@38 108 Set all timescales to 0
binietoglou@38 109 :return:
binietoglou@38 110 """
binietoglou@38 111 timescale_dict = self.variables['id_timescale']
binietoglou@38 112 self.variables['id_timescale'] = dict.fromkeys(timescale_dict, 0)
binietoglou@38 113
binietoglou@38 114 def get_ordered_timescales(self):
binietoglou@38 115 scale_start_1, scale_start_2 = self.variables['Raw_Data_Start_Time']
binietoglou@38 116 scale_end_1, scale_end_2 = self.variables['Raw_Data_Stop_Time']
binietoglou@38 117
binietoglou@38 118 if scale_start_1[0] > scale_start_2[0]:
binietoglou@38 119 scale_start_1, scale_start_2 = scale_start_2, scale_start_1
binietoglou@38 120
binietoglou@38 121 if scale_end_1[0] > scale_end_2[0]:
binietoglou@38 122 scale_end_1, scale_end_2 = scale_end_2, scale_end_1
binietoglou@38 123
binietoglou@38 124 return (scale_start_1, scale_start_2), (scale_end_1, scale_end_2)
binietoglou@38 125
binietoglou@38 126 def add_fake_measurements(self, no_profiles, variation=0.1):
binietoglou@38 127 """
binietoglou@38 128 Add a number of fake measurements. This is done to allow testing with single analog profiles.
binietoglou@38 129
binietoglou@38 130 Adds a predefined variation in each new profile.
binietoglou@38 131 """
binietoglou@38 132 duration = self.info['duration']
binietoglou@38 133 for channel_name, channel in self.channels.items():
ioannis@168 134 base_time = list(channel.data.keys())[0]
binietoglou@38 135 base_data = channel.data[base_time]
binietoglou@38 136
binietoglou@38 137 for n in range(no_profiles):
binietoglou@38 138 random_variation = base_data * (np.random.rand(len(base_data)) * 2 - 1) * variation
binietoglou@38 139
binietoglou@38 140 new_time = base_time + n * duration
binietoglou@38 141 new_data = channel.data[base_time].copy() + random_variation
binietoglou@38 142 if 'ph' in channel_name:
binietoglou@38 143 new_data = new_data.astype('int')
binietoglou@38 144 channel.data[new_time] = new_data
binietoglou@38 145
binietoglou@38 146 self.update()
binietoglou@38 147
binietoglou@38 148 def subset_photoncounting(self):
binietoglou@38 149 """
binietoglou@38 150 Subset photoncounting channels.
binietoglou@38 151 """
binietoglou@38 152 ph_channels = [channel for channel in self.channels.keys() if 'ph' in channel]
binietoglou@38 153 new_measurement = self.subset_by_channels(ph_channels)
binietoglou@38 154 return new_measurement
binietoglou@38 155
i@116 156 def _get_scc_channel_variables(self):
binietoglou@43 157 """
binietoglou@43 158 Get a list of variables to put in the SCC.
binietoglou@43 159
binietoglou@43 160 It can be overridden e.g. in the depolarization product class.
binietoglou@43 161
binietoglou@43 162 Returns
binietoglou@43 163 -------
binietoglou@43 164
binietoglou@43 165 channel_variables: dict
binietoglou@43 166 A dictionary with channel variable specifications.
binietoglou@43 167 """
binietoglou@43 168 channel_variables = \
ulalume3@71 169 {'Background_Low': (('channels',), 'd'),
binietoglou@43 170 'Background_High': (('channels',), 'd'),
binietoglou@43 171 'LR_Input': (('channels',), 'i'),
binietoglou@43 172 'DAQ_Range': (('channels',), 'd'),
binietoglou@43 173 'Pol_Calib_Range_Min': (('channels',), 'd'),
binietoglou@43 174 'Pol_Calib_Range_Max': (('channels',), 'd'),
binietoglou@43 175 }
binietoglou@43 176 return channel_variables
binietoglou@43 177
binietoglou@38 178 class UnequalMeasurementLengthError(RuntimeError):
binietoglou@38 179 """ Raised when the plus and minus files have different length.
binietoglou@38 180 """
binietoglou@38 181 pass
binietoglou@38 182
binietoglou@38 183 class WrongNumberOfTimescalesError(RuntimeError):
binietoglou@38 184 """ Raised when timescales are not two.
binietoglou@38 185 """
binietoglou@38 186 pass
i@164 187
i@164 188
i@164 189 class DarkLicelCalibrationMeasurement(LicelCalibrationMeasurement):
i@164 190
i@164 191 def __init__(self, dark_files=None, use_id_as_name=False, licel_timezone='UTC'):
i@164 192 """Class to handle dark files for depolarization calibration measurements according to the SCC.
i@164 193
i@164 194 It assumes that a single sent of dark measurements will be use for both plus and minus 45 channels.
i@164 195
i@164 196 Parameters
i@164 197 ----------
i@164 198 dark_files : list of str
i@164 199 List of paths for the dark measurement files.
i@164 200 use_id_as_name : bool
i@164 201 Defines if channels names are descriptive or transient digitizer IDs.
i@164 202 licel_timezone : str
i@164 203 String describing the timezone according to the tz database.
i@164 204 """
i@164 205 # Setup the empty class
i@164 206 super(DarkLicelCalibrationMeasurement, self).__init__(dark_files, dark_files,
i@164 207 use_id_as_name=use_id_as_name,
i@164 208 licel_timezone=licel_timezone)
i@164 209
i@164 210 def correct_timescales(self):
i@164 211 """ For dark measuremetns, no need to correct timescales. """
i@164 212 pass

mercurial