Exemplo n.º 1
0
    def populate_data(self, transect, data_type, threshold, data_extent=None):
        """Computes the normalized values for a single transect.

        Parameters
        ----------
        transect: TransectData
            Object of TransectData
        data_type: str
            Type of data (v, q, V, or Q)
        threshold: int
            Number of data points in an increment for the increment to be valid.
        data_extent: list
            Defines percent of data from start of transect to use, default [0, 100]
        """

        # If the data extent is not defined set data_extent to zero to trigger all data to be used
        if data_extent is None:
            data_extent = [0, 100]
            
        # Determine number of transects to be processed
        # n_cells = np.nan
        # n_ens = np.nan
        
        filename = transect.file_name
        in_transect_idx = transect.in_transect_idx

        depths_selected = getattr(transect.depths, transect.depths.selected)
        cell_depth = np.copy(depths_selected.depth_cell_depth_m[:, in_transect_idx])
        cells_above_sl = transect.w_vel.cells_above_sl[:, in_transect_idx]
        cell_depth[cells_above_sl == False] = np.nan
        depth_ens = np.copy(depths_selected.depth_processed_m[in_transect_idx])

        w_vel_x = np.copy(transect.w_vel.u_processed_mps[:, in_transect_idx])
        w_vel_y = np.copy(transect.w_vel.v_processed_mps[:, in_transect_idx])

        invalid_data = np.logical_not(transect.w_vel.valid_data[0, :, in_transect_idx]).T
        w_vel_x[invalid_data] = np.nan
        w_vel_y[invalid_data] = np.nan
        
        boat_select = getattr(transect.boat_vel, transect.boat_vel.selected)
        if boat_select is not None:
            bt_vel_x = np.copy(boat_select.u_processed_mps[in_transect_idx])
            bt_vel_y = np.copy(boat_select.v_processed_mps[in_transect_idx])
        else:
            bt_vel_x = np.tile([np.nan], transect.boat_vel.bt_vel.u_processed_mps[in_transect_idx].shape)
            bt_vel_y = np.tile([np.nan], transect.boat_vel.bt_vel.u_processed_mps[in_transect_idx].shape)
            
        # Compute normalized cell depth by average depth in each ensemble
        norm_cell_depth = np.divide(cell_depth, depth_ens)
        norm_cell_depth[norm_cell_depth < 0] = np.nan
        # If data type is discharge compute unit discharge for each cell
        if data_type.lower() == 'q':
            # Compute the cross product for each cell
            unit = np.multiply(w_vel_x, bt_vel_y) - np.multiply(w_vel_y, bt_vel_x)
        else:
            # Compute mean velocity components in each ensemble
            w_vel_mean_1 = np.nanmean(w_vel_x, 0)
            w_vel_mean_2 = np.nanmean(w_vel_y, 0)

            # Compute a unit vector
            direction, _ = cart2pol(w_vel_mean_1, w_vel_mean_2)
            unit_vec_1, unit_vec_2 = pol2cart(direction, 1)
            unit_vec = np.vstack([unit_vec_1, unit_vec_2])
            
            # Compute the velocity magnitude in the direction of the mean velocity of each
            # ensemble using the dot product and unit vector
            unit = np.tile([np.nan], w_vel_x.shape)
            for i in range(w_vel_x.shape[0]):
                unit[i, :] = np.vstack([w_vel_x[i, :], w_vel_y[i, :]]).dot(unit_vec)
                                       
        # Compute Total
        unit_total = np.nansum(np.nansum(unit), 0)
        
        # Adjust to positive value
        if unit_total < 0:
            unit *= -1
            
        # Compute normalize unit values
        unit_norm = np.divide(unit, np.abs(np.nanmean(unit, 0)))
        
        # Apply extents if they have been specified
        if data_extent[0] != 0 or data_extent[1] != 100:
            
            # Unit discharge is computed here because the unit norm could be based on velocity
            unit = np.multiply(w_vel_x, bt_vel_y) - np.multiply(w_vel_y, bt_vel_x)
            unit_ens = np.nansum(unit, 0)
            unit_total = np.nancumsum(unit_ens)
            
            # Adjust so total discharge is positive
            if unit_total[-1] < 0:
                unit_total *= -1
                
            # Apply extents
            unit_lower = unit_total[-1] * data_extent[0] / 100
            unit_upper = unit_total[-1] * data_extent[1] / 100
            idx_extent = np.where(np.logical_and(np.greater(unit_total, unit_lower),
                                                 np.less(unit_total, unit_upper)))[0]
            unit_norm = unit_norm[:, idx_extent]
            norm_cell_depth = norm_cell_depth[:, idx_extent]
            
        # If whole profile is negative make positive
        idx_neg1 = np.tile([np.nan], [unit_norm.shape[1], 1])
        idx_neg2 = np.tile([np.nan], [unit_norm.shape[1], 1])
        for c in range(unit_norm.shape[1]):
            idx_neg1[c] = len(np.where(unit_norm[:, c] < 0)[0])
            idx_neg2[c] = len(np.where(np.isnan(unit_norm[:, c]) == False)[0])
        idx_neg = np.squeeze(idx_neg1) == np.squeeze(idx_neg2)
        unit_norm[:, idx_neg] = unit_norm[:, idx_neg] * -1

        # Store results
        self.file_name = filename
        self.data_extent = data_extent
        self.data_type = data_type
        self.cell_depth_normalized = norm_cell_depth
        self.unit_normalized = unit_norm
        self.compute_stats(threshold)
