Functionality for plotting scanning lidar functions.

Fri, 14 Dec 2018 16:11:54 +0200

author
Iannis <i.binietoglou@impworks.gr>
date
Fri, 14 Dec 2018 16:11:54 +0200
changeset 174
16cf4d961d02
parent 173
9c29ffa49f2d
child 175
6960ccccc713

Functionality for plotting scanning lidar functions.

atmospheric_lidar/generic.py file | annotate | diff | comparison | revisions
atmospheric_lidar/raymetrics.py file | annotate | diff | comparison | revisions
--- a/atmospheric_lidar/generic.py	Wed Dec 12 18:02:39 2018 +0200
+++ b/atmospheric_lidar/generic.py	Fri Dec 14 16:11:54 2018 +0200
@@ -712,7 +712,7 @@
         duration = np.array(durations)
         return duration
 
-    def calculate_rc(self, idx_min=4000, idx_max=5000):
+    def calculate_rc(self, idx_min=-2000, idx_max=-500):
         """ Calculate range corrected signal.
         
         The background signal is estimated as the mean signal between idx_min and idx_max. 
@@ -726,9 +726,33 @@
         idx_max : int
            Maximum index to calculate background signal.
         """
-        background = np.mean(self.matrix[:, idx_min:idx_max], axis=1)  # Calculate the background from 30000m and above
+        background = np.mean(self.matrix[:, idx_min:idx_max], axis=1)
         self.rc = (self.matrix.transpose() - background).transpose() * (self.z ** 2)
 
