Fri, 22 Sep 2017 15:02:53 +0300
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 |