Exemplo n.º 2
0
    def process_vtg(self, v_setting=None):
        """Processes raw vtg data to achieve a velocity for each ensemble containing data.

        Parameters
        ----------
        v_setting: str
            Method to used to compute ensemble velocity.
        """

        # Determine method used to compute ensemble velocity
        if v_setting is None:
            v_setting = self.vtg_velocity_method

        # Use only valid data
        vtg_speed_mps = np.copy(self.raw_vtg_speed_mps)
        vtg_course_deg = np.copy(self.raw_vtg_course_deg)
        vtg_delta_time = np.copy(self.raw_vtg_delta_time)

        # Use mode indicator to identify invalid original data
        idx = np.where(self.raw_vtg_mode_indicator == 'N')
        vtg_speed_mps[idx] = np.nan
        vtg_course_deg[idx] = np.nan
        vtg_delta_time[idx] = np.nan

        # Use average velocity for ensemble velocity
        if v_setting == 'Average':
            # Compute vtg velocity in x y coordinates from speed and course
            direction = azdeg2rad(vtg_course_deg)
            vx, vy = pol2cart(direction, vtg_speed_mps)
            vx[np.logical_and(vx == 0, vy == 0)] = np.nan
            vy[np.isnan(vx)] = np.nan
            vx_mean = np.nanmean(vx, 1)
            vy_mean = np.nanmean(vy, 1)
            self.vtg_velocity_ens_mps = np.vstack([vx_mean.T, vy_mean.T])

        # Use last velocity for ensemble velocity
        elif v_setting == 'End':
            n_ensembles = vtg_speed_mps.shape[0]
            vtg_vel = nans(n_ensembles)
            vtg_dir = nans(n_ensembles)

            for n in range(n_ensembles):
                idx = np.where(~np.isnan(vtg_speed_mps[n, :]))[0]
                if len(idx) > 0:
                    idx = idx[-1]
                else:
                    idx = 0
                vtg_vel[n] = vtg_speed_mps[n, idx]
                vtg_dir[n] = vtg_course_deg[n, idx]

            direction = azdeg2rad(vtg_dir)
            vx, vy = pol2cart(direction, vtg_vel)
            vx[np.logical_and(vx == 0, vy == 0)] = np.nan
            vy[np.isnan(vx)] = np.nan
            self.vtg_velocity_ens_mps = np.vstack([vx, vy])

        # Use first velocity for ensemble velocity
        elif v_setting == 'First':
            n_ensembles = vtg_speed_mps.shape[0]
            vtg_vel = nans(n_ensembles)
            vtg_dir = nans(n_ensembles)

            for n in range(n_ensembles):
                idx = 0
                vtg_vel[n] = vtg_speed_mps[n, idx]
                vtg_dir[n] = vtg_course_deg[n, idx]
            direction = azdeg2rad(vtg_dir)
            vx, vy = pol2cart(direction, vtg_vel)
            vx[np.logical_and(vx == 0, vy == 0)] = np.nan
            vy[np.isnan(vx)] = np.nan
            self.vtg_velocity_ens_mps = np.vstack([vx, vy])

        # Use the velocity with the minimum delta time for the ensemble velocity
        elif v_setting == 'Mindt':
            d_time = np.abs(vtg_delta_time)
            # d_time[d_time==0] = np.nan
            d_time_min = np.nanmin(d_time.T, 0).T

            use = []
            vtg_speed = []
            vtg_dir = []

            for n in range(len(d_time_min)):
                use.append(np.abs(d_time[n, :]) == d_time_min[n])

            use = np.array(use)
            for n in range(len(d_time_min)):
                idx = np.where(use[n, :] == True)[0]
                if len(idx) > 0:
                    idx = idx[0]
                    vtg_speed.append(vtg_speed_mps[n, idx])
                    vtg_dir.append(vtg_course_deg[n, idx])
                else:
                    vtg_speed.append(np.nan)
                    vtg_dir.append(np.nan)

                direction = azdeg2rad(np.array(vtg_dir))
                vx, vy = pol2cart(direction, np.array(vtg_speed))
                self.vtg_velocity_ens_mps = np.vstack([vx, vy])

        # Use velocity selected by external algorithm for ensemble velocity
        elif v_setting == 'External':
            direction = azdeg2rad(self.ext_vtg_course_deg)
            vx, vy = pol2cart(direction, self.ext_vtg_speed_mps)
            self.vtg_velocity_ens_mps = np.vstack([vx.T, vy.T])
