atmospheric_lidar/licel_depol.py

Fri, 09 Feb 2018 13:23:06 +0200

author
Iannis <i.binietoglou@impworks.gr>
date
Fri, 09 Feb 2018 13:23:06 +0200
changeset 122
1456119a46b5
parent 116
d9703af687aa
child 160
db28629317fa
permissions
-rw-r--r--

* Added PI name and email in DIVA global attributes
* Fixed some bugs and inconsistencies in the NetCDF file structure.

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

mercurial