atmospheric_lidar/licel.py

Thu, 29 Mar 2018 17:30:39 +0300

author
Iannis B <ioannis@inoe.ro>
date
Thu, 29 Mar 2018 17:30:39 +0300
changeset 146
968f8c944f90
parent 145
a99ae250e086
child 147
d6a27ff6df88
permissions
-rwxr-xr-x

Minor change in colorbar and warning messages.

ioannis@55 1 import datetime
ioannis@55 2 import logging
i@117 3 import copy
ioannis@55 4
binietoglou@0 5 import numpy as np
i@101 6 import pytz
ioannis@55 7
ulalume3@27 8 from generic import BaseLidarMeasurement, LidarChannel
binietoglou@0 9
i@101 10 logger = logging.getLogger(__name__)
i@101 11
i@116 12 c = 299792458.0 # Speed of light
i@116 13
ioannis@22 14
i@130 15 class LicelChannelData:
i@128 16 """ A class representing a single channel found in a single Licel file."""
i@128 17
i@128 18 def __init__(self, raw_info, raw_data, duration, use_id_as_name=False):
i@128 19 """
i@128 20 This is run when creating a new object.
i@128 21
i@128 22 Parameters
i@128 23 ----------
i@128 24 raw_info : dict
i@128 25 A dictionary containing raw channel information.
i@128 26 raw_data : dict
i@128 27 An array with raw channel data.
i@128 28 duration : float
i@128 29 Duration of the file, in seconds
i@128 30 use_id_as_name : bool
i@128 31 If True, the transient digitizer name (e.g. BT0) is used as a channel
i@128 32 name. If False, a more descriptive name is used (e.g. '01064.o_an').
i@128 33 """
i@128 34 self.raw_info = raw_info
i@128 35 self.raw_data = raw_data
i@128 36 self.duration = duration
i@128 37 self.use_id_as_name = use_id_as_name
i@128 38 self.adcbits = int(raw_info['ADCbits'])
i@129 39 self.active = int(raw_info['active'])
i@129 40 self.analog_photon = raw_info['analog_photon']
i@129 41 self.bin_width = float(raw_info['bin_width'])
i@129 42 self.data_points = int(raw_info['number_of_datapoints'])
i@128 43
i@128 44 self.hv = float(raw_info['HV'])
i@128 45 self.id = raw_info['ID']
i@129 46 self.laser_used = int(raw_info['laser_used'])
i@129 47 self.number_of_shots = int(raw_info['number_of_shots'])
i@129 48 self.wavelength_str = raw_info['wavelength']
i@128 49
i@128 50 if self.is_analog:
i@129 51 self.discriminator = float(raw_info['discriminator']) * 1000 # Analog range in mV
i@128 52 else:
i@129 53 self.discriminator = float(raw_info['discriminator'])
i@128 54
i@128 55 @property
i@130 56 def is_photodiode(self):
i@130 57 return self.id[0:2] == 'PD'
i@130 58
i@130 59 @property
i@128 60 def wavelength(self):
i@128 61 """ Property describing the nominal wavelength of the channel.
i@128 62
i@128 63 Returns
i@128 64 -------
i@128 65 : int or None
i@128 66 The integer value describing the wavelength. If no raw_info have been provided,
i@128 67 returns None.
i@128 68 """
i@128 69 wavelength = self.wavelength_str.split('.')[0]
i@128 70 return int(wavelength)
i@128 71
i@128 72 @property
i@128 73 def channel_name(self):
i@128 74 """
i@128 75 Construct the channel name adding analog photon info to avoid duplicates
i@128 76
i@128 77 If use_id_as_name is True, the channel name will be the transient digitizer ID (e.g. BT01).
i@128 78 This could be useful if the lidar system has multiple telescopes, so the descriptive name is
i@128 79 not unique.
i@128 80
i@128 81 Returns
i@128 82 -------
i@128 83 channel_name : str
i@128 84 The channel name
i@128 85 """
i@128 86 if self.use_id_as_name:
i@128 87 channel_name = self.id
i@128 88 else:
i@128 89 acquisition_type = self.analog_photon_string
i@128 90 channel_name = "%s_%s" % (self.wavelength_str, acquisition_type)
i@128 91 return channel_name
i@128 92
i@128 93 @property
i@128 94 def analog_photon_string(self):
i@128 95 """ Convert the analog/photon flag found in the Licel file to a proper sting.
i@128 96
i@128 97 Returns
i@128 98 -------
i@128 99 string : str
i@128 100 'an' or 'ph' string, for analog or photon-counting channel respectively.
i@128 101 """
i@128 102 if self.analog_photon == '0':
i@128 103 string = 'an'
i@128 104 else:
i@128 105 string = 'ph'
i@128 106 return string
i@128 107
i@128 108 def calculate_physical(self):
i@128 109 """ Calculate physically-meaningful data from raw channel data:
i@128 110
i@128 111 * In case of analog signals, the data are converted to mV.
i@128 112 * In case of photon counting signals, data are stored as number of photons.
i@128 113
i@128 114 In addition, some ancillary variables are also calculated (z, dz, number_of_bins).
i@128 115 """
i@128 116 data = self.raw_data
i@128 117
i@128 118 norm = data / float(self.number_of_shots)
i@129 119 dz = self.bin_width
i@128 120
i@129 121 if self.is_analog:
i@128 122 # If the channel is in analog mode
i@128 123 ADCrange = self.discriminator # Discriminator value already in mV
i@128 124
i@130 125 if self.is_photodiode and (self.adcbits == 0):
ioannis@146 126 logger.warning("Assuming adcbits equal 1. This is a bug in current licel format when storing photodiode data.")
i@130 127 channel_data = norm * ADCrange / (2 ** self.adcbits)
i@130 128 else:
ioannis@146 129 channel_data = norm * ADCrange / ((2 ** self.adcbits) - 1) # Licel LabView code has a bug (does not account -1).
i@130 130
i@128 131 else:
i@130 132 channel_data = norm * self.number_of_shots
i@128 133
i@128 134 # Calculate Z
i@128 135 self.z = np.array([dz * bin_number + dz / 2.0 for bin_number in range(self.data_points)])
i@128 136 self.dz = dz
i@128 137 self.data = channel_data
i@128 138
i@128 139 @property
i@128 140 def is_analog(self):
i@128 141 return self.analog_photon == '0'
i@128 142
i@128 143
i@129 144 class LicelFile(object):
ulalume3@105 145 """ A class representing a single binary Licel file. """
i@116 146
i@129 147 licel_file_header_format = ['filename',
i@129 148 'start_date start_time end_date end_time altitude longitude latitude zenith_angle',
i@125 149 # Appart from Site that is read manually
i@129 150 'LS1 rate_1 LS2 rate_2 number_of_datasets', ]
i@129 151 licel_file_channel_format = 'active analog_photon laser_used number_of_datapoints 1 HV bin_width wavelength d1 d2 d3 d4 ADCbits number_of_shots discriminator ID'
i@125 152
i@130 153 channel_data_class = LicelChannelData
i@125 154
ioannis@144 155 def __init__(self, file_path, use_id_as_name=False, licel_timezone="UTC", import_now=True):
ulalume3@105 156 """
ulalume3@105 157 This is run when creating a new object.
i@128 158
ulalume3@105 159 Parameters
ulalume3@105 160 ----------
ulalume3@105 161 file_path : str
ulalume3@105 162 The path to the Licel file.
ulalume3@105 163 use_id_as_name : bool
ulalume3@105 164 If True, the transient digitizer name (e.g. BT0) is used as a channel
ulalume3@105 165 name. If False, a more descriptive name is used (e.g. '01064.o_an').
ulalume3@105 166 licel_timezone : str
ulalume3@105 167 The timezone of dates found in the Licel files. Should match the available
i@128 168 timezones in the TZ database.
ioannis@144 169 import_file : bool
ioannis@144 170 If True, the header and data are read immediately. If not, the user has to call the
ioannis@144 171 corresponding methods directly. This is used to speed up reading files when only
ioannis@144 172 header information are required.
ulalume3@105 173 """
ulalume3@105 174 self.filename = file_path
ioannis@55 175 self.use_id_as_name = use_id_as_name
binietoglou@0 176 self.start_time = None
binietoglou@0 177 self.stop_time = None
i@102 178 self.licel_timezone = licel_timezone
ulalume3@47 179
ioannis@144 180 if import_now:
ioannis@144 181 self.import_file()
i@130 182
ioannis@144 183 def import_file(self):
i@129 184 """ Read the header info and data of the Licel file.
ulalume3@105 185 """
binietoglou@0 186 channels = {}
i@130 187 photodiodes = {}
binietoglou@33 188
ioannis@146 189 with open(self.filename, 'rb') as f:
binietoglou@33 190
binietoglou@33 191 self.read_header(f)
ulalume3@47 192
binietoglou@33 193 # Check the complete header is read
i@101 194 f.readline()
binietoglou@0 195
binietoglou@33 196 # Import the data
binietoglou@33 197 for current_channel_info in self.channel_info:
i@129 198 raw_data = np.fromfile(f, 'i4', int(current_channel_info['number_of_datapoints']))
binietoglou@33 199 a = np.fromfile(f, 'b', 1)
binietoglou@33 200 b = np.fromfile(f, 'b', 1)
ulalume3@47 201
binietoglou@33 202 if (a[0] != 13) | (b[0] != 10):
ioannis@146 203 logger.warning("No end of line found after record. File could be corrupt: %s" % self.filename)
ioannis@146 204 logger.warning('a: {0}, b: {1}.'.format(a, b))
ulalume3@47 205
i@130 206 channel = self.channel_data_class(current_channel_info, raw_data, self.duration(),
i@130 207 use_id_as_name=self.use_id_as_name)
ulalume3@47 208
i@130 209 # Assign the channel either as normal channel or photodiode
i@130 210 if channel.is_photodiode:
i@130 211 if channel.channel_name in photodiodes.keys():
i@130 212 # Check if current naming convention produces unique files
i@130 213 raise IOError('Trying to import two photodiodes with the same name')
i@130 214 photodiodes[channel.channel_name] = channel
i@130 215 else:
i@130 216 if channel.channel_name in channels.keys():
i@130 217 # Check if current naming convention does not produce unique files
i@130 218 raise IOError('Trying to import two channels with the same name')
i@130 219 channels[channel.channel_name] = channel
ulalume3@47 220
binietoglou@33 221 self.channels = channels
i@130 222 self.photodiodes = photodiodes
ulalume3@47 223
ioannis@144 224 self._calculate_physical()
ioannis@144 225
binietoglou@33 226 def read_header(self, f):
i@128 227 """ Read the header of an open Licel file.
i@128 228
ulalume3@105 229 Parameters
ulalume3@105 230 ----------
ulalume3@105 231 f : file-like object
ulalume3@105 232 An open file object.
ulalume3@105 233 """
ulalume3@47 234 # Read the first 3 lines of the header
binietoglou@0 235 raw_info = {}
binietoglou@33 236 channel_info = []
ulalume3@47 237
binietoglou@33 238 # Read first line
binietoglou@33 239 raw_info['Filename'] = f.readline().strip()
ulalume3@47 240
i@125 241 raw_info.update(self._read_second_header_line(f))
ulalume3@47 242
i@125 243 raw_info.update(self._read_rest_of_header(f))
ulalume3@47 244
binietoglou@33 245 # Update the object properties based on the raw info
i@129 246 start_string = '%s %s' % (raw_info['start_date'], raw_info['start_time'])
i@129 247 stop_string = '%s %s' % (raw_info['end_date'], raw_info['end_time'])
binietoglou@0 248 date_format = '%d/%m/%Y %H:%M:%S'
ulalume3@47 249
i@101 250 try:
i@101 251 logger.debug('Creating timezone object %s' % self.licel_timezone)
i@101 252 timezone = pytz.timezone(self.licel_timezone)
i@101 253 except:
i@101 254 raise ValueError("Cloud not create time zone object %s" % self.licel_timezone)
i@101 255
i@101 256 # According to pytz docs, timezones do not work with default datetime constructor.
i@101 257 local_start_time = timezone.localize(datetime.datetime.strptime(start_string, date_format))
i@101 258 local_stop_time = timezone.localize(datetime.datetime.strptime(stop_string, date_format))
i@101 259
i@101 260 # Only save UTC time.
i@101 261 self.start_time = local_start_time.astimezone(pytz.utc)
i@101 262 self.stop_time = local_stop_time.astimezone(pytz.utc)
i@101 263
i@129 264 self.latitude = float(raw_info['latitude'])
i@129 265 self.longitude = float(raw_info['longitude'])
ulalume3@47 266
binietoglou@0 267 # Read the rest of the header.
i@129 268 for c1 in range(int(raw_info['number_of_datasets'])):
i@125 269 channel_info.append(self.match_lines(f.readline(), self.licel_file_channel_format))
ulalume3@47 270
binietoglou@33 271 self.raw_info = raw_info
binietoglou@33 272 self.channel_info = channel_info
ulalume3@47 273
i@129 274 self._assign_properties()
i@129 275
i@129 276 def _assign_properties(self):
i@129 277 """ Assign properties from the raw_info dictionary. """
i@129 278 self.number_of_datasets = int(self.raw_info['number_of_datasets'])
i@129 279 self.altitude = float(self.raw_info['altitude'])
i@129 280 self.longitude = float(self.raw_info['longitude'])
i@129 281 self.latitude = float(self.raw_info['latitude'])
i@129 282 self.zenith_angle = float(self.raw_info['zenith_angle'])
i@129 283
i@125 284 def _read_second_header_line(self, f):
i@125 285 """ Read the second line of a licel file. """
i@125 286 raw_info = {}
i@125 287
i@125 288 second_line = f.readline()
i@125 289 # Many Licel files don't follow the licel standard. Specifically, the
i@125 290 # measurement site is not always 8 characters, and can include white
i@125 291 # spaces. For this, the site name is detect everything before the first
i@125 292 # date. For efficiency, the first date is found by the first '/'.
i@125 293 # e.g. assuming a string like 'Site name 01/01/2010 ...'
i@125 294
i@125 295 site_name = second_line.split('/')[0][:-2]
i@125 296 clean_site_name = site_name.strip()
i@129 297 raw_info['site'] = clean_site_name
i@125 298
i@125 299 raw_info.update(self.match_lines(second_line[len(clean_site_name) + 1:], self.licel_file_header_format[1]))
i@125 300 return raw_info
i@125 301
i@125 302 def _read_rest_of_header(self, f):
i@125 303 """ Read the rest of the header lines, after line 2. """
i@125 304 # Read third line
i@125 305 third_line = f.readline()
i@125 306 raw_dict = self.match_lines(third_line, self.licel_file_header_format[2])
i@125 307 return raw_dict
i@125 308
ioannis@144 309 def _calculate_physical(self):
ioannis@144 310 """ Calculate physical quantities from raw data for all channels in the file. """
ioannis@144 311 for channel in self.channels.itervalues():
ioannis@144 312 channel.calculate_physical()
ioannis@144 313
ioannis@144 314 for photodiode in self.photodiodes.itervalues():
ioannis@144 315 photodiode.calculate_physical()
ioannis@144 316
ulalume3@27 317 def duration(self):
i@128 318 """ Return the duration of the file.
i@128 319
ulalume3@105 320 Returns
ulalume3@105 321 -------
ulalume3@105 322 : float
ulalume3@105 323 The duration of the file in seconds.
ulalume3@105 324 """
ulalume3@27 325 dt = self.stop_time - self.start_time
ulalume3@27 326 return dt.seconds
ulalume3@47 327
ioannis@144 328 def import_header_only(self):
ioannis@146 329 """ Import only the header lines, without reading the actual data."""
ioannis@146 330 with open(self.filename, 'rb') as f:
ioannis@144 331 self.read_header(f)
ioannis@144 332
i@116 333 @staticmethod
i@116 334 def match_lines(f1, f2):
i@116 335 list1 = f1.split()
i@116 336 list2 = f2.split()
i@116 337
i@116 338 if len(list1) != len(list2):
i@129 339 logging.debug("Channel parameter list has different length from LICEL specifications.")
i@116 340 logging.debug("List 1: %s" % list1)
i@116 341 logging.debug("List 2: %s" % list2)
i@129 342
i@116 343 combined = zip(list2, list1)
i@116 344 combined = dict(combined)
i@116 345 return combined
i@116 346
ulalume3@47 347
i@128 348 class LicelChannel(LidarChannel):
ulalume3@105 349
i@128 350 def __init__(self):
i@128 351 self.name = None
i@128 352 self.resolution = None
i@128 353 self.points = None
i@128 354 self.wavelength = None
i@128 355 self.laser_used = None
i@116 356
i@128 357 self.rc = []
i@128 358 self.raw_info = []
i@128 359 self.laser_shots = []
i@128 360 self.duration = []
i@128 361 self.number_of_shots = []
i@128 362 self.discriminator = []
i@128 363 self.hv = []
i@128 364 self.data = {}
ulalume3@47 365
i@131 366 def append_file(self, current_file, file_channel):
i@128 367 """ Append file to the current object """
ulalume3@47 368
i@131 369 self._assign_properties(current_file, file_channel)
i@128 370
i@128 371 self.binwidth = self.resolution * 2. / c # in seconds
i@128 372 self.z = file_channel.z
ulalume3@47 373
i@131 374 self.data[current_file.start_time] = file_channel.data
i@128 375 self.raw_info.append(file_channel.raw_info)
i@128 376 self.laser_shots.append(file_channel.number_of_shots)
i@128 377 self.duration.append(file_channel.duration)
i@128 378 self.number_of_shots.append(file_channel.number_of_shots)
i@128 379 self.discriminator.append(file_channel.discriminator)
i@128 380 self.hv.append(file_channel.hv)
ulalume3@47 381
i@131 382 def _assign_properties(self, current_file, file_channel):
i@128 383 self._assign_unique_property('name', file_channel.channel_name)
i@128 384 self._assign_unique_property('resolution', file_channel.dz)
i@128 385 self._assign_unique_property('wavelength', file_channel.wavelength)
i@128 386 self._assign_unique_property('points', file_channel.data_points)
i@128 387 self._assign_unique_property('adcbits', file_channel.adcbits)
i@128 388 self._assign_unique_property('active', file_channel.active)
i@129 389 self._assign_unique_property('laser_used', file_channel.laser_used)
i@128 390 self._assign_unique_property('analog_photon_string', file_channel.analog_photon_string)
ulalume3@47 391
i@128 392 def _assign_unique_property(self, property_name, value):
i@128 393
i@128 394 current_value = getattr(self, property_name, None)
i@128 395
i@128 396 if current_value is None:
i@128 397 setattr(self, property_name, value)
binietoglou@0 398 else:
i@128 399 if current_value != value:
i@128 400 raise ValueError('Cannot combine channels with different values of {0}.'.format(property_name))
ioannis@22 401
i@116 402 @property
i@116 403 def is_analog(self):
i@128 404 return self.analog_photon_string == 'an'
i@128 405
i@128 406 @property
i@128 407 def is_photon_counting(self):
i@128 408 return self.analog_photon_string == 'ph'
i@116 409
i@128 410 def __unicode__(self):
i@128 411 return "<Licel channel: %s>" % self.name
i@128 412
i@128 413 def __str__(self):
i@128 414 return unicode(self).encode('utf-8')
i@130 415
i@130 416
i@130 417 class PhotodiodeChannel(LicelChannel):
i@130 418
i@131 419 def _assign_properties(self, current_channel, file_channel):
i@130 420 """ In contrast with normal channels, don't check for constant points."""
i@130 421 self._assign_unique_property('name', file_channel.channel_name)
i@130 422 self._assign_unique_property('resolution', file_channel.dz)
i@130 423 self._assign_unique_property('wavelength', file_channel.wavelength)
i@130 424 self._assign_unique_property('adcbits', file_channel.adcbits)
i@130 425 self._assign_unique_property('active', file_channel.active)
i@130 426 self._assign_unique_property('laser_used', file_channel.laser_used)
i@130 427 self._assign_unique_property('adcbits', file_channel.adcbits)
i@130 428 self._assign_unique_property('analog_photon_string', file_channel.analog_photon_string)
i@130 429
ulalume3@47 430
binietoglou@0 431 class LicelLidarMeasurement(BaseLidarMeasurement):
ulalume3@47 432
i@125 433 file_class = LicelFile
i@125 434 channel_class = LicelChannel
i@130 435 photodiode_class = PhotodiodeChannel
i@125 436
i@101 437 def __init__(self, file_list=None, use_id_as_name=False, licel_timezone='UTC'):
i@116 438 self.raw_info = {} # Keep the raw info from the files
i@116 439 self.durations = {} # Keep the duration of the files
i@116 440 self.laser_shots = []
i@116 441
ulalume3@47 442 self.use_id_as_name = use_id_as_name
i@101 443 self.licel_timezone = licel_timezone
i@130 444 self.photodiodes = {}
i@130 445
ulalume3@92 446 super(LicelLidarMeasurement, self).__init__(file_list)
ulalume3@47 447
ulalume3@92 448 def _import_file(self, filename):
i@130 449
binietoglou@0 450 if filename in self.files:
i@116 451 logger.warning("File has been imported already: %s" % filename)
binietoglou@0 452 else:
i@117 453 logger.debug('Importing file {0}'.format(filename))
i@125 454 current_file = self.file_class(filename, use_id_as_name=self.use_id_as_name, licel_timezone=self.licel_timezone)
ioannis@22 455 self.raw_info[current_file.filename] = current_file.raw_info
ulalume3@27 456 self.durations[current_file.filename] = current_file.duration()
i@104 457
victor@84 458 file_laser_shots = []
ulalume3@47 459
i@125 460 self._create_or_append_channel(current_file)
i@104 461
victor@84 462 self.laser_shots.append(file_laser_shots)
binietoglou@0 463 self.files.append(current_file.filename)
ulalume3@47 464
i@125 465 def _create_or_append_channel(self, current_file):
i@125 466
i@125 467 for channel_name, channel in current_file.channels.items():
i@125 468 if channel_name not in self.channels:
i@125 469 self.channels[channel_name] = self.channel_class()
i@131 470 self.channels[channel_name].append_file(current_file, channel)
i@125 471
i@130 472 for photodiode_name, photodiode in current_file.photodiodes.items():
i@130 473 if photodiode_name not in self.photodiodes:
i@130 474 self.photodiodes[photodiode_name] = self.photodiode_class()
i@131 475 self.photodiodes[photodiode_name].append_file(current_file, photodiode)
i@130 476
binietoglou@0 477 def append(self, other):
binietoglou@0 478
binietoglou@0 479 self.start_times.extend(other.start_times)
binietoglou@0 480 self.stop_times.extend(other.stop_times)
binietoglou@0 481
binietoglou@0 482 for channel_name, channel in self.channels.items():
binietoglou@0 483 channel.append(other.channels[channel_name])
binietoglou@0 484
ioannis@22 485 def _get_duration(self, raw_start_in_seconds):
ulalume3@92 486 """ Return the duration for a given time scale. If only a single
ioannis@22 487 file is imported, then this cannot be guessed from the time difference
ioannis@22 488 and the raw_info of the file are checked.
ulalume3@92 489 """
ulalume3@47 490
ulalume3@47 491 if len(raw_start_in_seconds) == 1: # If only one file imported
ulalume3@47 492 duration = self.durations.itervalues().next() # Get the first (and only) raw_info
ioannis@31 493 duration_sec = duration
ioannis@22 494 else:
ioannis@22 495 duration_sec = np.diff(raw_start_in_seconds)[0]
binietoglou@0 496
ioannis@22 497 return duration_sec
i@104 498
i@116 499 def _get_custom_variables(self, channel_names):
i@116 500
i@116 501 daq_ranges = np.ma.masked_all(len(channel_names))
i@116 502 for n, channel_name in enumerate(channel_names):
i@116 503 channel = self.channels[channel_name]
i@116 504 if channel.is_analog:
i@116 505 unique_values = list(set(channel.discriminator))
i@116 506 if len(unique_values) > 1:
i@116 507 logger.warning('More than one discriminator levels for channel {0}: {1}'.format(channel_name, unique_values))
i@116 508 daq_ranges[n] = unique_values[0]
i@116 509
i@116 510 laser_shots = []
i@116 511 for channel_name in channel_names:
i@116 512 channel = self.channels[channel_name]
i@116 513 laser_shots.append(channel.laser_shots)
i@116 514
ioannis@144 515 try:
ioannis@144 516 laser_shots = np.vstack(laser_shots).T
ioannis@144 517 except Exception as e:
ioannis@144 518 logger.error('Could not read laser shots as an array. Maybe files contain different number of channels?')
ioannis@144 519 raise e
i@116 520
victor@84 521 params = [{
i@104 522 "name": "DAQ_Range",
i@104 523 "dimensions": ('channels',),
i@104 524 "type": 'd',
i@116 525 "values": daq_ranges,
i@104 526 }, {
i@104 527 "name": "Laser_Shots",
i@104 528 "dimensions": ('time', 'channels',),
i@104 529 "type": 'i',
i@116 530 "values": laser_shots,
i@104 531 },
victor@84 532 ]
i@104 533
victor@84 534 return params
i@104 535
i@116 536 def _get_custom_global_attributes(self):
i@116 537 """
i@116 538 NetCDF global attributes that should be included
i@116 539 in the final NetCDF file.
i@116 540
i@116 541 Currently the method assumes that all files in the measurement object have the same altitude, lat and lon
i@116 542 properties.
i@116 543 """
i@117 544 logger.debug('Setting custom global attributes')
i@117 545 logger.debug('raw_info keys: {0}'.format(self.raw_info.keys()))
i@117 546
victor@87 547 params = [{
i@104 548 "name": "Altitude_meter_asl",
i@137 549 "value": float(self.raw_info[self.files[0]]["altitude"])
i@104 550 }, {
i@104 551 "name": "Latitude_degrees_north",
i@137 552 "value": float(self.raw_info[self.files[0]]["latitude"])
i@104 553 }, {
i@104 554 "name": "Longitude_degrees_east",
i@137 555 "value": float(self.raw_info[self.files[0]]["longitude"])
i@104 556 },
victor@87 557 ]
victor@87 558
victor@87 559 return params
ioannis@22 560
i@117 561 def subset_by_channels(self, channel_subset):
i@117 562 """
i@117 563 Create a measurement object containing only a subset of channels.
i@117 564
i@117 565 This method overrides the parent method to add some licel-spefic parameters to the new object.
i@117 566
i@117 567 Parameters
i@117 568 ----------
i@117 569 channel_subset : list
i@117 570 A list of channel names (str) to be included in the new measurement object.
i@117 571
i@117 572 Returns
i@117 573 -------
i@117 574 m : BaseLidarMeasurements object
i@117 575 A new measurements object
i@117 576 """
i@117 577 new_measurement = super(LicelLidarMeasurement, self).subset_by_channels(channel_subset)
i@117 578
i@117 579 new_measurement.raw_info = copy.deepcopy(self.raw_info)
i@117 580 new_measurement.durations = copy.deepcopy(self.durations)
i@117 581 new_measurement.laser_shots = copy.deepcopy(self.laser_shots)
i@117 582
i@117 583 return new_measurement
i@117 584
i@119 585 def subset_by_time(self, channel_subset):
i@119 586 """
i@119 587 Subsetting by time does not work yet with Licel files.
i@117 588
i@119 589 This requires changes in generic.py
i@119 590 """
i@119 591 raise NotImplementedError("Subsetting by time, not yet implemented for Licel files.")

mercurial