Exemplo n.º 3
0
    def stationary_test(self):
        """Processed the stationary moving-bed tests."""

        # Assign data from transect to local variables
        trans_data = self.transect
        in_transect_idx = self.transect.in_transect_idx
        bt_valid = trans_data.boat_vel.bt_vel.valid_data[0, in_transect_idx]

        # Check to see that there is valid bottom track data
        self.messages = []
        if np.nansum(bt_valid) > 0:
            # Assign data to local variables
            wt_u = trans_data.w_vel.u_processed_mps[:, in_transect_idx]
            wt_v = trans_data.w_vel.v_processed_mps[:, in_transect_idx]
            ens_duration = trans_data.date_time.ens_duration_sec[in_transect_idx]
            bt_u = trans_data.boat_vel.bt_vel.u_processed_mps[in_transect_idx]
            bt_v = trans_data.boat_vel.bt_vel.v_processed_mps[in_transect_idx]
            
            bin_depth = trans_data.depths.bt_depths.depth_cell_depth_m[:, in_transect_idx]
            trans_select = getattr(trans_data.depths, trans_data.depths.selected)
            depth_ens = trans_select.depth_processed_m[in_transect_idx]

            nb_u, nb_v, unit_nbu, unit_nbv = self.near_bed_velocity(wt_u, wt_v, depth_ens, bin_depth)
            
            # Compute bottom track parallel to water velocity
            unit_nb_vel = np.vstack([unit_nbu, unit_nbv])
            bt_vel = np.vstack([bt_u, bt_v])
            bt_vel_up_strm = -1 * np.sum(bt_vel * unit_nb_vel, 0)
            bt_up_strm_dist = bt_vel_up_strm * ens_duration
            bt_up_strm_dist_cum = np.nancumsum(bt_up_strm_dist)
            
            # Compute bottom track perpendicular to water velocity
            nb_vel_ang, _ = cart2pol(unit_nbu, unit_nbv)
            nb_vel_unit_cs1, nb_vel_unit_cs2 = pol2cart(nb_vel_ang + np.pi / 2, np.ones(nb_vel_ang.shape))
            nb_vel_unit_cs = np.vstack([nb_vel_unit_cs1, nb_vel_unit_cs2])
            bt_vel_cs = np.sum(bt_vel * nb_vel_unit_cs, 0)
            bt_cs_strm_dist = bt_vel_cs * ens_duration
            bt_cs_strm_dist_cum = np.nancumsum(bt_cs_strm_dist)
            
            # Compute cumulative mean moving bed velocity
            valid_bt_vel_up_strm = np.isnan(bt_vel_up_strm) == False
            mb_vel = np.nancumsum(bt_vel_up_strm) / np.nancumsum(valid_bt_vel_up_strm)
            
            # Compute the average ensemble velocities corrected for moving bed
            if mb_vel[-1] > 0:
                u_corrected = np.add(wt_u, (unit_nb_vel[0, :]) * bt_vel_up_strm)
                v_corrected = np.add(wt_v, (unit_nb_vel[1, :]) * bt_vel_up_strm)
            else:
                u_corrected = wt_u
                v_corrected = wt_v
                
            # Compute the mean of the ensemble magnitudes

            # Mean is computed using magnitudes because if a Streampro with no compass is the data source the change
            # in direction could be either real change in water direction or an uncompensated turn of the floating
            # platform. This approach is the best compromise when there is no compass or the compass is unreliable,
            # which is often why the stationary method is used. A weighted average is used to account for the possible
            # change in cell size within and ensemble for the RiverRay and RiverPro.

            mag = np.sqrt(u_corrected**2 + v_corrected**2)
            depth_cell_size = trans_data.depths.bt_depths.depth_cell_size_m[:, in_transect_idx]
            depth_cell_size[np.isnan(mag)] = np.nan
            mag_w = mag * depth_cell_size
            avg_vel = np.nansum(mag_w) / np.nansum(depth_cell_size)
            pot_error_per = (mb_vel[-1] / avg_vel) * 100
            if pot_error_per < 0:
                pot_error_per = 0   
                
            # Compute percent invalid bottom track
            self.percent_invalid_bt = (np.nansum(bt_valid == False) / len(bt_valid)) * 100
            # Store computed test characteristics
            self.dist_us_m = bt_up_strm_dist_cum[-1]
            self.duration_sec = np.nansum(ens_duration)
            self.compass_diff_deg = []
            self.flow_dir = []
            self.mb_dir = []
            self.flow_spd_mps = avg_vel
            self.mb_spd_mps = mb_vel[-1]
            self.percent_mb = pot_error_per
            self.near_bed_speed_mps = np.sqrt(np.nanmean(nb_u)**2 + np.nanmean(nb_v)**2)
            self.stationary_us_track = bt_up_strm_dist_cum
            self.stationary_cs_track = bt_cs_strm_dist_cum
            self.stationary_mb_vel = mb_vel
            
            # Quality check
            self.test_quality = 'Good'
            # Check duration
            if self.duration_sec < 300:
                self.messages.append('WARNING - Duration of stationary test is less than 5 minutes')
                self.test_quality = 'Warnings'
                
            # Check validity of mean moving-bed velocity
            if self.duration_sec > 60:
                mb_vel_std = np.nanstd(mb_vel[-30:])
                cov = mb_vel_std / mb_vel[-1]
                if cov > 0.25 and mb_vel_std > 0.03:
                    self.messages.append('WARNING - Moving-bed velocity may not be consistent. '
                                         + 'Average maybe inaccurate.')
                    self.test_quality = 'Warnings'
                    
            # Check percentage of invalid BT data
            if np.nansum(ens_duration[valid_bt_vel_up_strm]) <= 120:
                
                self.messages.append('ERROR - Total duration of valid BT data is insufficient for a valid test.')
                self.test_quality = 'Errors'
                self.moving_bed = 'Unknown'
            elif self.percent_invalid_bt > 10:
                self.messages.append('WARNING - Number of ensembles with invalid bottom track exceeds 10%')
                self.test_quality = 'Warnings'
                
            # Determine if the test indicates a moving bed
            if self.test_quality != 'Errors':
                if self.percent_mb > 1:
                    self.moving_bed = 'Yes'
                else:
                    self.moving_bed = 'No'

        else:
            self.messages.append('ERROR - Stationary moving-bed test has no valid bottom track data.')
            self.test_quality = 'Errors'
            self.moving_bed = 'Unknown'