def generate_vectors(earth_vel): """ Generate the velocity vectors. This will calculate the magnitude and direction of the water. If any of the data is marked bad in a bin, then the magnitude and direction will also be marked bad. Call this again and set the self.Magnitude and self.Direction when Bottom Track Velocity is available. :param earth_vel: Earth Velocities[bin][beam] :return: [magnitude], [direction] List with a value for each bin """ mag = [] dir = [] for bin_num in range(len(earth_vel)): # Calculate the magnitude and direction mag.append( Ensemble.calculate_magnitude(earth_vel[bin_num][0], earth_vel[bin_num][1], earth_vel[bin_num][2])) dir.append( Ensemble.calculate_direction(earth_vel[bin_num][0], earth_vel[bin_num][1])) return mag, dir
def pd0_beam_vel_mm_per_sec(self, pd0_beam_num: int): """ Convert the Beam Velocity from m/s to mm/s. Also remap the Beam numbers to match PD0 beams. RTB and PD0 do not share the same Beam Order RTB BEAM 0,1,2,3 = PD0 BEAM 3,2,0,1 :param pd0_beam_num: PD0 Beam number. :type pd0_beam_num: Integer :return: Velocity for the given PD0 beam, converted to mm/s for the beam. The beam will be based on reordering for PD0 :rtype: Velocity data for given beam. """ if pd0_beam_num == 0 and pd0_beam_num <= self.NumBeams: if Ensemble.is_bad_velocity(self.BeamVelocity[2]): return -32768 return round(self.BeamVelocity[2] * 1000.0 * -1.0) # Convert to mm/s PD0 0 - RTB 2 if pd0_beam_num == 1 and pd0_beam_num <= self.NumBeams: if Ensemble.is_bad_velocity(self.BeamVelocity[3]): return -32768 return round(self.BeamVelocity[3] * 1000.0 * -1.0) # Convert to mm/s PD0 1 - RTB 3 if pd0_beam_num == 2 and pd0_beam_num <= self.NumBeams: if Ensemble.is_bad_velocity(self.BeamVelocity[1]): return -32768 return round(self.BeamVelocity[1] * 1000.0 * -1.0) # Convert to mm/s PD0 2 - RTB 1 if pd0_beam_num == 3 and pd0_beam_num <= self.NumBeams: if Ensemble.is_bad_velocity(self.BeamVelocity[0]): return -32768 return round(self.BeamVelocity[0] * 1000.0 * -1.0) # Convert to mm/s PD0 3 - RTB 0 return None
def remove_ship_speed(self, ens): """ Store the last good bottom track velocity data. Then use it to remove the ship speed from the Earth velocities and velocity vectors. :param ens: Ensemble data """ if ens.IsBottomTrack and ens.IsEarthVelocity and ens.BottomTrack.NumBeams >= 3: # Check bottom track velocity for good data # If the bt velocity is not bad, then store it for next time # if it is bad, then use the previous good value # East if not Ensemble.is_bad_velocity(ens.BottomTrack.EarthVelocity[0]): self.prev_bt_east = ens.BottomTrack.EarthVelocity[0] # North if not Ensemble.is_bad_velocity(ens.BottomTrack.EarthVelocity[1]): self.prev_bt_north = ens.BottomTrack.EarthVelocity[1] # Vertical if not Ensemble.is_bad_velocity(ens.BottomTrack.EarthVelocity[2]): self.prev_bt_vert = ens.BottomTrack.EarthVelocity[2] # Remove the ship speed ens.EarthVelocity.remove_vessel_speed(bt_east=self.prev_bt_east, bt_north=self.prev_bt_north, bt_vert=self.prev_bt_vert)
def remove_vessel_speed(self, bt_east=0.0, bt_north=0.0, bt_vert=0.0): """ Remove the vessel speed. If the bottom track data is good and the velocity is good, then remove the vessel speed from the earth speed. The bottom track velocity is the vessel velocity. You can also use GPS data as a backup. Calculate the East and North component from the GPS speed bt_east = Convert.ToSingle(speed * Math.Sin(MathHelper.DegreeToRadian(heading))); bt_north = Convert.ToSingle(speed * Math.Cos(MathHelper.DegreeToRadian(heading))); :param bt_east: Bottom Track East velocity :param bt_north: Bottom Track North velocity :param bt_vert: Bottom Track Vertical velocity :return: """ # Remove the vessel speed for bin_num in range(len(self.Velocities)): if not Ensemble.is_bad_velocity(self.Velocities[bin_num][0]): self.Velocities[bin_num][0] = self.Velocities[bin_num][ 0] + bt_east # Remove vessel speed if not Ensemble.is_bad_velocity(self.Velocities[bin_num][1]): self.Velocities[bin_num][1] = self.Velocities[bin_num][ 1] + bt_north # Remove vessel speed if not Ensemble.is_bad_velocity(self.Velocities[bin_num][2]): self.Velocities[bin_num][2] = self.Velocities[bin_num][ 2] + bt_vert # Remove vessel speed # Generate the new vectors after removing the vessel speed self.generate_velocity_vectors()
def pd0_range_cm(self, pd0_beam_num: int): """ Convert the range from meters to centimeters. Remap the Beam numbers to match PD0 beams. RTB and PD0 do not share the same Beam Order RTB BEAM 0,1,2,3 = PD0 BEAM 3,2,0,1 :param pd0_beam_num: PD0 Beam number. :type pd0_beam_num: Integer :return: Ranges for the given PD0 beam, converted to centimeters for the beam. The beam will be based on reordering for PD0 :rtype: Float - Range value. """ if pd0_beam_num == 0 and pd0_beam_num <= self.NumBeams: if Ensemble.is_bad_velocity(self.Range[2]): return -32768 return round(self.Range[2] * 100.0) # PD0 0 - RTB 2 if pd0_beam_num == 1 and pd0_beam_num <= self.NumBeams: if Ensemble.is_bad_velocity(self.Range[3]): return -32768 return round(self.Range[3] * 100.0) # PD0 1 - RTB 3 if pd0_beam_num == 2 and pd0_beam_num <= self.NumBeams: if Ensemble.is_bad_velocity(self.Range[1]): return -32768 return round(self.Range[1] * 100.0) # PD0 2 - RTB 1 if pd0_beam_num == 3 and pd0_beam_num <= self.NumBeams: if Ensemble.is_bad_velocity(self.Range[0]): return -32768 return round(self.Range[0] * 100.0) # PD0 3 - RTB 0 return None
def encode_csv(self, dt, ss_code, ss_config, blank=0, bin_size=0): """ Encode into CSV format. :param dt: Datetime object. :param ss_code: Subsystem code. :param ss_config: Subsystem Configuration :param blank: Blank or first bin position in meters. :param bin_size: Bin size in meters. :return: List of CSV lines. """ str_result = [] # Create the CSV strings str_result.append(Ensemble.gen_csv_line(dt, Ensemble.CSV_FIRST_PING_TIME, ss_code, ss_config, 0, 0, blank, bin_size, self.FirstPingTime)) str_result.append(Ensemble.gen_csv_line(dt, Ensemble.CSV_LAST_PING_TIME, ss_code, ss_config, 0, 0, blank, bin_size, self.LastPingTime)) str_result.append(Ensemble.gen_csv_line(dt, Ensemble.CSV_HEADING, ss_code, ss_config, 0, 0, blank, bin_size, self.Heading)) str_result.append(Ensemble.gen_csv_line(dt, Ensemble.CSV_PITCH, ss_code, ss_config, 0, 0, blank, bin_size, self.Pitch)) str_result.append(Ensemble.gen_csv_line(dt, Ensemble.CSV_ROLL, ss_code, ss_config, 0, 0, blank, bin_size, self.Roll)) str_result.append(Ensemble.gen_csv_line(dt, Ensemble.CSV_WATER_TEMP, ss_code, ss_config, 0, 0, blank, bin_size, self.WaterTemp)) str_result.append(Ensemble.gen_csv_line(dt, Ensemble.CSV_SYS_TEMP, ss_code, ss_config, 0, 0, blank, bin_size, self.SystemTemp)) str_result.append(Ensemble.gen_csv_line(dt, Ensemble.CSV_PRESSURE, ss_code, ss_config, 0, 0, blank, bin_size, self.Pressure)) str_result.append(Ensemble.gen_csv_line(dt, Ensemble.CSV_XDCR_DEPTH, ss_code, ss_config, 0, 0, blank, bin_size, self.TransducerDepth)) str_result.append(Ensemble.gen_csv_line(dt, Ensemble.CSV_SOS, ss_code, ss_config, 0, 0, blank, bin_size, self.SpeedOfSound)) return str_result
def test_ones_compliment(): value = 0x9E result = Ensemble.ones_complement(value) assert 0x61 == result value = 22 result = Ensemble.oness_complement(value) assert 9 == result
def generate_csv_data_no_avg(self, ens): """ Generate the CSV and Dataframe data from the ensemble. This is only called if the user is not averaging the data. :param ens: Ensemble data :return: """ csv_rows = [] dt = datetime.datetime.now() ss_code = 0 ss_config = 0 blank = 0.0 bin_size = 0.0 if ens.IsEnsembleData: dt = ens.EnsembleData.datetime() ss_code = ens.EnsembleData.SysFirmwareSubsystemCode ss_config = ens.EnsembleData.SubsystemConfig if ens.IsAncillaryData: blank = ens.AncillaryData.FirstBinRange bin_size = ens.AncillaryData.BinSize pressure = ens.AncillaryData.Pressure xdcr_depth = ens.AncillaryData.TransducerDepth # Pressure csv_rows.append([ Ensemble.gen_csv_line(dt, Ensemble.CSV_PRESSURE, ss_code, ss_config, 0, 0, blank, bin_size, pressure) ]) # Transducer Depth csv_rows.append([ Ensemble.gen_csv_line(dt, Ensemble.CSV_XDCR_DEPTH, ss_code, ss_config, 0, 0, blank, bin_size, xdcr_depth) ]) if ens.IsRangeTracking: for beam_num in range(len(ens.RangeTracking.Range)): # Range Tracking csv_rows.append([ Ensemble.gen_csv_line(dt, Ensemble.CSV_RT_RANGE, ss_code, ss_config, 0, beam_num, blank, bin_size, ens.RangeTracking.Range[beam_num]) ]) if ens.IsEarthVelocity: csv_rows += (ens.EarthVelocity.encode_csv(dt, ss_code, ss_config, blank, bin_size)) return csv_rows
def get_csv_data(self, data, awc_key, data_type, last_time): """ Append the data to the CSV file. Ex: ["datetime", "data_type", "ss_code", "ss_config", "bin_num", "beam_num", "blank", "bin_size", "value"] 2019/02/23 15:23:22.56, EARTH_VEL, 4, 1, 2, 2, 7.5, 1.245 :param data: Data for all beams. :param awc_key: Key used to give the file an identifier for the subsystem and config. :param data_type: Data type to place into the csv line. :param last_time: Last time in the average. :return: """ # Get the parameters for the data blank = self.awc_dict[awc_key].blank bin_size = self.awc_dict[awc_key].bin_size ss_code = self.awc_dict[awc_key].ss_code ss_config = self.awc_dict[awc_key].ss_config # Use the current time as a backup #dt_time = datetime.datetime.now().strftime('%Y/%m/%d %H:%M:%S:%f') dt_time = datetime.datetime.now() if last_time: dt_time = last_time # Get the data bin_num = 1 beam_num = 0 row_data = [] # Go through each bin and add a line to the csv file for bin_data in data: if type(bin_data) == list: beam_num = 0 for beam_data in bin_data: val = beam_data row_data.append([ (Ensemble.gen_csv_line(dt_time, data_type, ss_code, ss_config, bin_num, beam_num, blank, bin_size, val)) ]) beam_num += 1 else: row_data.append([ (Ensemble.gen_csv_line(dt_time, data_type, ss_code, ss_config, bin_num, beam_num, blank, bin_size, bin_data)) ]) # Increment the bin number bin_num += 1 return row_data
def accum_rt_range(self, awc): """ Create the Range Tracking dataframe. This takes all the information from the Range Tracking and creates a row in the dataframe for each bin,beam value. :param awc: Average data. :return: """ # Convert the east array to df # params: vel_array, dt, ss_code, ss_config, blank, bin_size # DF Columns: Index, time_stamp, ss_code, ss_config, bin_num, beam_num, bin_depth, value df = Ensemble.array_beam_1d_to_df( awc[AverageWaterColumn.INDEX_RANGE_TRACK], awc[AverageWaterColumn.INDEX_LAST_TIME], awc[AverageWaterColumn.INDEX_SS_CODE], awc[AverageWaterColumn.INDEX_SS_CONFIG], awc[AverageWaterColumn.INDEX_FIRST_ENS_NUM], awc[AverageWaterColumn.INDEX_LAST_ENS_NUM]) # Store the range results if self.df_rt_range.empty: self.df_rt_range = df else: self.df_rt_range = pd.concat([self.df_rt_range, df], ignore_index=True) # Average the Range Track Range avg_range = Ensemble.get_avg_range( awc[AverageWaterColumn.INDEX_RANGE_TRACK]) # Create a dict entry dict_result = {} dict_result[0] = { 'time_stamp': awc[AverageWaterColumn.INDEX_LAST_TIME], 'ss_code': awc[AverageWaterColumn.INDEX_SS_CODE], 'ss_config': awc[AverageWaterColumn.INDEX_SS_CONFIG], 'bin_num': 0, 'beam_num': 0, 'bin_depth': avg_range, 'first_ens_num': awc[AverageWaterColumn.INDEX_FIRST_ENS_NUM], 'last_ens_num': awc[AverageWaterColumn.INDEX_LAST_ENS_NUM], 'value': avg_range } # Create the dataframe from the dictionary # important to set the 'orient' parameter to "index" to make the keys as rows df = pd.DataFrame.from_dict(dict_result, "index") # Store the range results if self.df_avg_rt_range.empty: self.df_avg_rt_range = df else: self.df_avg_rt_range = pd.concat([self.df_avg_rt_range, df], ignore_index=True)
def decode(self, data): """ Take the data bytearray. Decode the data to populate the velocities. :param data: Bytearray for the dataset. """ packet_pointer = Ensemble.GetBaseDataSize(self.name_len) for beam in range(self.element_multiplier): for bin_num in range(self.num_elements): self.Velocities[bin_num][beam] = Ensemble.GetFloat(packet_pointer, Ensemble().BytesInFloat, data) packet_pointer += Ensemble().BytesInFloat logging.debug(self.Velocities)
def decode(self, data): """ Take the data bytearray. Decode the data to populate the Good Beams. :param data: Bytearray for the dataset. """ packet_pointer = Ensemble.GetBaseDataSize(self.name_len) for beam in range(self.element_multiplier): for bin_num in range(self.num_elements): self.GoodBeam[bin_num][beam] = Ensemble.GetInt32(packet_pointer, Ensemble().BytesInInt32, data) packet_pointer += Ensemble().BytesInInt32 logging.debug(self.GoodBeam)
def calculate_magnitude(east, north, vertical): """ Calculate the magnitude of the water current. :param east: Earth East Velocity :param north: Earth North Velocity :param vertical: Earth Vertical Velocity :return: Magnitude value """ if not Ensemble.is_bad_velocity(east) and not Ensemble.is_bad_velocity( north) and not Ensemble.is_bad_velocity(vertical): return math.sqrt((east * east) + (north * north) + (vertical * vertical)) else: return Ensemble.BadVelocity
def accum_earth_vel(self, awc): """ Create the Earth Velocity dataframe. This takes all the information from the earth velocity and creates a row in the dataframe for each bin,beam value. :param awc: Average data. :return: """ # Convert the east array to df # params: vel_array, dt, ss_code, ss_config, blank, bin_size # DF Columns: Index, time_stamp, ss_code, ss_config, bin_num, beam_num, bin_depth, value df = Ensemble.array_2d_to_df( awc[AverageWaterColumn.INDEX_EARTH], awc[AverageWaterColumn.INDEX_LAST_TIME], awc[AverageWaterColumn.INDEX_SS_CODE], awc[AverageWaterColumn.INDEX_SS_CONFIG], awc[AverageWaterColumn.INDEX_BLANK], awc[AverageWaterColumn.INDEX_BIN_SIZE], awc[AverageWaterColumn.INDEX_FIRST_ENS_NUM], awc[AverageWaterColumn.INDEX_LAST_ENS_NUM]) # Store the results if self.df_earth.empty: self.df_earth = df else: self.df_earth = pd.concat([self.df_earth, df], ignore_index=True)
def verify_ens_data(ens_data, ens_start=0): """ This will check the checksum and verify it is correct. :param ens_data: Ensemble data. :param ens_start: Start location in the ens_data """ try: # Ensemble Length ens_len = len(ens_data) # Verify at least the minimum number of bytes are available to verify the ensemble if ens_len <= Ensemble().HeaderSize + Ensemble().ChecksumSize: return False # Check Ensemble number ens_num = struct.unpack("I", ens_data[ens_start + 16:ens_start + 20]) # Check ensemble size payload_size = struct.unpack( "I", ens_data[ens_start + 24:ens_start + 28]) # Ensure the entire ensemble is in the buffer if ens_len >= ens_start + Ensemble( ).HeaderSize + payload_size[0] + Ensemble().ChecksumSize: # Check checksum checksum_loc = ens_start + Ensemble( ).HeaderSize + payload_size[0] checksum = struct.unpack( "I", ens_data[checksum_loc:checksum_loc + Ensemble().ChecksumSize]) # Calculate Checksum # Use only the payload for the checksum ens = ens_data[ens_start + Ensemble().HeaderSize:ens_start + Ensemble().HeaderSize + payload_size[0]] calc_checksum = binascii.crc_hqx(ens, 0) # Verify checksum if checksum[0] == calc_checksum: logging.debug(ens_num[0]) return True else: logging.warning( "Ensemble fails checksum. {:#04x} {:#04x}".format( checksum[0], calc_checksum)) return False else: logging.warning("Incomplete ensemble.") return False except Exception as e: logging.error("Error verifying Ensemble. " + str(e)) return False return False
def avg_range(self): """ Return the average range (depth to the bottom). This will determine the good values for the range and average them together. :return: Average range. """ # Average the range return Ensemble.get_avg_range(self.Range)
def encode_csv(self, dt, ss_code, ss_config, blank=0, bin_size=0): """ Encode into CSV format. :param dt: Datetime object. :param ss_code: Subsystem code. :param ss_config: Subsystem Configuration :param blank: Blank or first bin position in meters. :param bin_size: Bin size in meters. :return: List of CSV lines. """ str_result = [] # Create the CSV strings for beams in range(len(self.Range)): str_result.append( Ensemble.gen_csv_line(dt, Ensemble.CSV_RT_RANGE, ss_code, ss_config, 0, beams, blank, bin_size, self.Range[beams])) for beams in range(len(self.Pings)): str_result.append( Ensemble.gen_csv_line(dt, Ensemble.CSV_RT_PINGS, ss_code, ss_config, 0, beams, blank, bin_size, self.Pings[beams])) for beams in range(len(self.BeamVelocity)): str_result.append( Ensemble.gen_csv_line(dt, Ensemble.CSV_RT_BEAM_VEL, ss_code, ss_config, 0, beams, blank, bin_size, self.BeamVelocity[beams])) for beams in range(len(self.InstrumentVelocity)): str_result.append( Ensemble.gen_csv_line(dt, Ensemble.CSV_RT_INSTR_VEL, ss_code, ss_config, 0, beams, blank, bin_size, self.InstrumentVelocity[beams])) for beams in range(len(self.EarthVelocity)): str_result.append( Ensemble.gen_csv_line(dt, Ensemble.CSV_RT_EARTH_VEL, ss_code, ss_config, 0, beams, blank, bin_size, self.EarthVelocity[beams])) return str_result
def encode(self): """ Encode the data into RTB format. :return: """ result = [] # Generate header result += Ensemble.generate_header(self.ds_type, self.num_elements, self.element_multiplier, self.image, self.name_len, self.Name) # Add the data for beam in range(self.element_multiplier): for bin_num in range(self.num_elements): val = self.Correlation[bin_num][beam] result += Ensemble.float_to_bytes(val) return result
def encode(self): """ Encode the data into RTB format. :return: """ result = [] self.num_elements = (8 * int( self.NumBeams)) + 1 # 8 is the number of list plus 1 for NumBeams # Generate header result += Ensemble.generate_header(self.ds_type, self.num_elements, self.element_multiplier, self.image, self.name_len, self.Name) # Add the data result += Ensemble.float_to_bytes(self.NumBeams) for beam in range(len(self.SNR)): result += Ensemble.float_to_bytes(self.SNR[beam]) for beam in range(len(self.Range)): result += Ensemble.float_to_bytes(self.Range[beam]) for beam in range(len(self.Pings)): result += Ensemble.float_to_bytes(self.Pings[beam]) for beam in range(len(self.Amplitude)): result += Ensemble.float_to_bytes(self.Amplitude[beam]) for beam in range(len(self.Correlation)): result += Ensemble.float_to_bytes(self.Correlation[beam]) for beam in range(len(self.BeamVelocity)): result += Ensemble.float_to_bytes(self.BeamVelocity[beam]) for beam in range(len(self.InstrumentVelocity)): result += Ensemble.float_to_bytes(self.InstrumentVelocity[beam]) for beam in range(len(self.EarthVelocity)): result += Ensemble.float_to_bytes(self.EarthVelocity[beam]) return result
def run(self): """ Find the start of an ensemble. Then find the end of the ensemble. Then remove the ensemble from the buffer and process the raw data. :return: """ while self.thread_alive: # Lock to look for the data self.thread_lock.acquire() # Create a buffer to hold the ensemble bin_ens_list = [] timeout = 0 # Wait for the next ensemble added to the buffer self.thread_lock.wait() # Look for first 16 bytes of header ens_start = self.buffer.find(self.DELIMITER) # Verify enough data is in the buffer for an ensemble header while ens_start >= 0 and len( self.buffer) > Ensemble().HeaderSize + ens_start: # Decode the Ensemble bin_ens = self.decode_ensemble(ens_start) # Add the data to the list or count for timeout if len(bin_ens) > 0: # Add it to the list bin_ens_list.append(bin_ens) else: # Timeout if we are only getting bad data timeout += 1 if timeout > 5: logging.warning("Find good ensemble timeout") break # Search if there is a new start location ens_start = self.buffer.find(self.DELIMITER) self.thread_lock.release() # If data was found for an ensemble # Process the ensemble binary data for ens in bin_ens_list: if len(ens) > 0: # Decode data ensemble = BinaryCodec.decode_data_sets(ens) if ensemble: # Publish the ensemble self.process_ensemble(ensemble) else: logging.debug("No Ensemble data found")
def test_generate_header(): value_type = 10 # Float num_elements = 30 # 30 bins element_multiplier = 4 # 4 Beams imag = 0 # NOT USED name_length = 8 # Length of name name = "E000001\0" # Beam Vel name header = Ensemble.generate_header(value_type, num_elements, element_multiplier, imag, name_length, name) # Value type assert 0xA == header[0] assert 0x0 == header[1] assert 0x0 == header[2] assert 0x0 == header[3] # Num Elements assert 0x1E == header[4] assert 0x0 == header[5] assert 0x0 == header[6] assert 0x0 == header[7] # Element Multiplier assert 0x4 == header[8] assert 0x0 == header[9] assert 0x0 == header[10] assert 0x0 == header[11] # Imag assert 0x0 == header[12] assert 0x0 == header[13] assert 0x0 == header[14] assert 0x0 == header[15] # Name Length assert 0x8 == header[16] assert 0x0 == header[17] assert 0x0 == header[18] assert 0x0 == header[19] # Name assert ord('E') == header[20] assert ord('0') == header[21] assert ord('0') == header[22] assert ord('0') == header[23] assert ord('0') == header[24] assert ord('0') == header[25] assert ord('1') == header[26] assert ord('\0') == header[27]
def test_generate_header(): value_type = 10 # Float num_elements = 25 # 25 elements element_multiplier = 1 # no multiplier imag = 0 # NOT USED name_length = 8 # Length of name name = "E000014\0" # System Setup name header = Ensemble.generate_header(value_type, num_elements, element_multiplier, imag, name_length, name) # Value type assert 0xA == header[0] assert 0x0 == header[1] assert 0x0 == header[2] assert 0x0 == header[3] # Num Elements assert 0x19 == header[4] assert 0x0 == header[5] assert 0x0 == header[6] assert 0x0 == header[7] # Element Multiplier assert 0x1 == header[8] assert 0x0 == header[9] assert 0x0 == header[10] assert 0x0 == header[11] # Imag assert 0x0 == header[12] assert 0x0 == header[13] assert 0x0 == header[14] assert 0x0 == header[15] # Name Length assert 0x8 == header[16] assert 0x0 == header[17] assert 0x0 == header[18] assert 0x0 == header[19] # Name assert ord('E') == header[20] assert ord('0') == header[21] assert ord('0') == header[22] assert ord('0') == header[23] assert ord('0') == header[24] assert ord('1') == header[25] assert ord('4') == header[26] assert ord('\0') == header[27]
def calculate_direction(east, north): """ Calculate the direction of the water current. This will return a value between 0 and 360. :param east: Earth East Velocity :param north: Earth North Velocity :return: Direction of the water """ if not Ensemble.is_bad_velocity(east) and not Ensemble.is_bad_velocity( north): bin_dir = (math.atan2(east, north)) * (180.0 / math.pi) # The range is -180 to 180 # This moves it to 0 to 360 if bin_dir < 0.0: bin_dir = 360.0 + bin_dir return bin_dir else: return Ensemble.BadVelocity
def accum_mag_dir(self, awc): """ Create the Magnitude dataframe. This takes all the information from the Magnitude and creates a row in the dataframe for each bin,beam value. :param awc: Average data. :return: """ # Convert the east array to df # params: vel_array, dt, ss_code, ss_config, blank, bin_size # DF Columns: Index, time_stamp, ss_code, ss_config, bin_num, beam_num, bin_depth, value df_mag = Ensemble.array_1d_to_df( awc[AverageWaterColumn.INDEX_MAG], awc[AverageWaterColumn.INDEX_LAST_TIME], awc[AverageWaterColumn.INDEX_SS_CODE], awc[AverageWaterColumn.INDEX_SS_CONFIG], awc[AverageWaterColumn.INDEX_BLANK], awc[AverageWaterColumn.INDEX_BIN_SIZE], awc[AverageWaterColumn.INDEX_FIRST_ENS_NUM], awc[AverageWaterColumn.INDEX_LAST_ENS_NUM]) # Store the mag results if self.df_mag.empty: self.df_mag = df_mag else: self.df_mag = pd.concat([self.df_mag, df_mag], ignore_index=True) df_dir = Ensemble.array_1d_to_df( awc[AverageWaterColumn.INDEX_DIR], awc[AverageWaterColumn.INDEX_LAST_TIME], awc[AverageWaterColumn.INDEX_SS_CODE], awc[AverageWaterColumn.INDEX_SS_CONFIG], awc[AverageWaterColumn.INDEX_BLANK], awc[AverageWaterColumn.INDEX_BIN_SIZE], awc[AverageWaterColumn.INDEX_FIRST_ENS_NUM], awc[AverageWaterColumn.INDEX_LAST_ENS_NUM]) # Store the dir results if self.df_dir.empty: self.df_dir = df_dir else: self.df_dir = pd.concat([self.df_dir, df_dir], ignore_index=True)
def test_generate_header(): value_type = 50 # Byte num_elements = 11 # 17 elements element_multiplier = 1 # no multiplier imag = 0 # NOT USED name_length = 8 # Length of name name = "E000011\0" # Ancillary name header = Ensemble.generate_header(value_type, num_elements, element_multiplier, imag, name_length, name) # Value type assert 0x32 == header[0] assert 0x0 == header[1] assert 0x0 == header[2] assert 0x0 == header[3] # Num Elements assert 0x0B == header[4] assert 0x0 == header[5] assert 0x0 == header[6] assert 0x0 == header[7] # Element Multiplier assert 0x1 == header[8] assert 0x0 == header[9] assert 0x0 == header[10] assert 0x0 == header[11] # Imag assert 0x0 == header[12] assert 0x0 == header[13] assert 0x0 == header[14] assert 0x0 == header[15] # Name Length assert 0x8 == header[16] assert 0x0 == header[17] assert 0x0 == header[18] assert 0x0 == header[19] # Name assert ord('E') == header[20] assert ord('0') == header[21] assert ord('0') == header[22] assert ord('0') == header[23] assert ord('0') == header[24] assert ord('1') == header[25] assert ord('1') == header[26] assert ord('\0') == header[27]
def test_generate_header(): value_type = 20 # Int num_elements = 19 # 19 elements element_multiplier = 1 # no multiplier imag = 0 # NOT USED name_length = 8 # Length of name name = "E000008\0" # Ensemble Dataset name header = Ensemble.generate_header(value_type, num_elements, element_multiplier, imag, name_length, name) # Value type assert 0x14 == header[0] assert 0x0 == header[1] assert 0x0 == header[2] assert 0x0 == header[3] # Num Elements assert 0x13 == header[4] assert 0x0 == header[5] assert 0x0 == header[6] assert 0x0 == header[7] # Element Multiplier assert 0x1 == header[8] assert 0x0 == header[9] assert 0x0 == header[10] assert 0x0 == header[11] # Imag assert 0x0 == header[12] assert 0x0 == header[13] assert 0x0 == header[14] assert 0x0 == header[15] # Name Length assert 0x8 == header[16] assert 0x0 == header[17] assert 0x0 == header[18] assert 0x0 == header[19] # Name assert ord('E') == header[20] assert ord('0') == header[21] assert ord('0') == header[22] assert ord('0') == header[23] assert ord('0') == header[24] assert ord('0') == header[25] assert ord('8') == header[26] assert ord('\0') == header[27]
def avg_range(self): # Average the ranges #range_count = 0 #range_accum = 0.0 #for beam in range(int(self.NumBeams)): # if self.Range[beam] > 0.0: # range_count += 1 # range_accum += self.Range[beam] #if range_count > 0: # return range_accum / range_count #else: # return 0.0 return Ensemble.get_avg_range(self.Range)
def encode_csv(self, dt, ss_code, ss_config, blank, bin_size): """ Encode into CSV format. :param dt: Datetime object. :param ss_code: Subsystem code. :param ss_config: Subsystem Configuration :param blank: Blank or first bin position in meters. :param bin_size: Bin size in meters. :return: List of CSV lines. """ str_result = [] for beam in range(self.element_multiplier): for bin_num in range(self.num_elements): # Get the value val = self.Velocities[bin_num][beam] # Create the CSV string str_result.append([ Ensemble.gen_csv_line(dt, Ensemble.CSV_EARTH_VEL, ss_code, ss_config, bin_num, beam, blank, bin_size, val) ]) # Generate Magnitude and Direction CSV for bin_num in range(self.num_elements): mag = self.Magnitude[bin_num] dir = self.Direction[bin_num] str_result.append([ Ensemble.gen_csv_line(dt, Ensemble.CSV_MAG, ss_code, ss_config, bin_num, 0, blank, bin_size, mag) ]) str_result.append([ Ensemble.gen_csv_line(dt, Ensemble.CSV_DIR, ss_code, ss_config, bin_num, 0, blank, bin_size, dir) ]) return str_result
def avg_range(self, rt): """ Average the Range Tracking data given. This will verify the number of beams is the same between ensembles. If any ensembles have a different number of beams, then a exception will be thrown. This will not average the data if the data is BAD VELOCITY. :param rt: Range Tracking data from each ensemble. :return: Average of all the Range Tracking in the all the ensembles. """ # Determine number of beams num_beams = 0 avg_accum = [] avg_count = [] avg_range = None for ens_range in rt: temp_num_beams = len(ens_range) # Verify the bins and beams has not changed if num_beams == 0: num_beams = temp_num_beams elif num_beams != temp_num_beams: logging.error( "Number of beams is not consistent between ensembles") self.thread_lock.release() raise Exception( "Number of beams is not consistent between ensembles") # Create the average lists if num_beams != 0 and len(avg_accum) == 0: avg_accum = [0 for beam in range(num_beams)] avg_count = [0 for beam in range(num_beams)] avg_range = [0 for beam in range(num_beams)] # Accumulate the data for beam in range(len(ens_range)): if not Ensemble.is_bad_velocity(ens_range[beam]): avg_accum[beam] += ens_range[beam] # Accumulate range avg_count[beam] += 1 # Count good data # Average the data accumulate for beam in range(len(avg_accum)): if avg_count[beam] > 0: # Verify data was accumulated avg_range[beam] = avg_accum[beam] / avg_count[ beam] # Average data return avg_range
def avg_mag_dir(self, data): """ Average the magnitude or direction data given. This will verify the number of bins is the same between ensembles. If any ensembles have a different number of bins, then a exception will be thrown. This will not average the data if the data is BAD VELOCITY. :param data: Magnitude or direction data from each ensemble. :return: Average of all the velocities in the all the ensembles. """ # Determine number of bins and beams num_bins = 0 avg_accum = [] avg_count = [] avg_vel = None for ens_data in data: temp_num_bins = len(ens_data) if num_bins == 0: num_bins = temp_num_bins elif num_bins != temp_num_bins: logging.error( "Number of bins is not consistent between ensembles") self.thread_lock.release() raise Exception( "Number of bins is not consistent between ensembles") # Create the average lists if num_bins != 0 and len(avg_accum) == 0: avg_accum = [0 for b in range(num_bins)] avg_count = [0 for b in range(num_bins)] avg_vel = [0 for b in range(num_bins)] # Accumulate the data for ens_bin in range(len(ens_data)): if not Ensemble.is_bad_velocity(ens_data[ens_bin]): avg_accum[ens_bin] += ens_data[ ens_bin] # Accumulate velocity avg_count[ens_bin] += 1 # Count good data # Average the data accumulate for ens_bin in range(len(avg_accum)): if avg_count[ens_bin] > 0: # Verify data was accumulated avg_vel[ens_bin] = avg_accum[ens_bin] / avg_count[ ens_bin] # Average data return avg_vel