Fri, 15 Dec 2017 13:33:10 +0200
* Ancillary variable group
* Example of analog and photon-counting channel parameters.
i@108 | 1 | """ This is a class for experimenting with the new DIVA / EARLINET NetCDF file format. |
i@108 | 2 | |
i@108 | 3 | In the long run, this should be places as a method in BaseLidarMeasurement class. For now it is kept |
i@108 | 4 | separately not to interfere with normal development. |
i@108 | 5 | """ |
i@108 | 6 | import netCDF4 as netcdf |
i@108 | 7 | import pyyaml |
i@108 | 8 | import datetime |
i@108 | 9 | import os |
i@108 | 10 | |
i@108 | 11 | from .generic import BaseLidarMeasurement |
i@108 | 12 | |
i@108 | 13 | |
i@108 | 14 | class DivaOutput(BaseLidarMeasurement): |
i@108 | 15 | |
i@108 | 16 | def save_as_diva(self, output_path, parameter_file): |
i@108 | 17 | """ Save the current data in the 'draft' DIVA format. """ |
i@108 | 18 | |
i@108 | 19 | with open(parameter_file, 'r') as f: |
i@108 | 20 | parameters = pyyaml.load(f) |
i@108 | 21 | |
i@108 | 22 | global_parameters = parameters['global_parameters'] # Shortcut |
ulalume3@112 | 23 | channels = parameters['channels'] |
i@108 | 24 | |
i@108 | 25 | iso_date = datetime.datetime.utcnow().isoformat() |
i@108 | 26 | python_file_name = os.path.basename(__file__) |
i@108 | 27 | |
i@108 | 28 | with netcdf.Dataset(output_path, 'w', format="NETCDF4") as f: |
i@108 | 29 | |
i@108 | 30 | # Global attributes |
i@108 | 31 | f.title = global_parameters['title'] |
i@108 | 32 | f.source = global_parameters['source'] |
i@108 | 33 | f.institution = global_parameters['institution'] |
i@108 | 34 | f.references = global_parameters['references'] |
i@108 | 35 | f.location = global_parameters['location'] |
i@108 | 36 | f.data_version = global_parameters['data_version'] |
i@108 | 37 | f.conversion_date = iso_date |
i@108 | 38 | f.comment = global_parameters['comment'] |
i@108 | 39 | f.Conventions = global_parameters['Conventions'] |
i@108 | 40 | f.history = global_parameters['history'].format(date=iso_date, file=python_file_name) |
i@108 | 41 | f.featureType = "timeSeriesProfile" |
i@108 | 42 | |
ulalume3@112 | 43 | # Top level dimensions |
ulalume3@112 | 44 | f.createDimension('name_strlen', size=40) |
ulalume3@112 | 45 | f.createDimension('nv', size=2) |
ulalume3@112 | 46 | |
i@108 | 47 | # Top level variables |
i@108 | 48 | latitude = f.createVariable('latitude', datatype='f4') |
i@108 | 49 | latitude.standard_name = 'latitude' |
i@108 | 50 | latitude.long_name = 'system latitude' |
i@108 | 51 | latitude.units = 'degrees_north' |
i@108 | 52 | |
i@108 | 53 | longitude = f.createVariable('longitude', datatype='f4') |
i@108 | 54 | longitude.standard_name = 'longitude' |
i@108 | 55 | longitude.long_name = 'system longitude' |
i@108 | 56 | longitude.units = 'degrees_east' |
i@108 | 57 | |
i@108 | 58 | laser_angle = f.createVariable('laser_zenith_angle', datatype='f4') |
i@108 | 59 | laser_angle.standard_name = 'sensor_zenith_angle' |
i@108 | 60 | laser_angle.long_name = 'zenith angle of emitted laser' |
i@108 | 61 | laser_angle.units = 'degree' |
i@108 | 62 | |
ulalume3@112 | 63 | laser_azimuth = f.createVariable('laser_azimuth_angle', datatype='f4') |
ulalume3@112 | 64 | laser_azimuth.standard_name = 'sensor_azimuth_angle' |
ulalume3@112 | 65 | laser_azimuth.long_name = 'azimuth angle of emitted laser' |
ulalume3@112 | 66 | laser_azimuth.units = 'degree' |
ulalume3@112 | 67 | laser_azimuth.comment = 'Based on North. Optional' |
i@108 | 68 | |
i@108 | 69 | altitude = f.createVariable('altitude', datatype='f4') |
i@108 | 70 | altitude.standard_name = 'altitude' |
i@108 | 71 | altitude.long_name = 'system altitude' |
i@108 | 72 | altitude.units = 'm' |
i@108 | 73 | |
ulalume3@113 | 74 | # Optional ancillary group |
ulalume3@113 | 75 | ancillary = f.createGroup('ancillary') |
ulalume3@113 | 76 | ancillary.featureType = "timeSeries" |
ulalume3@113 | 77 | |
ulalume3@113 | 78 | ancillary.createDimension('time', size=None) |
ulalume3@113 | 79 | |
ulalume3@113 | 80 | time = ancillary.createVariable('time', datatype='f8', dimensions=('time',)) |
ulalume3@113 | 81 | time.long_name = 'time' |
ulalume3@113 | 82 | time.units = 'seconds since 1970-01-01 00:00' |
ulalume3@113 | 83 | time.standard_name = 'time' |
ulalume3@113 | 84 | |
ulalume3@113 | 85 | temperature = ancillary.createVariable('air_temperature', datatype='f8', dimensions=('time',)) |
ulalume3@113 | 86 | temperature.long_name = 'air temperature at instrument level' |
ulalume3@113 | 87 | temperature.units = 'K' |
ulalume3@113 | 88 | temperature.standard_name = 'air_temperature' |
ulalume3@113 | 89 | |
ulalume3@113 | 90 | pressure = ancillary.createVariable('air_pressure', datatype='f8', dimensions=('time',)) |
ulalume3@113 | 91 | pressure.long_name = 'air pressure at instrument level' |
ulalume3@113 | 92 | pressure.units = 'hPa' |
ulalume3@113 | 93 | pressure.standard_name = 'air_pressure' |
ulalume3@113 | 94 | |
ulalume3@113 | 95 | # Create a separate group for each channel |
ulalume3@112 | 96 | for channel_name, channel_parameters in channels.iteritems(): |
i@108 | 97 | |
ulalume3@112 | 98 | group_name = "channel_{0}".format(channel_name) # Give channels groups a standard name |
ulalume3@112 | 99 | g = f.createGroup(group_name) |
ulalume3@112 | 100 | g.long_name = channel_parameters['long_name'] |
ulalume3@112 | 101 | g.detector_manufacturer = channel_parameters['long_name'] # Optional |
ulalume3@112 | 102 | g.detector_model = channel_parameters['detector_model'] |
ulalume3@112 | 103 | g.daq_manufacturer = channel_parameters['daq_manufacturer'] |
ulalume3@112 | 104 | g.daq_model = channel_parameters['daq_model'] |
i@108 | 105 | |
ulalume3@112 | 106 | # Dimensions |
ulalume3@112 | 107 | g.createDimension('profile', size=None) # Infinite dimension |
ulalume3@112 | 108 | g.createDimension('range', len(self.range)) |
i@108 | 109 | |
ulalume3@112 | 110 | # Variables |
ulalume3@112 | 111 | name = g.createVariable('channel_id', dimensions=('name_strlen',)) |
ulalume3@112 | 112 | name.cf_role = 'timeseries_id' |
ulalume3@112 | 113 | name.long_name = 'channel identification' |
i@108 | 114 | |
ulalume3@112 | 115 | laser_rep_rate = g.createVariable('laser_repetition_rate') |
ulalume3@112 | 116 | laser_rep_rate.long_name = 'nominal laser repetition rate' |
ulalume3@112 | 117 | laser_rep_rate.units = 'Hz' |
i@108 | 118 | |
ulalume3@112 | 119 | emission_energy = g.createVariable('emission_energy', datatype='f8', dimensions=('profile',)) |
ulalume3@112 | 120 | emission_energy.long_name = 'emission energy per pulse' |
ulalume3@112 | 121 | emission_energy.units = 'mJ' |
ulalume3@112 | 122 | emission_energy.standard_name = 'radiation_wavelength' |
ulalume3@112 | 123 | emission_energy.comment = "could be scalar, if value is nominal." |
i@108 | 124 | |
ulalume3@112 | 125 | emission_pol = g.createVariable('emission_polarization', datatype='b') |
ulalume3@112 | 126 | emission_pol.long_name = 'nominal emission poalrization' |
ulalume3@112 | 127 | emission_pol.flag_values = '0b 1b 2b' |
ulalume3@112 | 128 | emission_pol.flag_meanings = 'linear circular none' |
i@108 | 129 | |
ulalume3@112 | 130 | fov = g.createVariable('fov', datatype='f4') |
ulalume3@112 | 131 | fov.long_name = 'channel field of view full angle' |
ulalume3@112 | 132 | fov.units = 'mrad' |
ulalume3@112 | 133 | fov.comment = 'simulated' |
ulalume3@112 | 134 | |
ulalume3@112 | 135 | detector_type = g.createVariable('detector_type', datatype='b') |
ulalume3@112 | 136 | detector_type.long_name = 'detector type' |
ulalume3@112 | 137 | detector_type.flag_values = '0b 1b' |
ulalume3@112 | 138 | detector_type.flag_meanings = 'PMT APD' |
i@108 | 139 | |
ulalume3@112 | 140 | detection_mode = g.createVariable('detection_mode', datatype='b') |
ulalume3@112 | 141 | detection_mode.long_name = 'detection mode' |
ulalume3@112 | 142 | detection_mode.flag_values = '0b 1b' |
ulalume3@112 | 143 | detection_mode.flag_meanings = 'analog photon_counting' |
ulalume3@112 | 144 | |
ulalume3@112 | 145 | detection_cw = g.createVariable('detection_wavelength', datatype='f8') |
ulalume3@112 | 146 | detection_cw.long_name = 'center wavelength of detection filters' |
ulalume3@112 | 147 | detection_cw.units = 'nm' |
ulalume3@112 | 148 | detection_cw.standard_name = 'sensor_band_central_radiation_wavelength' |
i@108 | 149 | |
ulalume3@112 | 150 | detection_fwhm = g.createVariable('detection_fwhm', datatype='f8') |
ulalume3@112 | 151 | detection_fwhm.long_name = 'FWHM of detection filters' |
ulalume3@112 | 152 | detection_fwhm.units = 'nm' |
i@108 | 153 | |
ulalume3@112 | 154 | detection_pol = g.createVariable('detection_polarization', datatype='b') |
ulalume3@112 | 155 | detection_pol.long_name = 'nominal detection poalrization' |
ulalume3@112 | 156 | detection_pol.flag_values = '0b 1b 2b' |
ulalume3@112 | 157 | detection_pol.flag_meanings = 'linear circular none' |
ulalume3@112 | 158 | |
ulalume3@112 | 159 | polarizer_angle = g.createVariable('polarizer_angle', datatype='f4', dimensions=('profile', ), zlib=True) |
ulalume3@112 | 160 | polarizer_angle.long_name = 'polarizer angle in respect to laser plane of polarization' |
ulalume3@112 | 161 | polarizer_angle.units = 'degree' |
ulalume3@112 | 162 | polarizer_angle.comments = 'Optional' |
i@108 | 163 | |
ulalume3@112 | 164 | dead_time_model = g.createVariable('dead_time_model', datatype='b') |
ulalume3@112 | 165 | dead_time_model.long_name = 'optimal dead time model of detection system' |
ulalume3@112 | 166 | dead_time_model.flag_values = '0b 1b 2b' |
ulalume3@112 | 167 | dead_time_model.flag_meanings = 'paralyzable non_paralyzable other' |
i@108 | 168 | |
ulalume3@112 | 169 | dead_time = g.createVariable('dead_time', datatype='f8') |
ulalume3@112 | 170 | dead_time.long_name = 'dead time value' |
ulalume3@112 | 171 | dead_time.units = 'ns' |
ulalume3@112 | 172 | dead_time.comment = "Manufacturer. Source of the value." |
ulalume3@112 | 173 | |
ulalume3@112 | 174 | bin_length = g.createVariable('bin_length', datatype='f4') |
ulalume3@112 | 175 | bin_length.long_name = "time duration of each bin" |
ulalume3@112 | 176 | bin_length.units = 'ns' |
ulalume3@112 | 177 | |
ulalume3@112 | 178 | detector_voltage = g.createVariable('detector_voltage', datatype='f4', dimensions=('profile',), zlib=True) |
ulalume3@112 | 179 | detector_voltage.long_name = 'detector voltage' |
ulalume3@112 | 180 | detector_voltage.units = 'V' |
ulalume3@112 | 181 | detector_voltage.coordinates = "time" |
i@108 | 182 | |
ulalume3@112 | 183 | discriminator = g.createVariable('discriminator', datatype='f8', dimensions=('profiles',)) |
ulalume3@112 | 184 | discriminator.long_name = 'discriminator level' |
ulalume3@112 | 185 | discriminator.units = '' |
ulalume3@112 | 186 | |
ulalume3@112 | 187 | adc_range = g.createVariable('adc_range', datatype='f4', dimensions=('profile',), |
ulalume3@112 | 188 | zlib=True) |
ulalume3@112 | 189 | adc_range.long_name = 'analog-to-digital converter range' |
ulalume3@112 | 190 | adc_range.units = 'mV' |
ulalume3@112 | 191 | adc_range.coordinates = "time" |
i@108 | 192 | |
ulalume3@112 | 193 | adc_bits = g.createVariable('adc_bits', datatype='i4', dimensions=('profile',), |
ulalume3@112 | 194 | zlib=True) |
ulalume3@112 | 195 | adc_bits.long_name = 'analog-to-digital converter bits' |
ulalume3@112 | 196 | adc_bits.coordinates = "time" |
ulalume3@112 | 197 | |
ulalume3@112 | 198 | pulses = g.createVariable('pulses', datatype='i4', dimensions=('profile',), |
ulalume3@112 | 199 | zlib=True) |
ulalume3@112 | 200 | pulses.long_name = 'accumulated laser pulses per record' |
ulalume3@112 | 201 | pulses.coordinates = "time" |
i@108 | 202 | |
ulalume3@112 | 203 | nd_filter = g.createVariable('nd_filter_od', datatype='f8', dimensions=('profile',)) |
ulalume3@112 | 204 | nd_filter.long_name = "neutral density filter optical depth " |
ulalume3@112 | 205 | nd_filter.coordinates = "time" |
i@108 | 206 | |
ulalume3@112 | 207 | emission_delay = g.createVariable('emission_delay', datatype='f4') |
ulalume3@112 | 208 | emission_delay.long_name = "pulse emission difference from channel trigger" |
ulalume3@112 | 209 | emission_delay.units = 'ns' |
ulalume3@112 | 210 | emission_delay.comments = 'Positive values for pre-trigger systems. Negative for trigger delay.' |
i@108 | 211 | |
ulalume3@112 | 212 | time = g.createVariable('time', datatype='f8', dimensions=('profile',), |
ulalume3@112 | 213 | zlib=True) |
ulalume3@112 | 214 | time.long_name = 'profile start time ' |
ulalume3@112 | 215 | time.units = "seconds since 1970-01-01 00:00:00" |
ulalume3@112 | 216 | time.standard_name = "time" |
ulalume3@112 | 217 | time.bounds = "time_bnds" |
ulalume3@112 | 218 | g.createVariable('time_bnds', datatype='f8', dimensions=('profile', 'nv'), zlib=True) |
i@108 | 219 | |
ulalume3@112 | 220 | bin_time = g.createVariable('bin_time', datatype='f4', dimensions=('range',), |
ulalume3@112 | 221 | zlib=True) # Use name 'bin_range' to avoid conflict with built-in range |
ulalume3@112 | 222 | bin_time.long_name = 'bin start time since trigger' |
ulalume3@112 | 223 | bin_time.units = "ns" |
i@108 | 224 | |
ulalume3@112 | 225 | signal = g.createVariable('signal', datatype='i4', dimensions=('profile', 'range'), |
ulalume3@112 | 226 | zlib=True) |
ulalume3@112 | 227 | signal.long_name = 'signal' |
ulalume3@112 | 228 | signal.units = channel_parameters['signal_units'] |
ulalume3@112 | 229 | signal.coordinates = "time" |
ulalume3@112 | 230 | signal.ancillary_variables = "signal_stddev" |
ulalume3@112 | 231 | |
ulalume3@112 | 232 | # If measured |
ulalume3@112 | 233 | signal_stddev = g.createVariable('signal', datatype='i4', dimensions=('profile', 'range'), |
ulalume3@112 | 234 | zlib=True) |
ulalume3@112 | 235 | signal_stddev.long_name = 'signal standard deviation' |
ulalume3@112 | 236 | signal_stddev.units = channel_parameters['signal_units'] |
ulalume3@112 | 237 | signal_stddev.coordinates = "time" |
ulalume3@112 | 238 | |
ulalume3@112 | 239 | # Assign variables |
ulalume3@112 | 240 | # TBD |