+    def noise_mask(self, idx_min=-2000, idx_max=-500, threshold=1.):
+        """ Calculate points that are probably noise.
+
+        To calculate this, we find the max value of the background region. Then we reject all points that
+        are less than `threshold` times this value.
+        Parameters
+        ----------
+        idx_min : int
+           Minimum index to calculate background signal.
+        idx_max : int
+           Maximum index to calculate background signal.
+        threshold : float
+           Threshold value.
+        """
+        background_max = np.max(self.matrix[:, idx_min:idx_max], axis=1)
+        background_mean = np.mean(self.matrix[:, idx_min:idx_max], axis=1)
+        mean_to_max = background_max - background_mean
+
+        noise_limit = background_mean + mean_to_max * threshold
+
+        mask = self.matrix <= noise_limit[:, np.newaxis]
+
+        return mask
+
     def update(self):
         """
         Update the time parameters and data according to the raw input data. 
--- a/atmospheric_lidar/raymetrics.py	Wed Dec 12 18:02:39 2018 +0200
+++ b/atmospheric_lidar/raymetrics.py	Fri Dec 14 16:11:54 2018 +0200
@@ -1,6 +1,10 @@
 """ Code to read Raymetrics version of Licel binary files."""
 import logging
 
+import numpy as np
+import matplotlib as mpl
+from matplotlib import pyplot as plt
+
 from .licel import LicelFile, LicelLidarMeasurement, LicelChannel, PhotodiodeChannel
 
 logger = logging.getLogger(__name__)
@@ -153,6 +157,151 @@
         self._assign_unique_property('zenith_stop', current_file.zenith_stop)
         self._assign_unique_property('zenith_step', current_file.zenith_step)
 
+    def plot_ppi(self, figsize=(8, 4), signal_type='rc', z_min=0., z_max=12000., show_plot=True,
+             cmap=plt.cm.jet, title=None, vmin=0, vmax=1.3 * 10 ** 7, mask_noise=True, noise_threshold=1.):
+        """
+        Plot a vertical project of channel data.
+
+        Parameters
+        ----------
+        figsize : tuple
+           (width, height) of the output figure (inches)
+        signal_type : str
+           If 'rc', the range corrected signal is ploted. Else, the raw signals are used.
+        z_min : float
+           Minimum z range
+        z_max : float
+           Maximum z range
+        show_plot : bool
+           If True, the show_plot command is run.
+        cmap : cmap
+           An instance of a matplotlib colormap to use.
+        z0 : float
+           The ground-level altitude. If provided the plot shows altitude above sea level.
+        title : str
+           Optional title for the plot.
+        vmin : float
+           Minimum value for the color scale.
+        vmax : float
+           Maximum value for the color scale.
+        mask_noise : bool
+           If True, remove noisy bins.
+        noise_threshold : int
+           Threshold to use in the noise masking routine.
+        """
+        fig = plt.figure(figsize=figsize)
+        ax1 = fig.add_subplot(111)
+
+        self.draw_ppi(ax1, cmap=cmap, signal_type=signal_type, z_min=z_min, z_max=z_max, vmin=vmin, vmax=vmax,
+                      mask_noise=mask_noise, noise_threshold=noise_threshold)
+
+        if title:
+            ax1.set_title(title)
+        else:
+            ax1.set_title("PPI scan")
+
+        if show_plot:
+            plt.show()
+
+    def draw_ppi(self, ax1, cmap=plt.cm.jet, signal_type='rc',
+                  z_min=0, z_max=12000., add_colorbar=True, cmap_label='a.u.', cb_format=None,
+                  vmin=0, vmax=1.3 * 10 ** 7, mask_noise=True, noise_threshold=1.):
+        """
+        Draw channel data as a PPI plot.
+
+        Parameters
+        ----------
+        ax1 : axis object
+           The axis object to draw.
+        cmap : cmap
+           An instance of a matplotlib colormap to use.
+        signal_type : str
+           If 'rc', the range corrected signal is ploted. Else, the raw signals are used.
+        z_min : float
+           Minimum z range
+        z_max : float
+           Maximum z range
+        add_colorbar : bool
+           If True, a colorbar will be added to the plot.
+        cmap_label : str
+           Label for the colorbar. Ignored if add_colorbar is False.
+        cb_format : str
+           Colorbar tick format string.
+        vmin : float
+           Minimum value for the color scale.
+        vmax : float
+           Maximum value for the color scale.
+        mask_noise : bool
+           If True, remove noisy bins.
+        noise_threshold : int
+           Threshold to use in the noise masking routine.
+        """
+        if signal_type == 'rc':
+            if len(self.rc) == 0:
+                self.calculate_rc()
+            data = self.rc
+        else:
+            data = self.matrix
+
+        if mask_noise:
+            mask = self.noise_mask(threshold=noise_threshold)
+            data = np.ma.masked_where(mask, data)
+
+        z_min_idx = self._index_at_height(z_min)
+        z_max_idx = self._index_at_height(z_max)
+
+        x, y = self._polar_to_ground(self.z[z_min_idx:z_max_idx], self.azimuth_angles, self.zenith_angles)
+
+        im1 = ax1.pcolormesh(x, y, data[:, z_min_idx:z_max_idx], cmap=cmap, vmin=vmin, vmax=vmax)
+
+        ax1.axis('equal')
+
+        if add_colorbar:
+            if cb_format:
+                cb1 = plt.colorbar(im1, format=cb_format)
+            else:
+                cb1 = plt.colorbar(im1)
+            cb1.ax.set_ylabel(cmap_label)
+
+            # Make the ticks of the colorbar smaller, two points smaller than the default font size
+            cb_font_size = mpl.rcParams['font.size'] - 2
+            for ticklabels in cb1.ax.get_yticklabels():
+                ticklabels.set_fontsize(cb_font_size)
+            cb1.ax.yaxis.get_offset_text().set_fontsize(cb_font_size)
+
+    @staticmethod
+    def _polar_to_ground(z, azimuth, zenith):
+        """
+        Convert polar coordinates to cartesian project for a PPI scan
+
+        Parameters
+        ----------
+        z : array
+           Distance array in meters
+        azimuth : list
+           List of profile azimuth angles in degrees
+        zenith : list
+           List of profile zenith angles in degrees
+
+        Returns
+        -------
+        x : array
+           X axis in meters
+        y : array
+           Y axis in meters
+        """
+        # Generate the mesh
+        zenith_rad = np.deg2rad(zenith)
+        azimuth_rad = np.deg2rad(azimuth)
+
+        Z, Zeniths = np.meshgrid(z, zenith_rad)
+        Z_ground = Z * np.sin(Zeniths)
+
+        x = Z_ground * np.cos(azimuth_rad)[:, np.newaxis]
+        y = Z_ground * np.sin(azimuth_rad)[:, np.newaxis]
+
+        return x, y
+
 
 class ScanningLidarMeasurement(LicelLidarMeasurement):
     """ A class representing a scanning measurement set.

mercurial