atmospheric_lidar/diva.py

Thu, 19 Nov 2020 10:23:32 +0000

author
Ioannis Binietoglou <ulalume3@gmail.com>
date
Thu, 19 Nov 2020 10:23:32 +0000
changeset 208
686c1525ce36
parent 168
9fed2446a59f
permissions
-rw-r--r--

Merge branch 'gdoxastakis-master-patch-40497' into 'master'

Added custom_info field support

See merge request ioannis_binietoglou/atmospheric-lidar!1

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@116 7 import yaml
i@108 8 import datetime
i@108 9 import os
i@116 10 import numpy as np
i@150 11 import logging
i@116 12
i@116 13 import pytz
i@108 14
i@108 15 from .generic import BaseLidarMeasurement
i@108 16
i@150 17 logger = logging.getLogger(__name__)
i@108 18
i@150 19
ioannis@165 20 class DivaConverterMixin:
i@108 21
i@116 22 def save_as_diva_netcdf(self, output_path, parameter_file):
i@108 23 """ Save the current data in the 'draft' DIVA format. """
i@108 24
i@108 25 with open(parameter_file, 'r') as f:
i@116 26 parameters = yaml.load(f)
i@108 27
i@108 28 global_parameters = parameters['global_parameters'] # Shortcut
i@122 29 global_variables = parameters['global_variables'] # Shortcut
ulalume3@112 30 channels = parameters['channels']
i@108 31
ioannis@165 32 iso_date = datetime.datetime.utcnow().strftime('%Y-%d-%mT%H:%M:%SZ')
i@108 33 python_file_name = os.path.basename(__file__)
i@108 34
i@108 35 with netcdf.Dataset(output_path, 'w', format="NETCDF4") as f:
i@108 36
i@108 37 # Global attributes
i@108 38 f.title = global_parameters['title']
i@108 39 f.source = global_parameters['source']
i@108 40 f.institution = global_parameters['institution']
i@108 41 f.references = global_parameters['references']
i@108 42 f.location = global_parameters['location']
i@108 43 f.data_version = global_parameters['data_version']
i@122 44 f.PI = global_parameters['PI_name']
i@122 45 f.PI_email = global_parameters['PI_email']
i@108 46 f.conversion_date = iso_date
i@108 47 f.comment = global_parameters['comment']
i@108 48 f.Conventions = global_parameters['Conventions']
i@108 49 f.history = global_parameters['history'].format(date=iso_date, file=python_file_name)
i@108 50 f.featureType = "timeSeriesProfile"
i@108 51
ulalume3@112 52 # Top level dimensions
ulalume3@112 53 f.createDimension('name_strlen', size=40)
ulalume3@112 54 f.createDimension('nv', size=2)
ulalume3@112 55
i@108 56 # Top level variables
i@108 57 latitude = f.createVariable('latitude', datatype='f4')
i@108 58 latitude.standard_name = 'latitude'
i@108 59 latitude.long_name = 'system latitude'
i@108 60 latitude.units = 'degrees_north'
i@108 61
i@108 62 longitude = f.createVariable('longitude', datatype='f4')
i@108 63 longitude.standard_name = 'longitude'
i@108 64 longitude.long_name = 'system longitude'
i@108 65 longitude.units = 'degrees_east'
i@108 66
ioannis@165 67 lidar_zenith_angle = f.createVariable('lidar_zenith_angle', datatype='f4')
ioannis@165 68 lidar_zenith_angle.standard_name = 'sensor_zenith_angle'
ioannis@165 69 lidar_zenith_angle.long_name = 'zenith angle of emitted laser'
ioannis@165 70 lidar_zenith_angle.units = 'degree'
i@108 71
ioannis@165 72 lidar_azimuth = f.createVariable('lidar_azimuth_angle', datatype='f4')
ioannis@165 73 lidar_azimuth.standard_name = 'sensor_azimuth_angle'
ioannis@165 74 lidar_azimuth.long_name = 'azimuth angle of emitted laser'
ioannis@165 75 lidar_azimuth.units = 'degree'
ioannis@165 76 lidar_azimuth.comment = 'Based on North. Optional'
i@108 77
i@108 78 altitude = f.createVariable('altitude', datatype='f4')
i@108 79 altitude.standard_name = 'altitude'
i@108 80 altitude.long_name = 'system altitude'
i@108 81 altitude.units = 'm'
i@108 82
i@122 83 # Assign top-level variables
i@123 84 latitude[:] = global_variables['latitude']
i@123 85 longitude[:] = global_variables['longitude']
ioannis@165 86 lidar_zenith_angle[:] = global_variables['laser_pointing_angle']
i@123 87 altitude[:] = global_variables['system_altitude']
i@122 88
ulalume3@113 89 # Optional ancillary group
ulalume3@113 90 ancillary = f.createGroup('ancillary')
ulalume3@113 91 ancillary.featureType = "timeSeries"
ulalume3@113 92
ulalume3@113 93 ancillary.createDimension('time', size=None)
ulalume3@113 94
ulalume3@113 95 time = ancillary.createVariable('time', datatype='f8', dimensions=('time',))
ulalume3@113 96 time.long_name = 'time'
ulalume3@113 97 time.units = 'seconds since 1970-01-01 00:00'
ulalume3@113 98 time.standard_name = 'time'
ulalume3@113 99
ulalume3@113 100 temperature = ancillary.createVariable('air_temperature', datatype='f8', dimensions=('time',))
ulalume3@113 101 temperature.long_name = 'air temperature at instrument level'
ulalume3@113 102 temperature.units = 'K'
ulalume3@113 103 temperature.standard_name = 'air_temperature'
ulalume3@113 104
ulalume3@113 105 pressure = ancillary.createVariable('air_pressure', datatype='f8', dimensions=('time',))
ulalume3@113 106 pressure.long_name = 'air pressure at instrument level'
ulalume3@113 107 pressure.units = 'hPa'
ulalume3@113 108 pressure.standard_name = 'air_pressure'
ulalume3@113 109
ulalume3@113 110 # Create a separate group for each channel
ioannis@168 111 for channel_name, channel_parameters in channels.items():
i@108 112
ioannis@168 113 if channel_name not in list(self.channels.keys()):
ioannis@168 114 raise ValueError('Channel name not one of {0}: {1}'.format(list(self.channels.keys()), channel_name))
i@115 115
i@115 116 channel = self.channels[channel_name]
i@115 117
i@116 118 group_name = "channel_{0}".format(channel_name.replace('.', '_')) # Give channels groups a standard name
i@116 119
ulalume3@112 120 g = f.createGroup(group_name)
ulalume3@112 121 g.long_name = channel_parameters['long_name']
i@116 122 g.detector_manufacturer = channel_parameters['detector_manufacturer'] # Optional
ulalume3@112 123 g.detector_model = channel_parameters['detector_model']
ulalume3@112 124 g.daq_manufacturer = channel_parameters['daq_manufacturer']
ulalume3@112 125 g.daq_model = channel_parameters['daq_model']
i@108 126
ulalume3@112 127 # Dimensions
ulalume3@112 128 g.createDimension('profile', size=None) # Infinite dimension
i@116 129 g.createDimension('range', len(channel.z))
i@108 130
ulalume3@112 131 # Variables
i@116 132 name = g.createVariable('channel_id', 'c', dimensions=('name_strlen',))
ulalume3@112 133 name.cf_role = 'timeseries_id'
ulalume3@112 134 name.long_name = 'channel identification'
i@108 135
i@116 136 laser_rep_rate = g.createVariable('laser_repetition_rate', 'f4')
ulalume3@112 137 laser_rep_rate.long_name = 'nominal laser repetition rate'
ulalume3@112 138 laser_rep_rate.units = 'Hz'
i@108 139
ioannis@167 140 emission_wavelength = g.createVariable('emission_wavelength', datatype='f8', ) # or dimensions=('profile',)
ioannis@167 141 emission_wavelength.long_name = 'emission wavelength'
ioannis@167 142 emission_wavelength.units = 'nm'
ioannis@167 143 emission_wavelength.comment = "could have dimension profile if measured."
i@122 144
i@114 145 emission_energy = g.createVariable('emission_energy', datatype='f8', ) # or dimensions=('profile',)
ulalume3@112 146 emission_energy.long_name = 'emission energy per pulse'
ulalume3@112 147 emission_energy.units = 'mJ'
ulalume3@112 148 emission_energy.comment = "could be scalar, if value is nominal."
i@108 149
ulalume3@112 150 emission_pol = g.createVariable('emission_polarization', datatype='b')
ulalume3@112 151 emission_pol.long_name = 'nominal emission poalrization'
ulalume3@112 152 emission_pol.flag_values = '0b 1b 2b'
ulalume3@112 153 emission_pol.flag_meanings = 'linear circular none'
i@108 154
ulalume3@112 155 fov = g.createVariable('fov', datatype='f4')
ulalume3@112 156 fov.long_name = 'channel field of view full angle'
ulalume3@112 157 fov.units = 'mrad'
ulalume3@112 158 fov.comment = 'simulated'
ulalume3@112 159
ulalume3@112 160 detector_type = g.createVariable('detector_type', datatype='b')
ulalume3@112 161 detector_type.long_name = 'detector type'
ulalume3@112 162 detector_type.flag_values = '0b 1b'
ulalume3@112 163 detector_type.flag_meanings = 'PMT APD'
i@108 164
ulalume3@112 165 detection_mode = g.createVariable('detection_mode', datatype='b')
ulalume3@112 166 detection_mode.long_name = 'detection mode'
ulalume3@112 167 detection_mode.flag_values = '0b 1b'
ulalume3@112 168 detection_mode.flag_meanings = 'analog photon_counting'
ulalume3@112 169
ulalume3@112 170 detection_cw = g.createVariable('detection_wavelength', datatype='f8')
ulalume3@112 171 detection_cw.long_name = 'center wavelength of detection filters'
ulalume3@112 172 detection_cw.units = 'nm'
ulalume3@112 173 detection_cw.standard_name = 'sensor_band_central_radiation_wavelength'
i@108 174
ulalume3@112 175 detection_fwhm = g.createVariable('detection_fwhm', datatype='f8')
ulalume3@112 176 detection_fwhm.long_name = 'FWHM of detection filters'
ulalume3@112 177 detection_fwhm.units = 'nm'
i@108 178
ulalume3@112 179 detection_pol = g.createVariable('detection_polarization', datatype='b')
ulalume3@112 180 detection_pol.long_name = 'nominal detection poalrization'
ulalume3@112 181 detection_pol.flag_values = '0b 1b 2b'
i@114 182 detection_pol.flag_meanings = 'linear circular total'
ulalume3@112 183
ulalume3@112 184 polarizer_angle = g.createVariable('polarizer_angle', datatype='f4', dimensions=('profile', ), zlib=True)
ulalume3@112 185 polarizer_angle.long_name = 'polarizer angle in respect to laser plane of polarization'
ulalume3@112 186 polarizer_angle.units = 'degree'
ulalume3@112 187 polarizer_angle.comments = 'Optional'
i@108 188
ioannis@166 189 if channel.is_photon_counting:
i@116 190 dead_time_model = g.createVariable('dead_time_model', datatype='b')
i@116 191 dead_time_model.long_name = 'optimal dead time model of detection system'
i@116 192 dead_time_model.flag_values = '0b 1b 2b'
i@116 193 dead_time_model.flag_meanings = 'paralyzable non_paralyzable other'
i@108 194
i@116 195 dead_time = g.createVariable('dead_time', datatype='f8')
i@116 196 dead_time.long_name = 'dead time value'
i@116 197 dead_time.units = 'ns'
i@116 198 dead_time.comment = "Manufacturer. Source of the value."
ulalume3@112 199
ulalume3@112 200 bin_length = g.createVariable('bin_length', datatype='f4')
ulalume3@112 201 bin_length.long_name = "time duration of each bin"
ulalume3@112 202 bin_length.units = 'ns'
ulalume3@112 203
i@116 204 if channel.is_analog:
i@116 205 adc_bits = g.createVariable('adc_bits', datatype='i4')
i@116 206 adc_bits.long_name = 'analog-to-digital converter bits'
i@116 207 adc_bits.coordinates = "time"
i@115 208
ulalume3@112 209 detector_voltage = g.createVariable('detector_voltage', datatype='f4', dimensions=('profile',), zlib=True)
ulalume3@112 210 detector_voltage.long_name = 'detector voltage'
ulalume3@112 211 detector_voltage.units = 'V'
ulalume3@112 212 detector_voltage.coordinates = "time"
i@108 213
i@116 214 if channel.is_photon_counting:
i@116 215 discriminator = g.createVariable('discriminator', datatype='f8', dimensions=('profile',))
i@116 216 discriminator.long_name = 'discriminator level'
i@116 217 discriminator.units = ''
ulalume3@112 218
i@116 219 if channel.is_analog:
i@116 220 adc_range = g.createVariable('adc_range', datatype='f4', dimensions=('profile',),
i@116 221 zlib=True)
i@116 222 adc_range.long_name = 'analog-to-digital converter range'
i@116 223 adc_range.units = 'mV'
i@116 224 adc_range.coordinates = "time"
ulalume3@112 225
ulalume3@112 226 pulses = g.createVariable('pulses', datatype='i4', dimensions=('profile',),
ulalume3@112 227 zlib=True)
ulalume3@112 228 pulses.long_name = 'accumulated laser pulses per record'
ulalume3@112 229 pulses.coordinates = "time"
i@108 230
ulalume3@112 231 nd_filter = g.createVariable('nd_filter_od', datatype='f8', dimensions=('profile',))
ulalume3@112 232 nd_filter.long_name = "neutral density filter optical depth "
ulalume3@112 233 nd_filter.coordinates = "time"
i@108 234
i@116 235 trigger_delay = g.createVariable('trigger_delay', datatype='f4')
i@122 236 trigger_delay.long_name = "channel trigger difference from pulse emission"
i@116 237 trigger_delay.units = 'ns'
i@116 238 trigger_delay.comments = 'Negative values for pre-trigger systems.'
i@108 239
ulalume3@112 240 time = g.createVariable('time', datatype='f8', dimensions=('profile',),
ulalume3@112 241 zlib=True)
ulalume3@112 242 time.long_name = 'profile start time '
ulalume3@112 243 time.units = "seconds since 1970-01-01 00:00:00"
ulalume3@112 244 time.standard_name = "time"
ulalume3@112 245 time.bounds = "time_bnds"
i@116 246 time_bounds = g.createVariable('time_bnds', datatype='f8', dimensions=('profile', 'nv'), zlib=True)
i@108 247
i@116 248 bin_time = g.createVariable('bin_time', datatype='f4', dimensions=('range',), zlib=True)
i@116 249 bin_time.long_name = 'bin start time since channel trigger'
ulalume3@112 250 bin_time.units = "ns"
i@108 251
i@116 252 if channel.is_analog:
i@116 253 signal_units = 'mV'
i@116 254 signal_datatype = 'f8'
i@116 255 else:
i@116 256 signal_units = 'counts'
i@116 257 signal_datatype = 'i8'
i@116 258
i@116 259 signal = g.createVariable('signal', datatype=signal_datatype, dimensions=('profile', 'range'),
ulalume3@112 260 zlib=True)
ulalume3@112 261 signal.long_name = 'signal'
i@116 262 signal.units = signal_units
ulalume3@112 263 signal.coordinates = "time"
ulalume3@112 264 signal.ancillary_variables = "signal_stddev"
ulalume3@112 265
ulalume3@112 266 # If measured
i@116 267 signal_stddev = g.createVariable('signal_stddev', datatype=signal_datatype, dimensions=('profile', 'range'),
ulalume3@112 268 zlib=True)
ulalume3@112 269 signal_stddev.long_name = 'signal standard deviation'
i@116 270 signal_stddev.units = signal_units
ulalume3@112 271 signal_stddev.coordinates = "time"
i@116 272 signal_stddev.comments = "Only if measured. Should be removed if not."
ulalume3@112 273
ulalume3@112 274 # Assign variables
i@116 275 name[:len(channel_name)] = channel_name
i@114 276 laser_rep_rate[:] = channel_parameters['laser_repetition_rate']
ioannis@167 277 emission_wavelength[:] = channel_parameters['emission_wavelength']
i@114 278 emission_energy[:] = channel_parameters['emission_energy']
i@114 279 emission_pol[:] = self._emission_pol_flag(channel_parameters['emission_polarization'])
i@114 280 fov[:] = channel_parameters['fov']
i@114 281 detector_type[:] = self._detector_type_flag(channel_parameters['detector_type'])
i@114 282 detection_mode[:] = self._detection_mode_flag(channel_parameters['detection_mode'])
i@114 283 detection_fwhm[:] = channel_parameters['filter_fwhm']
i@114 284 detection_pol[:] = self._detection_pol_flag(channel_parameters['detection_polarization'])
i@116 285 polarizer_angle[:] = channel_parameters['polarizer_angle'] * np.ones(len(channel.time)) # For now, assumed constant.
i@116 286
i@116 287 if channel.is_photon_counting:
i@116 288 dead_time_model[:] = self._deadtime_model_flag(channel_parameters['dead_time_model'])
i@116 289 dead_time[:] = channel_parameters['dead_time']
i@116 290
i@114 291 bin_length[:] = channel_parameters['bin_length']
i@116 292 trigger_delay[:] = channel_parameters['trigger_delay']
i@116 293
i@116 294 detector_voltage[:] = channel.hv
i@116 295
i@116 296 if channel.is_analog:
i@116 297 adc_range[:] = channel.discriminator
i@116 298 adc_bits[:] = channel.adcbits
i@116 299 else:
i@116 300 discriminator[:] = channel.discriminator
i@115 301
i@116 302 pulses[:] = channel.laser_shots
i@115 303
i@122 304 epoch = datetime.datetime(1970, 1, 1, tzinfo=pytz.utc)
i@116 305 seconds_since_epoch = [(t - epoch).total_seconds() for t in channel.time]
i@116 306 time[:] = seconds_since_epoch
i@116 307 time_bounds[:, 0] = seconds_since_epoch
i@116 308 time_bounds[:, 1] = seconds_since_epoch + channel.get_duration()
i@116 309
i@116 310 bin_time[:] = channel.binwidth * np.arange(len(channel.z))
i@116 311
i@116 312 signal[:] = channel.matrix
i@114 313
i@114 314 def _deadtime_model_flag(self, model_str):
i@114 315 """ Convert dead-time model string to byte flag.
i@114 316
i@114 317 Parameters
i@114 318 ----------
i@114 319 model_str : str
i@114 320 String describing the dead-time model (one of paralyzable, non-paralyzable, or other)
i@114 321
i@114 322 Returns
i@114 323 -------
i@114 324 : int
i@114 325 Byte encoding of dead-time model
i@114 326 """
i@114 327 choices = {'paralyzable': 0,
i@114 328 'non-paralyzable': 1,
i@114 329 'other': 2}
i@114 330
ioannis@168 331 if model_str not in list(choices.keys()):
ioannis@168 332 raise ValueError('Dead-time model is not one of {0}: {1}'.format(list(choices.keys()), model_str))
i@114 333
i@114 334 return choices[model_str]
i@114 335
i@114 336 def _detection_pol_flag(self, pol_str):
i@114 337 """ Convert detection polarization string to byte flag.
i@114 338
i@114 339 Parameters
i@114 340 ----------
i@114 341 pol_str : str
i@114 342 String describing the detection polarization (one of linear, circular, or total)
i@114 343
i@114 344 Returns
i@114 345 -------
i@114 346 : int
i@114 347 Byte encoding of detection polarization
i@114 348 """
i@114 349 choices = {'linear': 0,
i@114 350 'circular': 1,
i@114 351 'total': 2}
i@114 352
ioannis@168 353 if pol_str not in list(choices.keys()):
ioannis@168 354 raise ValueError('Detection polarization is not one of {0}: {1}'.format(list(choices.keys()), pol_str))
i@114 355
i@114 356 return choices[pol_str]
i@114 357
i@114 358 def _detection_mode_flag(self, mode_str):
i@114 359 """ Convert detection mode string to byte flag.
i@114 360
i@114 361 Parameters
i@114 362 ----------
i@114 363 mode_str : str
i@114 364 String describing the detector mode (one of photon-counting or analog)
i@114 365
i@114 366 Returns
i@114 367 -------
i@114 368 : int
i@114 369 Byte encoding of detection mode
i@114 370 """
i@114 371 choices = {'analog': 0,
i@114 372 'photon-counting': 1,}
i@114 373
ioannis@168 374 if mode_str not in list(choices.keys()):
ioannis@168 375 raise ValueError('Detection mode is not one of {0}: {1}'.format(list(choices.keys()), mode_str))
i@114 376
i@114 377 return choices[mode_str]
i@114 378
i@114 379 def _detector_type_flag(self, type_string):
i@114 380 """ Convert emission string to byte flag.
i@114 381
i@114 382 Parameters
i@114 383 ----------
i@114 384 type_string : str
i@114 385 String describing the detector type (one of APD or PMT)
i@114 386
i@114 387 Returns
i@114 388 -------
i@114 389 : int
i@114 390 Byte encoding of detector type
i@114 391 """
i@114 392 choices = {'PMT': 0,
i@114 393 'APD': 1,}
i@114 394
ioannis@168 395 if type_string not in list(choices.keys()):
ioannis@168 396 raise ValueError('Detector type is not one of {0}: {1}'.format(list(choices.keys()), type_string))
i@114 397
i@114 398 return choices[type_string]
i@114 399
i@114 400 def _emission_pol_flag(self, pol_string):
i@114 401 """ Convert emission string to byte flag.
i@114 402
i@114 403 Parameters
i@114 404 ----------
i@114 405 pol_string : str
i@114 406 String describing the polarization (one of linear, circular, or none)
i@114 407
i@114 408 Returns
i@114 409 -------
i@114 410 : int
i@114 411 Byte encoding of polarization state
i@114 412 """
i@116 413 choices = {'linear': 0,
i@114 414 'circular': 1,
i@114 415 'none': 2}
i@114 416
ioannis@168 417 if pol_string not in list(choices.keys()):
ioannis@168 418 raise ValueError('Emission polarization not one of {0}: {1}'.format(list(choices.keys()), pol_string))
i@114 419
i@114 420 return choices[pol_string]
i@150 421
i@150 422
ioannis@165 423 class DivaLidarMeasurement(object):
ioannis@165 424 """ A class to read raw lidar files in DIVA format.
i@150 425
ioannis@165 426 Unlike other classes in this module, it does not inherit from BasicLidarMeasurement. This is done
ioannis@165 427 to avoid all the burden of backward compatibility. In the future this could be hosted also as a separte moduel.
ioannis@165 428 """
ioannis@165 429
ioannis@166 430 def __init__(self, file_path, header_only=False):
i@150 431 """
i@150 432 This is run when creating a new object.
i@150 433
i@150 434 Parameters
i@150 435 ----------
ioannis@165 436 file_path : str
ioannis@165 437 Paths to the input netCDF file.
ioannis@166 438 header_only : bool
ioannis@166 439 If True, channel info are not loaded.
i@150 440 """
ioannis@165 441 self.file_path = file_path
ioannis@165 442 self.file_name = os.path.basename(file_path)
ioannis@165 443
ioannis@167 444 self.import_file(header_only)
ioannis@165 445
ioannis@166 446 def import_file(self, header_only):
ioannis@165 447 """ Import data from a single DIVA file.
ioannis@165 448 """
ioannis@165 449
ioannis@165 450 logger.debug('Importing file {0}'.format(self.file_name))
ioannis@165 451
ioannis@165 452 self.channels = {}
i@150 453
ioannis@165 454 with netcdf.Dataset(self.file_path) as input_file:
ioannis@165 455 self.title = input_file.title
ioannis@165 456 self.source = input_file.source
ioannis@165 457 self.institution = input_file.institution
ioannis@165 458 self.references = input_file.references
ioannis@165 459 self.location = input_file.location
ioannis@165 460 self.data_version = input_file.data_version
ioannis@165 461 self.PI = input_file.PI
ioannis@165 462 self.PI_email = input_file.PI_email
ioannis@165 463 self.conversion_date_str = input_file.conversion_date
ioannis@165 464 self.conversion_date = datetime.datetime.strptime(input_file.conversion_date, '%Y-%d-%mT%H:%M:%SZ')
ioannis@165 465 self.comment = input_file.comment
ioannis@165 466 self.conventions = input_file.Conventions
ioannis@165 467 self.history = input_file.history
i@150 468
ioannis@165 469 self.latitude = input_file.variables['latitude'][:]
ioannis@165 470 self.longitude = input_file.variables['longitude'][:]
ioannis@165 471
ioannis@165 472 self.lidar_zenith_angle = input_file.variables['lidar_zenith_angle'][:]
ioannis@165 473 self.lidar_azimuth_angle = input_file.variables['lidar_azimuth_angle'][:]
ioannis@165 474 self.lidar_altitude = input_file.variables['altitude'][:]
ioannis@165 475
ioannis@165 476 ancillary = input_file.groups.pop('ancillary')
i@150 477
ioannis@165 478 self.meteo_time = ancillary.variables['time'][:]
ioannis@165 479 self.air_temperature_kelvin = ancillary.variable['air_temperature'][:]
ioannis@165 480 self.air_pressure_hpa = ancillary.variable['air_pressure'][:]
i@150 481
ioannis@166 482 self.available_channels = []
ioannis@168 483 for group_name, group in list(input_file.groups.items()):
ioannis@165 484 channel_name = group_name[8:] # Remove 'channel_' prefix
ioannis@166 485 self.available_channels.append(channel_name)
ioannis@166 486
ioannis@166 487 if not header_only:
ioannis@166 488 self.channels[channel_name] = DivaChannel(channel_name, group)
ioannis@165 489
ioannis@166 490 def import_channel(self, channel_name):
ioannis@166 491 """ Import a specific channel. """
ioannis@166 492 if channel_name not in self.available_channels:
ioannis@166 493 raise ValueError('Channel {0} not available. Should be one of {1}'.format(channel_name, self.available_channels))
ioannis@166 494
ioannis@166 495 group_name = 'channel_{0}'.format(channel_name)
ioannis@166 496
ioannis@166 497 with netcdf.Dataset(self.file_path) as input_file:
ioannis@166 498 group = input_file.groups[group_name]
ioannis@166 499 self.channels[channel_name] = DivaChannel(channel_name, group)
i@150 500
i@150 501
i@150 502 class DivaChannel(object):
i@150 503
ioannis@165 504 def __init__(self, channel_name, group):
i@150 505 """ This is run when first creating the object.
i@150 506
i@150 507 Parameters
i@150 508 ----------
ioannis@165 509 channel_name : str
ioannis@165 510 Name of the group
ioannis@165 511 group : netCDF4.Group object
ioannis@165 512 An open netcdf group to initialize.
i@150 513 """
ioannis@166 514 self.channel_name = channel_name
ioannis@166 515
ioannis@166 516 self.long_name = group.long_name
ioannis@167 517 self.detector_manufacturer = getattr(group, 'detector_manufacturer', None)
ioannis@167 518 self.detector_model = getattr(group, 'detector_model', None)
ioannis@167 519 self.daq_manufacturer = getattr(group, 'daq_manufacturer', None)
ioannis@167 520 self.daq_model = getattr(group, 'daq_model', None)
ioannis@166 521
ioannis@166 522 self.number_of_profiles = len(group.dimensions['profile'])
ioannis@166 523 self.number_of_bins = len(group.dimensions['range'])
ioannis@166 524 self.channel_id = group.variables['channel_id'][:]
ioannis@166 525 self.laser_repetition_rate = group.variables['laser_repetition_rate'][:]
ioannis@166 526
ioannis@166 527 self.emission_energy_mJ = group.variables['emission_energy'][:]
ioannis@166 528 self.emission_polarization_flag = group.variables['emission_polarization'][:]
ioannis@166 529 self.emission_polarization = self._flag_to_polarization(self.emission_polarization_flag)
ioannis@166 530 self.field_of_view = group.variables['fov'][:]
ioannis@166 531 self.field_of_view_comment = group.variables['fov'].comment
ioannis@166 532
ioannis@166 533 self.detector_type_flag = group.variables['detector_type'][:]
ioannis@166 534 self.detector_type = self._flag_to_detector_type(self.detector_type_flag)
ioannis@166 535
ioannis@166 536 self.detection_mode_flag = group.variables['detection_mode'][:]
ioannis@166 537 self.detection_mode = self._flag_to_detector_type(self.detection_mode_flag)
ioannis@166 538
ioannis@166 539 self.detection_wavelength_nm = group.variables['detection_wavelength'][:]
ioannis@166 540 self.detection_fwhm = group.variables['detection_fwhm'][:]
ioannis@166 541
ioannis@166 542 self.detection_polarization_flag = group.variables['detection_polarization']
ioannis@166 543 self.detection_polariation = self._flag_to_detection_polarization(self.detection_polarization_flag)
ioannis@166 544
ioannis@166 545 self.polarizer_angle_degrees = group.variables['polarizer_angle'][:]
ioannis@166 546
ioannis@166 547 if self.is_photon_counting:
ioannis@166 548 self.dead_time_model_flag = group.variables['dead_time_model'][:]
ioannis@166 549 self.dead_time_model = self._flag_to_dead_time_model(self.dead_time_model_flag)
ioannis@166 550
ioannis@166 551 self.dead_time = group.variables['dead_time'][:]
ioannis@166 552 self.dead_time_source = group.variables['dead_time'].comment
ioannis@166 553 self.discriminator = group.variables['discriminator'][:]
ioannis@166 554
ioannis@166 555 if self.is_analog:
ioannis@166 556 self.adc_bits = group.variables['adc_bits'][:]
ioannis@166 557 self.adc_range = group.variables['adc_range'][:]
ioannis@166 558
ioannis@166 559 self.bin_length_ns = group.variables['bin_length'][:]
ioannis@166 560 self.detector_voltage = group.variables['detector_voltage'][:]
ioannis@166 561 self.pulses = group.variables['pulses'][:]
ioannis@166 562 self.nd_filter_od = group.variables['nd_filter_od'][:]
ioannis@166 563 self.trigger_delay_ns = group.variables['trigger_delay'][:]
ioannis@166 564 self.time_since_epoch = group.variables['time'][:]
ioannis@166 565
ioannis@166 566 self.time = [datetime.datetime.utcfromtimestamp(t) for t in self.time_since_epoch]
ioannis@166 567 self.bin_time_ns = group.variables['bin_time'][:]
ioannis@166 568
ioannis@166 569 self.signal = group.variables['signal'][:]
ioannis@166 570 self.signal_units = group.variables['signal'].units
ioannis@166 571
ioannis@166 572 signal_stddev_var = group.variables.pop('signal_stddev', None)
ioannis@166 573
ioannis@166 574 if signal_stddev_var:
ioannis@166 575 self.signal_stddev = signal_stddev_var[:]
ioannis@166 576 else:
ioannis@166 577 self.signal_stddev = None
i@150 578
ioannis@166 579 def _flag_to_polarization(self, flag):
ioannis@166 580 """ Convert polarization flag to str"""
ioannis@166 581 if flag not in [0, 1, 2]:
ioannis@166 582 logger.warning('Polarization flag has unrecognized value: {0}'.format(flag))
ioannis@166 583 return ""
ioannis@166 584
ioannis@166 585 values = {0: 'linear',
ioannis@166 586 1: 'circular',
ioannis@166 587 2: 'None'}
ioannis@166 588
ioannis@166 589 return values[flag]
ioannis@166 590
ioannis@166 591 def _flag_to_detector_type(self, flag):
ioannis@166 592 """ Convert detector type flag to str"""
ioannis@166 593 if flag not in [0, 1]:
ioannis@166 594 logger.warning('Detector type flag has unrecognized value: {0}'.format(flag))
ioannis@166 595 return ""
ioannis@166 596
ioannis@166 597 values = {0: 'PMT',
ioannis@167 598 1: 'APD'}
ioannis@166 599
ioannis@166 600 return values[flag]
ioannis@166 601
ioannis@166 602 def _flag_to_detection_mode(self, flag):
ioannis@166 603 """ Convert detector type flag to str"""
ioannis@166 604 if flag not in [0, 1]:
ioannis@166 605 logger.warning('Detection mode flag has unrecognized value: {0}'.format(flag))
ioannis@166 606 return ""
ioannis@166 607
ioannis@166 608 values = {0: 'analog',
ioannis@166 609 1: 'photon counting'}
ioannis@165 610
ioannis@166 611 return values[flag]
ioannis@166 612
ioannis@166 613 def _flag_to_detection_polarization(self, flag):
ioannis@166 614 """ Convert detector type flag to str"""
ioannis@166 615 if flag not in [0, 1, 2]:
ioannis@166 616 logger.warning('Detection polarization flag has unrecognized value: {0}'.format(flag))
ioannis@166 617 return ""
ioannis@166 618
ioannis@166 619 values = {0: 'linear',
ioannis@166 620 1: 'circular',
ioannis@167 621 2: 'total'}
ioannis@166 622
ioannis@166 623 return values[flag]
ioannis@166 624
ioannis@166 625 def _flag_to_dead_time_model(self, flag):
ioannis@166 626 """ Convert detector type flag to str"""
ioannis@166 627 if flag not in [0, 1, 2]:
ioannis@166 628 logger.warning('Dead time model flag has unrecognized value: {0}'.format(flag))
ioannis@166 629 return ""
ioannis@166 630
ioannis@166 631 values = {0: 'paralyzable',
ioannis@166 632 1: 'non_paralyzable',
ioannis@167 633 2: 'other'}
ioannis@166 634
ioannis@166 635 return values[flag]
ioannis@166 636
ioannis@166 637 @property
ioannis@166 638 def is_analog(self):
ioannis@166 639 return self.detection_mode_flag==0
ioannis@166 640
ioannis@166 641 @property
ioannis@166 642 def is_photon_counting(self):
ioannis@166 643 return self.detection_mode_flag==1

mercurial