atmospheric_lidar/raymetrics.py

changeset 177
580821c7487c
parent 176
34866a2a1aa5
child 178
804313c093a8
equal deleted inserted replaced
176:34866a2a1aa5 177:580821c7487c
92 return raw_info 92 return raw_info
93 93
94 def _assign_properties(self): 94 def _assign_properties(self):
95 """ Assign scanning-specific parameters found in the header as object properties.""" 95 """ Assign scanning-specific parameters found in the header as object properties."""
96 super(ScanningFile, self)._assign_properties() 96 super(ScanningFile, self)._assign_properties()
97 self.azimuth_angle = float(self.raw_info['azimuth_angle']) 97 self.azimuth_angle_raw = float(self.raw_info['azimuth_angle'])
98 self.temperature = float(self.raw_info['temperature']) 98 self.temperature = float(self.raw_info['temperature'])
99 self.pressure = float(self.raw_info['pressure']) 99 self.pressure = float(self.raw_info['pressure'])
100 self.azimuth_start_raw = float(self.raw_info['azimuth_start']) 100 self.azimuth_start_raw = float(self.raw_info['azimuth_start'])
101 self.azimuth_stop_raw = float(self.raw_info['azimuth_stop']) 101 self.azimuth_stop_raw = float(self.raw_info['azimuth_stop'])
102 self.azimuth_step = float(self.raw_info['azimuth_step']) 102 self.azimuth_step = float(self.raw_info['azimuth_step'])
103 self.zenith_start_raw = float(self.raw_info['zenith_start']) 103 self.zenith_start_raw = float(self.raw_info['zenith_start'])
104 self.zenith_stop_raw = float(self.raw_info['zenith_stop']) 104 self.zenith_stop_raw = float(self.raw_info['zenith_stop'])
105 self.zenith_step = float(self.raw_info['zenith_step']) 105 self.zenith_step = float(self.raw_info['zenith_step'])
106 self.azimuth_offset = float(self.raw_info['azimuth_offset']) 106 self.azimuth_offset = float(self.raw_info['azimuth_offset'])
107 107
108 logger.warning("Azimuth offset correction not applied.") 108 self.azimuth_angle = (self.azimuth_angle_raw + self.azimuth_offset) % 360
109 # TODO: Apply azimuth offset correction. 109 self.azimuth_start = (self.azimuth_start_raw + self.azimuth_offset) % 360
110 self.azimuth_start = self.azimuth_start_raw 110 self.azimuth_stop = (self.azimuth_stop_raw + self.azimuth_offset) % 360
111 self.azimuth_stop = self.azimuth_stop_raw
112 111
113 if self.fix_zenith_angle: 112 if self.fix_zenith_angle:
114 logger.debug('Fixing zenith start and zenith stop angles.') 113 logger.debug('Fixing zenith start and zenith stop angles.')
115 self.zenith_start = self._correct_zenith_angle(self.zenith_start_raw) 114 self.zenith_start = self._correct_zenith_angle(self.zenith_start_raw)
116 self.zenith_stop = self._correct_zenith_angle(self.zenith_stop_raw) 115 self.zenith_stop = self._correct_zenith_angle(self.zenith_stop_raw)
132 self.zenith_stop = None 131 self.zenith_stop = None
133 self.zenith_step = None 132 self.zenith_step = None
134 self.azimuth_offset = None 133 self.azimuth_offset = None
135 self.zenith_angles = [] 134 self.zenith_angles = []
136 self.azimuth_angles = [] 135 self.azimuth_angles = []
136 self.temperature = []
137 self.pressure = []
137 138
138 def append_file(self, current_file, file_channel): 139 def append_file(self, current_file, file_channel):
139 """ Keep track of scanning-specific variable properties of each file. """ 140 """ Keep track of scanning-specific variable properties of each file. """
140 super(ScanningChannel, self).append_file(current_file, file_channel) 141 super(ScanningChannel, self).append_file(current_file, file_channel)
141 self.zenith_angles.append(current_file.zenith_angle) 142 self.zenith_angles.append(current_file.zenith_angle)
142 self.azimuth_angles.append(current_file.azimuth_angle) 143 self.azimuth_angles.append(current_file.azimuth_angle)
144 self.temperature.append(current_file.temperature)
145 self.pressure.append(current_file.pressure)
143 146
144 def _assign_properties(self, current_file, file_channel): 147 def _assign_properties(self, current_file, file_channel):
145 """ Assign scanning-specific properties as object properties. Check that these are unique, 148 """ Assign scanning-specific properties as object properties. Check that these are unique,
146 i.e. that all files belong to the same measurements set. 149 i.e. that all files belong to the same measurements set.
147 150
153 A specific LicelChannelData object holding data found in the file. 156 A specific LicelChannelData object holding data found in the file.
154 """ 157 """
155 super(ScanningChannel, self)._assign_properties(current_file, file_channel) 158 super(ScanningChannel, self)._assign_properties(current_file, file_channel)
156 self._assign_unique_property('azimuth_start', current_file.azimuth_start) 159 self._assign_unique_property('azimuth_start', current_file.azimuth_start)
157 self._assign_unique_property('azimuth_stop', current_file.azimuth_stop) 160 self._assign_unique_property('azimuth_stop', current_file.azimuth_stop)
161 self._assign_unique_property('azimuth_start_raw', current_file.azimuth_start_raw)
162 self._assign_unique_property('azimuth_stop_raw', current_file.azimuth_stop_raw)
158 self._assign_unique_property('azimuth_step', current_file.azimuth_step) 163 self._assign_unique_property('azimuth_step', current_file.azimuth_step)
159 self._assign_unique_property('zenith_start', current_file.zenith_start) 164 self._assign_unique_property('zenith_start', current_file.zenith_start)
160 self._assign_unique_property('zenith_stop', current_file.zenith_stop) 165 self._assign_unique_property('zenith_stop', current_file.zenith_stop)
166 self._assign_unique_property('zenith_start_raw', current_file.zenith_start_raw)
167 self._assign_unique_property('zenith_stop_raw', current_file.zenith_stop_raw)
161 self._assign_unique_property('zenith_step', current_file.zenith_step) 168 self._assign_unique_property('zenith_step', current_file.zenith_step)
162 169
163 def plot_ppi(self, figsize=(8, 4), signal_type='rc', z_min=0., z_max=12000., show_plot=True, 170 def plot_ppi(self, figsize=(8, 4), signal_type='rc', z_min=0., z_max=12000., show_plot=True,
164 cmap=plt.cm.jet, title=None, vmin=0, vmax=1.3 * 10 ** 7, mask_noise=True, noise_threshold=1.): 171 cmap=plt.cm.jet, title=None, vmin=0, vmax=1.3 * 10 ** 7, mask_noise=True, noise_threshold=1.):
165 """ 172 """
239 Threshold to use in the noise masking routine. 246 Threshold to use in the noise masking routine.
240 """ 247 """
241 fig = plt.figure(figsize=figsize) 248 fig = plt.figure(figsize=figsize)
242 ax1 = fig.add_subplot(111) 249 ax1 = fig.add_subplot(111)
243 250
244 self.draw_rhi(ax1, cmap=cmap, signal_type=signal_type, z_min=z_min, z_max=z_max, vmin=vmin, vmax=vmax, 251 projection_angle = self.draw_rhi(ax1, cmap=cmap, signal_type=signal_type, z_min=z_min, z_max=z_max, vmin=vmin, vmax=vmax,
245 mask_noise=mask_noise, noise_threshold=noise_threshold) 252 mask_noise=mask_noise, noise_threshold=noise_threshold)
246 253
247 if title: 254 if title:
248 ax1.set_title(title) 255 ax1.set_title(title)
249 else: 256 else:
250 ax1.set_title("RHI scan") 257 ax1.set_title("RHI scan ({0}$^\circ$)".format(projection_angle))
251 258
252 if show_plot: 259 if show_plot:
253 plt.show() 260 plt.show()
254 261
262 def plot_scan(self, figsize=(8, 4), signal_type='rc', z_min=0., z_max=12000., show_plot=True,
263 cmap=plt.cm.jet, vmin=0, vmax=1.3 * 10 ** 7, mask_noise=True, noise_threshold=1., cb_format='%.0e', box=False):
264 """
265 Plot data as RHI and PPI scans.
266
267 Parameters
268 ----------
269 figsize : tuple
270 (width, height) of the output figure (inches)
271 signal_type : str
272 If 'rc', the range corrected signal is ploted. Else, the raw signals are used.
273 z_min : float
274 Minimum z range
275 z_max : float
276 Maximum z range
277 show_plot : bool
278 If True, the show_plot command is run.
279 cmap : cmap
280 An instance of a matplotlib colormap to use.
281 z0 : float
282 The ground-level altitude. If provided the plot shows altitude above sea level.
283 title : str
284 Optional title for the plot.
285 vmin : float
286 Minimum value for the color scale.
287 vmax : float
288 Maximum value for the color scale.
289 mask_noise : bool
290 If True, remove noisy bins.
291 noise_threshold : int
292 Threshold to use in the noise masking routine.
293 """
294 fig = plt.figure(figsize=figsize)
295 ax1 = fig.add_subplot(121)
296 ax2 = fig.add_subplot(122)
297
298 self.draw_ppi(ax1, cmap=cmap, signal_type=signal_type, z_min=z_min, z_max=z_max, vmin=vmin, vmax=vmax,
299 mask_noise=mask_noise, noise_threshold=noise_threshold, add_colorbar=False, cb_format=cb_format,
300 box=box)
301
302 projection_angle = self.draw_rhi(ax2, cmap=cmap, signal_type=signal_type, z_min=z_min, z_max=z_max, vmin=vmin, vmax=vmax,
303 mask_noise=mask_noise, noise_threshold=noise_threshold, cb_format=cb_format, box=box)
304
305
306 fig.suptitle("Channel {0}: {1} - {2}".format(self.name,
307 self.start_time.strftime('%Y%m%dT%H%M'),
308 self.stop_time.strftime('%Y%m%dT%H%M')))
309
310 ax1.set_title('PPI')
311 ax2.set_title("RHI ({0:.1f}$^\circ$)".format(projection_angle))
312
313 plt.tight_layout()
314 plt.subplots_adjust(top=0.85)
315
316 if show_plot:
317 plt.show()
318
319
255 def draw_ppi(self, ax1, cmap=plt.cm.jet, signal_type='rc', 320 def draw_ppi(self, ax1, cmap=plt.cm.jet, signal_type='rc',
256 z_min=0, z_max=12000., add_colorbar=True, cmap_label='a.u.', cb_format=None, 321 z_min=0, z_max=12000., add_colorbar=True, cmap_label='a.u.', cb_format=None,
257 vmin=0, vmax=1.3 * 10 ** 7, mask_noise=True, noise_threshold=1., first_signal_bin=0): 322 vmin=0, vmax=1.3 * 10 ** 7, mask_noise=True, noise_threshold=1., first_signal_bin=0, box=False):
258 """ 323 """
259 Draw channel data as a PPI plot. 324 Draw channel data as a PPI plot.
260 325
261 Parameters 326 Parameters
262 ---------- 327 ----------
289 noise_threshold : int 354 noise_threshold : int
290 Threshold to use in the noise masking routine. 355 Threshold to use in the noise masking routine.
291 first_signal_bin : int 356 first_signal_bin : int
292 First signal bin. Can be used to fix analog bin shift of Licel channels. 357 First signal bin. Can be used to fix analog bin shift of Licel channels.
293 """ 358 """
294 x, y = self._polar_to_ground(self.z, self.azimuth_angles, self.zenith_angles) 359 x, y = self._polar_to_ground(self.z/1000., self.azimuth_angles, self.zenith_angles)
295 360
296 self.draw_projection(ax1, x, y, cmap=cmap, signal_type=signal_type, 361 self.draw_projection(ax1, x, y, cmap=cmap, signal_type=signal_type,
297 z_min=z_min, z_max=z_max, add_colorbar=add_colorbar, cmap_label=cmap_label, 362 z_min=z_min, z_max=z_max, add_colorbar=add_colorbar, cmap_label=cmap_label,
298 cb_format=cb_format, vmin=vmin, vmax=vmax, mask_noise=mask_noise, 363 cb_format=cb_format, vmin=vmin, vmax=vmax, mask_noise=mask_noise,
299 noise_threshold=noise_threshold, first_signal_bin=first_signal_bin) 364 noise_threshold=noise_threshold, first_signal_bin=first_signal_bin)
300 365
366 if box:
367 ax1.set_xlim(-z_max / 1000., z_max / 1000.)
368 ax1.set_ylim(-z_max / 1000., z_max / 1000.)
369
370 ax1.set_ylabel('South-North (km)')
371 ax1.set_xlabel('West-East (km)')
372
301 def draw_rhi(self, ax1, cmap=plt.cm.jet, signal_type='rc', 373 def draw_rhi(self, ax1, cmap=plt.cm.jet, signal_type='rc',
302 z_min=0, z_max=12000., add_colorbar=True, cmap_label='a.u.', cb_format=None, 374 z_min=0, z_max=12000., add_colorbar=True, cmap_label='a.u.', cb_format=None,
303 vmin=0, vmax=1.3 * 10 ** 7, mask_noise=True, noise_threshold=1., first_signal_bin=0): 375 vmin=0, vmax=1.3 * 10 ** 7, mask_noise=True, noise_threshold=1., first_signal_bin=0, box=False):
304 """ 376 """
305 Draw channel data as a PPI plot. 377 Draw channel data as a PPI plot.
306 378
307 Parameters 379 Parameters
308 ---------- 380 ----------
335 noise_threshold : int 407 noise_threshold : int
336 Threshold to use in the noise masking routine. 408 Threshold to use in the noise masking routine.
337 first_signal_bin : int 409 first_signal_bin : int
338 First signal bin. Can be used to fix analog bin shift of Licel channels. 410 First signal bin. Can be used to fix analog bin shift of Licel channels.
339 """ 411 """
340 x, y = self._polar_to_cross_section(self.z, self.azimuth_angles, self.zenith_angles, np.mean(self.azimuth_angles)) 412 projection_angle = np.mean(self.azimuth_angles)
413 x, y = self._polar_to_cross_section(self.z / 1000., self.azimuth_angles, self.zenith_angles, projection_angle)
341 414
342 self.draw_projection(ax1, x, y, cmap=cmap, signal_type=signal_type, 415 self.draw_projection(ax1, x, y, cmap=cmap, signal_type=signal_type,
343 z_min=z_min, z_max=z_max, add_colorbar=add_colorbar, cmap_label=cmap_label, 416 z_min=z_min, z_max=z_max, add_colorbar=add_colorbar, cmap_label=cmap_label,
344 cb_format=cb_format, vmin=vmin, vmax=vmax, mask_noise=mask_noise, 417 cb_format=cb_format, vmin=vmin, vmax=vmax, mask_noise=mask_noise,
345 noise_threshold=noise_threshold, first_signal_bin=first_signal_bin) 418 noise_threshold=noise_threshold, first_signal_bin=first_signal_bin)
419
420 padding = 0.5 # km
421
422 if box:
423 ax1.set_xlim(-z_max / 1000. - padding, z_max / 1000. + padding)
424 ax1.set_ylim(-padding, z_max / 1000. + padding)
425
426 ax1.set_xlabel('Distance (km)')
427 ax1.set_ylabel('Height a.l. (km)')
428
429 return projection_angle
346 430
347 def draw_projection(self, ax1, x, y, cmap=plt.cm.jet, signal_type='rc', 431 def draw_projection(self, ax1, x, y, cmap=plt.cm.jet, signal_type='rc',
348 z_min=0, z_max=12000., add_colorbar=True, cmap_label='a.u.', cb_format=None, 432 z_min=0, z_max=12000., add_colorbar=True, cmap_label='a.u.', cb_format=None,
349 vmin=0, vmax=1.3 * 10 ** 7, mask_noise=True, noise_threshold=1., 433 vmin=0, vmax=1.3 * 10 ** 7, mask_noise=True, noise_threshold=1.,
350 first_signal_bin=0): 434 first_signal_bin=0):
402 data_max_idx = z_max_idx + first_signal_bin 486 data_max_idx = z_max_idx + first_signal_bin
403 487
404 im1 = ax1.pcolormesh(x[:, z_min_idx:z_max_idx], y[:, z_min_idx:z_max_idx], data[:, data_min_idx:data_max_idx], 488 im1 = ax1.pcolormesh(x[:, z_min_idx:z_max_idx], y[:, z_min_idx:z_max_idx], data[:, data_min_idx:data_max_idx],
405 cmap=cmap, vmin=vmin, vmax=vmax) 489 cmap=cmap, vmin=vmin, vmax=vmax)
406 490
407 ax1.axis('equal') 491 ax1.set(adjustable='box', aspect='equal')
408 492
409 if add_colorbar: 493 if add_colorbar:
410 if cb_format: 494 if cb_format:
411 cb1 = plt.colorbar(im1, format=cb_format) 495 cb1 = plt.colorbar(im1, format=cb_format)
412 else: 496 else:
417 cb_font_size = mpl.rcParams['font.size'] - 2 501 cb_font_size = mpl.rcParams['font.size'] - 2
418 for ticklabels in cb1.ax.get_yticklabels(): 502 for ticklabels in cb1.ax.get_yticklabels():
419 ticklabels.set_fontsize(cb_font_size) 503 ticklabels.set_fontsize(cb_font_size)
420 cb1.ax.yaxis.get_offset_text().set_fontsize(cb_font_size) 504 cb1.ax.yaxis.get_offset_text().set_fontsize(cb_font_size)
421 505
506 # Centered axis in center: https://stackoverflow.com/a/31558968
507 # Move left y-axis and bottim x-axis to centre, passing through (0,0)
508 # ax1.spines['left'].set_position('center')
509 # ax1.spines['bottom'].set_position('center')
510 #
511 # # Eliminate upper and right axes
512 # ax1.spines['right'].set_color('none')
513 # ax1.spines['top'].set_color('none')
514 #
515 # # Show ticks in the left and lower axes only
516 # ax1.xaxis.set_ticks_position('bottom')
517 # ax1.yaxis.set_ticks_position('left')
518
519
422 @staticmethod 520 @staticmethod
423 def _polar_to_ground(z, azimuth, zenith): 521 def _polar_to_ground(z, azimuth, zenith):
424 """ 522 """
425 Convert polar coordinates to cartesian project for a PPI scan 523 Convert polar coordinates to cartesian project for a PPI scan
426 524
445 azimuth_rad = np.deg2rad(azimuth) 543 azimuth_rad = np.deg2rad(azimuth)
446 544
447 Z, Zeniths = np.meshgrid(z, zenith_rad) 545 Z, Zeniths = np.meshgrid(z, zenith_rad)
448 Z_ground = Z * np.sin(Zeniths) 546 Z_ground = Z * np.sin(Zeniths)
449 547
450 x = Z_ground * np.cos(azimuth_rad)[:, np.newaxis] 548 x = Z_ground * np.sin(azimuth_rad)[:, np.newaxis]
451 y = Z_ground * np.sin(azimuth_rad)[:, np.newaxis] 549 y = Z_ground * np.cos(azimuth_rad)[:, np.newaxis]
452 550
453 return x, y 551 return x, y
454 552
455 @staticmethod 553 @staticmethod
456 def _polar_to_cross_section(z, azimuth, zenith, cross_section_azimuth): 554 def _polar_to_cross_section(z, azimuth, zenith, cross_section_azimuth):
477 """ 575 """
478 576
479 zenith_rad = np.deg2rad(zenith) 577 zenith_rad = np.deg2rad(zenith)
480 578
481 # The angle between measurements and the cross section plance 579 # The angle between measurements and the cross section plance
482 azimuth_difference_rad = np.deg2rad(azimuth) - cross_section_azimuth 580 azimuth_difference_rad = np.deg2rad(azimuth) - np.deg2rad(cross_section_azimuth)
483 581
484 # Generate the mesh 582 # Generate the mesh
485 Z, Azimuth_differences = np.meshgrid(z, azimuth_difference_rad) 583 Z, Azimuth_differences = np.meshgrid(z, azimuth_difference_rad)
486 Z_cross_section = Z * np.sin(Azimuth_differences) 584
487 585 x = Z * np.sin(zenith_rad)[:, np.newaxis] * np.cos(Azimuth_differences)
488 x = Z_cross_section * np.sin(zenith_rad)[:, np.newaxis] 586 y = Z * np.cos(zenith_rad)[:, np.newaxis]
489 y = Z_cross_section * np.cos(zenith_rad)[:, np.newaxis]
490 587
491 return x, y 588 return x, y
492
493 589
494 590
495 class ScanningLidarMeasurement(LicelLidarMeasurement): 591 class ScanningLidarMeasurement(LicelLidarMeasurement):
496 """ A class representing a scanning measurement set. 592 """ A class representing a scanning measurement set.
497 593

mercurial