182 polarizer_angle = g.createVariable('polarizer_angle', datatype='f4', dimensions=('profile', ), zlib=True) |
182 polarizer_angle = g.createVariable('polarizer_angle', datatype='f4', dimensions=('profile', ), zlib=True) |
183 polarizer_angle.long_name = 'polarizer angle in respect to laser plane of polarization' |
183 polarizer_angle.long_name = 'polarizer angle in respect to laser plane of polarization' |
184 polarizer_angle.units = 'degree' |
184 polarizer_angle.units = 'degree' |
185 polarizer_angle.comments = 'Optional' |
185 polarizer_angle.comments = 'Optional' |
186 |
186 |
187 if not channel.is_analog: |
187 if channel.is_photon_counting: |
188 dead_time_model = g.createVariable('dead_time_model', datatype='b') |
188 dead_time_model = g.createVariable('dead_time_model', datatype='b') |
189 dead_time_model.long_name = 'optimal dead time model of detection system' |
189 dead_time_model.long_name = 'optimal dead time model of detection system' |
190 dead_time_model.flag_values = '0b 1b 2b' |
190 dead_time_model.flag_values = '0b 1b 2b' |
191 dead_time_model.flag_meanings = 'paralyzable non_paralyzable other' |
191 dead_time_model.flag_meanings = 'paralyzable non_paralyzable other' |
192 |
192 |
422 |
422 |
423 Unlike other classes in this module, it does not inherit from BasicLidarMeasurement. This is done |
423 Unlike other classes in this module, it does not inherit from BasicLidarMeasurement. This is done |
424 to avoid all the burden of backward compatibility. In the future this could be hosted also as a separte moduel. |
424 to avoid all the burden of backward compatibility. In the future this could be hosted also as a separte moduel. |
425 """ |
425 """ |
426 |
426 |
427 def __init__(self, file_path, import_now=True): |
427 def __init__(self, file_path, header_only=False): |
428 """ |
428 """ |
429 This is run when creating a new object. |
429 This is run when creating a new object. |
430 |
430 |
431 Parameters |
431 Parameters |
432 ---------- |
432 ---------- |
433 file_path : str |
433 file_path : str |
434 Paths to the input netCDF file. |
434 Paths to the input netCDF file. |
435 import_now : bool |
435 header_only : bool |
436 If True, the file is imported when the object is created. |
436 If True, channel info are not loaded. |
437 """ |
437 """ |
438 self.file_path = file_path |
438 self.file_path = file_path |
439 self.file_name = os.path.basename(file_path) |
439 self.file_name = os.path.basename(file_path) |
440 |
440 |
441 if import_now: |
441 self.import_file(file_path, header_only) |
442 self.import_file(file_path) |
442 |
443 |
443 def import_file(self, header_only): |
444 def import_file(self): |
|
445 """ Import data from a single DIVA file. |
444 """ Import data from a single DIVA file. |
446 """ |
445 """ |
447 |
446 |
448 logger.debug('Importing file {0}'.format(self.file_name)) |
447 logger.debug('Importing file {0}'.format(self.file_name)) |
449 |
448 |
475 |
474 |
476 self.meteo_time = ancillary.variables['time'][:] |
475 self.meteo_time = ancillary.variables['time'][:] |
477 self.air_temperature_kelvin = ancillary.variable['air_temperature'][:] |
476 self.air_temperature_kelvin = ancillary.variable['air_temperature'][:] |
478 self.air_pressure_hpa = ancillary.variable['air_pressure'][:] |
477 self.air_pressure_hpa = ancillary.variable['air_pressure'][:] |
479 |
478 |
|
479 self.available_channels = [] |
480 for group_name, group in input_file.groups.items(): |
480 for group_name, group in input_file.groups.items(): |
481 channel_name = group_name[8:] # Remove 'channel_' prefix |
481 channel_name = group_name[8:] # Remove 'channel_' prefix |
482 self.channels[channel_name] = DivaChannel(channel_name, group) |
482 self.available_channels.append(channel_name) |
483 |
483 |
|
484 if not header_only: |
|
485 self.channels[channel_name] = DivaChannel(channel_name, group) |
|
486 |
|
487 def import_channel(self, channel_name): |
|
488 """ Import a specific channel. """ |
|
489 if channel_name not in self.available_channels: |
|
490 raise ValueError('Channel {0} not available. Should be one of {1}'.format(channel_name, self.available_channels)) |
|
491 |
|
492 group_name = 'channel_{0}'.format(channel_name) |
|
493 |
|
494 with netcdf.Dataset(self.file_path) as input_file: |
|
495 group = input_file.groups[group_name] |
|
496 self.channels[channel_name] = DivaChannel(channel_name, group) |
484 |
497 |
485 |
498 |
486 class DivaChannel(object): |
499 class DivaChannel(object): |
487 |
500 |
488 def __init__(self, channel_name, group): |
501 def __init__(self, channel_name, group): |
493 channel_name : str |
506 channel_name : str |
494 Name of the group |
507 Name of the group |
495 group : netCDF4.Group object |
508 group : netCDF4.Group object |
496 An open netcdf group to initialize. |
509 An open netcdf group to initialize. |
497 """ |
510 """ |
498 self.group_name = channel_name |
511 self.channel_name = channel_name |
499 |
512 |
500 self. |
513 self.long_name = group.long_name |
501 |
514 self.detector_manufacturer = group.detector_manufacturer |
|
515 self.detector_model = group.detector_model |
|
516 self.daq_manufacturer = group.daq_manufacturer |
|
517 self.daq_model = group.daq_model |
|
518 |
|
519 self.number_of_profiles = len(group.dimensions['profile']) |
|
520 self.number_of_bins = len(group.dimensions['range']) |
|
521 self.channel_id = group.variables['channel_id'][:] |
|
522 self.laser_repetition_rate = group.variables['laser_repetition_rate'][:] |
|
523 |
|
524 self.emission_energy_mJ = group.variables['emission_energy'][:] |
|
525 self.emission_polarization_flag = group.variables['emission_polarization'][:] |
|
526 self.emission_polarization = self._flag_to_polarization(self.emission_polarization_flag) |
|
527 self.field_of_view = group.variables['fov'][:] |
|
528 self.field_of_view_comment = group.variables['fov'].comment |
|
529 |
|
530 self.detector_type_flag = group.variables['detector_type'][:] |
|
531 self.detector_type = self._flag_to_detector_type(self.detector_type_flag) |
|
532 |
|
533 self.detection_mode_flag = group.variables['detection_mode'][:] |
|
534 self.detection_mode = self._flag_to_detector_type(self.detection_mode_flag) |
|
535 |
|
536 self.detection_wavelength_nm = group.variables['detection_wavelength'][:] |
|
537 self.detection_fwhm = group.variables['detection_fwhm'][:] |
|
538 |
|
539 self.detection_polarization_flag = group.variables['detection_polarization'] |
|
540 self.detection_polariation = self._flag_to_detection_polarization(self.detection_polarization_flag) |
|
541 |
|
542 self.polarizer_angle_degrees = group.variables['polarizer_angle'][:] |
|
543 |
|
544 if self.is_photon_counting: |
|
545 self.dead_time_model_flag = group.variables['dead_time_model'][:] |
|
546 self.dead_time_model = self._flag_to_dead_time_model(self.dead_time_model_flag) |
|
547 |
|
548 self.dead_time = group.variables['dead_time'][:] |
|
549 self.dead_time_source = group.variables['dead_time'].comment |
|
550 self.discriminator = group.variables['discriminator'][:] |
|
551 |
|
552 if self.is_analog: |
|
553 self.adc_bits = group.variables['adc_bits'][:] |
|
554 self.adc_range = group.variables['adc_range'][:] |
|
555 |
|
556 self.bin_length_ns = group.variables['bin_length'][:] |
|
557 self.detector_voltage = group.variables['detector_voltage'][:] |
|
558 self.pulses = group.variables['pulses'][:] |
|
559 self.nd_filter_od = group.variables['nd_filter_od'][:] |
|
560 self.trigger_delay_ns = group.variables['trigger_delay'][:] |
|
561 self.time_since_epoch = group.variables['time'][:] |
|
562 |
|
563 self.time = [datetime.datetime.utcfromtimestamp(t) for t in self.time_since_epoch] |
|
564 self.bin_time_ns = group.variables['bin_time'][:] |
|
565 |
|
566 self.signal = group.variables['signal'][:] |
|
567 self.signal_units = group.variables['signal'].units |
|
568 |
|
569 signal_stddev_var = group.variables.pop('signal_stddev', None) |
|
570 |
|
571 if signal_stddev_var: |
|
572 self.signal_stddev = signal_stddev_var[:] |
|
573 else: |
|
574 self.signal_stddev = None |
|
575 |
|
576 def _flag_to_polarization(self, flag): |
|
577 """ Convert polarization flag to str""" |
|
578 if flag not in [0, 1, 2]: |
|
579 logger.warning('Polarization flag has unrecognized value: {0}'.format(flag)) |
|
580 return "" |
|
581 |
|
582 values = {0: 'linear', |
|
583 1: 'circular', |
|
584 2: 'None'} |
|
585 |
|
586 return values[flag] |
|
587 |
|
588 def _flag_to_detector_type(self, flag): |
|
589 """ Convert detector type flag to str""" |
|
590 if flag not in [0, 1]: |
|
591 logger.warning('Detector type flag has unrecognized value: {0}'.format(flag)) |
|
592 return "" |
|
593 |
|
594 values = {0: 'PMT', |
|
595 1: 'APD',} |
|
596 |
|
597 return values[flag] |
|
598 |
|
599 def _flag_to_detection_mode(self, flag): |
|
600 """ Convert detector type flag to str""" |
|
601 if flag not in [0, 1]: |
|
602 logger.warning('Detection mode flag has unrecognized value: {0}'.format(flag)) |
|
603 return "" |
|
604 |
|
605 values = {0: 'analog', |
|
606 1: 'photon counting'} |
|
607 |
|
608 return values[flag] |
|
609 |
|
610 def _flag_to_detection_polarization(self, flag): |
|
611 """ Convert detector type flag to str""" |
|
612 if flag not in [0, 1, 2]: |
|
613 logger.warning('Detection polarization flag has unrecognized value: {0}'.format(flag)) |
|
614 return "" |
|
615 |
|
616 values = {0: 'linear', |
|
617 1: 'circular', |
|
618 2: 'total',} |
|
619 |
|
620 return values[flag] |
|
621 |
|
622 def _flag_to_dead_time_model(self, flag): |
|
623 """ Convert detector type flag to str""" |
|
624 if flag not in [0, 1, 2]: |
|
625 logger.warning('Dead time model flag has unrecognized value: {0}'.format(flag)) |
|
626 return "" |
|
627 |
|
628 values = {0: 'paralyzable', |
|
629 1: 'non_paralyzable', |
|
630 2: 'other', } |
|
631 |
|
632 return values[flag] |
|
633 |
|
634 @property |
|
635 def is_analog(self): |
|
636 return self.detection_mode_flag==0 |
|
637 |
|
638 @property |
|
639 def is_photon_counting(self): |
|
640 return self.detection_mode_flag==1 |