Beispiel #1
0
def read_rtb_ensemble(file_path, N=0):
    """
    Read one ensemble from a RTB .ENS file.

    Parameters
    ----------
    file_path : str
        Name and path of the RTB file.
    N : int
        Index value of the ensemble to read.

    Returns
    -------
    rti_python.Ensemble.Ensemble
        Ensemble data object.

    """
    ensemble_starts, ensemble_lengths, data = index_rtb_data(file_path)

    chunk = data[ensemble_starts[N]:ensemble_starts[N] + ensemble_lengths[N]]
    if BinaryCodec.verify_ens_data(chunk):
        ens = BinaryCodec.decode_data_sets(chunk)
    else:
        ens = []

    return ens
    def process_playback_ens(self, ens_bin):
        """
        Process the playback ensemble found.  This will verify the ensemble is good.
        If the data is verified to be a good ensemble, then decode the ensemble and
        pass it to the event handler.
        :param ens_bin: Binary Ensemble data to decode
        :return:
        """
        # Verify the ENS data is good
        # This will check that all the data is there and the checksum is good
        if BinaryCodec.verify_ens_data(ens_bin):
            # Decode the ens binary data
            logging.debug("Decoding binary data to ensemble: " +
                          str(len(ens_bin)))
            ens = BinaryCodec.decode_data_sets(ens_bin)

            if ens.IsEnsembleData:
                logging.debug("Ensemble Found: " +
                              str(ens.EnsembleData.EnsembleNumber))
            else:
                logging.debug("Ensemble Found")

            if ens:
                # Pass the ensemble to the event handler
                self.ensemble_event(ens)
    def process_playback_ens(self, ens_bin):
        # Verify the ENS data is good
        # This will check that all the data is there and the checksum is good
        if BinaryCodec.verify_ens_data(ens_bin):
            # Decode the ens binary data
            ens = BinaryCodec.decode_data_sets(ens_bin)

            # Pass the ensemble
            if ens:
                self.ensemble_rcv(None, ens)
Beispiel #4
0
    def __init__(self, is_udp=False, udp_port=55057):
        if not is_udp:
            self.binary_codec = BinaryCodec()
        else:
            self.binary_codec = BinaryCodecUdp(udp_port)

        # Setup the event handler
        self.binary_codec.ensemble_event += self.process_ensemble
    def process_ens_bin(self, ens_bin, datetime_delta, output_file):
        """
        Process an ensemble.  Given the binary data.  Verify you can decode
        the data.  If you can decode the data, then look for the GPS data associated
        with the time in the ensemble.  If you find a match, merge the GPS data into
        the ADCP data.  Then write the ensemble back to the new file.

        Write the output file in batches to save on writes.
        :param ens_bin: Binary ensemble data to decode.
        :param datetime_delta: Time difference to find the GPS data that matches
        :param output_file: Output file path
        :return:
        """
        # Verify the ENS data is good
        # This will check that all the data is there and the checksum is good
        if BinaryCodec.verify_ens_data(ens_bin):
            # Decode the ens binary data
            ens = BinaryCodec.decode_data_sets(ens_bin)

            # Add the ensemble to the list
            if ens:
                # Find the GPS datetime in the dictionary matching the datetime range
                min_time = (ens.EnsembleData.datetime() -
                            datetime_delta).time()
                max_time = (ens.EnsembleData.datetime() +
                            datetime_delta).time()
                matching_key = self.gps_df[min_time:max_time]

                # Get the last matching key
                nmea_ds = None
                if not matching_key.empty:
                    nmea_ds = matching_key[-1:]['nmea'].item()

                # Add the NMEA data to the file if it exist
                if nmea_ds:
                    ens.AddNmeaData(nmea_ds)

                # Accumulate the buffer
                self.batch_write += ens.encode()

                if len(self.batch_write) > 4096 * 30:
                    # Write the ensemble to the file
                    output_file.write(self.batch_write)

                    # Clear the buffer
                    self.batch_write = bytes()
    def process_playback_ens(self, ens_bin):
        """
        Process the playback ensemble found.  This will verify the ensemble is good.
        If the data is verified to be a good ensemble, then decode the ensemble and
        pass it to the event handler.
        :param ens_bin: Binary Ensemble data to decode
        :return:
        """
        # Verify the ENS data is good
        # This will check that all the data is there and the checksum is good
        if BinaryCodec.verify_ens_data(ens_bin):
            # Decode the ens binary data
            ens = BinaryCodec.decode_data_sets(ens_bin)

            if ens:
                # Pass the ensemble to the event handler
                self.ensemble_event(ens)
Beispiel #7
0
def test_encode_decode():

    num_bins = 33
    num_beams = 4

    ens = Ensemble()

    ens_ds = EnsembleData()
    ens_ds.EnsembleNumber = 2668
    ens_ds.NumBins = 33
    ens_ds.NumBeams = 4
    ens_ds.DesiredPingCount = 45
    ens_ds.ActualPingCount = 46
    ens_ds.SerialNumber = "01H00000000000000000000000999999"
    ens_ds.SysFirmwareMajor = 2
    ens_ds.SysFirmwareMinor = 11
    ens_ds.SysFirmwareRevision = 5
    ens_ds.SysFirmwareSubsystemCode = "A"
    ens_ds.SubsystemConfig = 3
    ens_ds.Status = 9
    ens_ds.Year = 2019
    ens_ds.Month = 3
    ens_ds.Day = 9
    ens_ds.Hour = 12
    ens_ds.Minute = 23
    ens_ds.Second = 24
    ens_ds.HSec = 33

    anc = AncillaryData()
    anc.FirstBinRange = 1.0  # Blank.  Depth to the first bin in meters.
    anc.BinSize = 3.0  # Size of a bin in meters.
    anc.FirstPingTime = 1.2  # First Ping Time in seconds.
    anc.LastPingTime = 2.3  # Last Ping Time in seconds.  (If averaging pings, this will be the last ping)
    anc.Heading = 23.5  # Heading in degrees.
    anc.Pitch = 13.6  # Pitch in degrees.
    anc.Roll = 11.25  # Roll in degrees.
    anc.WaterTemp = 25.3  # Water Temperature in fahrenheit
    anc.SystemTemp = 54.6  # System Temperature in fahrenheit
    anc.Salinity = 35.0  # Water Salinity set by the user in PPT
    anc.Pressure = 23.78  # Pressure from pressure sensor in Pascals
    anc.TransducerDepth = 45.69  # Transducer Depth, used by Pressure sensor in meters
    anc.SpeedOfSound = 1400.23  # Speed of Sound in m/s.
    anc.RawMagFieldStrength = 3.0  # Raw magnetic field strength
    anc.PitchGravityVector = 4.0  # Pitch Gravity Vector
    anc.RollGravityVector = 5.0  # Roll Gravity Vector
    anc.VerticalGravityVector = 6.0  # Vertical Gravity Vector

    amp = Amplitude(num_bins, num_beams)
    corr = Correlation(num_bins, num_beams)
    beam_vel = BeamVelocity(num_bins, num_beams)
    inst_vel = InstrumentVelocity(num_bins, num_beams)
    earth_vel = EarthVelocity(num_bins, num_beams)
    gb = GoodBeam(num_bins, num_beams)
    ge = GoodEarth(num_bins, num_beams)
    val = 1.0
    for beam in range(amp.element_multiplier):
        for bin_num in range(amp.num_elements):
            amp.Amplitude[bin_num][beam] = val
            corr.Correlation[bin_num][beam] = val
            beam_vel.Velocities[bin_num][beam] = val
            inst_vel.Velocities[bin_num][beam] = val
            earth_vel.Velocities[bin_num][beam] = val
            gb.GoodBeam[bin_num][beam] = 1 * int(beam)
            ge.GoodEarth[bin_num][beam] = 1 * int(beam)
            val += 1.1

    bt = BottomTrack()
    bt.FirstPingTime = 12.5
    bt.LastPingTime = 12.8
    bt.Heading = 152.36
    bt.Pitch = 12.6
    bt.Roll = 223.1
    bt.WaterTemp = 15.23
    bt.SystemTemp = 78.58
    bt.Salinity = 35.0
    bt.Pressure = 23.36
    bt.TransducerDepth = 156.2
    bt.SpeedOfSound = 1402.36
    bt.Status = 9.0
    bt.NumBeams = 4.0
    bt.ActualPingCount = 23
    bt.Range = [1.1, 2.2, 3.3, 4.4]
    bt.SNR = [1.1, 2.2, 3.3, 4.4]
    bt.Amplitude = [1.1, 2.2, 3.3, 4.4]
    bt.Correlation = [1.1, 2.2, 3.3, 4.4]
    bt.BeamVelocity = [1.1, 2.2, 3.3, 4.4]
    bt.BeamGood = [1, 2, 3, 4]
    bt.InstrumentVelocity = [1.1, 2.2, 3.3, 4.4]
    bt.InstrumentGood = [1, 2, 3, 4]
    bt.EarthVelocity = [1.1, 2.2, 3.3, 4.4]
    bt.EarthGood = [1, 2, 3, 4]
    bt.SNR_PulseCoherent = [1, 2, 3, 4]
    bt.Amp_PulseCoherent = [1, 2, 3, 4]
    bt.Vel_PulseCoherent = [1, 2, 3, 4]
    bt.Noise_PulseCoherent = [1, 2, 3, 4]
    bt.Corr_PulseCoherent = [1, 2, 3, 4]

    rt = RangeTracking()
    rt.NumBeams = 4.0
    rt.Range = [1.1, 2.2, 3.3, 4.4]
    rt.Pings = [1, 2, 3, 4]
    rt.SNR = [1.1, 2.2, 3.3, 4.4]
    rt.Amplitude = [1.1, 2.2, 3.3, 4.4]
    rt.Correlation = [1.1, 2.2, 3.3, 4.4]
    rt.BeamVelocity = [1.1, 2.2, 3.3, 4.4]
    rt.InstrumentVelocity = [1.1, 2.2, 3.3, 4.4]
    rt.EarthVelocity = [1.1, 2.2, 3.3, 4.4]

    nmea = NmeaData()
    nmea.add_nmea("$HEHDT,244.39,T*17\n")
    nmea.add_nmea(
        "$GPGGA,195949.00,3254.8103248,N,11655.5779629,W,2,08,1.1,222.174,M,-32.602,M,6.0,0138*75\n"
    )
    nmea.add_nmea("$GPVTG,306.20,T,294.73,M,0.13,N,0.24,K,D*2E\n")
    nmea.add_nmea("$HEHDT,244.36,T*18\n")

    ss = SystemSetup()
    ss.BtSamplesPerSecond = 1.0
    ss.BtSystemFreqHz = 3.0
    ss.BtCPCE = 1.2
    ss.BtNCE = 2.3
    ss.BtRepeatN = 23.5
    ss.WpSamplesPerSecond = 13.6
    ss.WpSystemFreqHz = 11.25
    ss.WpCPCE = 25.3
    ss.WpNCE = 54.6
    ss.WpRepeatN = 35.0
    ss.WpLagSamples = 23.78
    ss.Voltage = 45.69
    ss.XmtVoltage = 1400.23
    ss.BtBroadband = 3.0
    ss.BtLagLength = 4.0
    ss.BtNarrowband = 5.0
    ss.BtBeamMux = 6.0
    ss.WpBroadband = 6.0
    ss.WpLagLength = 6.0
    ss.WpTransmitBandwidth = 6.0
    ss.WpReceiveBandwidth = 6.0

    ens.AddAmplitude(amp)
    ens.AddCorrelation(corr)
    ens.AddBeamVelocity(beam_vel)
    ens.AddInstrumentVelocity(inst_vel)
    ens.AddEarthVelocity(earth_vel)
    ens.AddGoodBeam(gb)
    ens.AddGoodEarth(ge)
    ens.AddAncillaryData(anc)
    ens.AddEnsembleData(ens_ds)
    ens.AddBottomTrack(bt)
    ens.AddRangeTracking(rt)
    ens.AddSystemSetup(ss)
    ens.AddNmeaData(nmea)

    # Encode the ensemble to binar
    binary_ens = ens.encode()

    # Use the codec to decode the data
    ens1 = BinaryCodec.decode_data_sets(binary_ens[:-4])  # Remove the checksum

    assert ens.EnsembleData.EnsembleNumber == ens1.EnsembleData.EnsembleNumber
    assert ens.EnsembleData.NumBins == ens1.EnsembleData.NumBins
    assert ens.EnsembleData.NumBeams == ens1.EnsembleData.NumBeams
    assert ens.EnsembleData.DesiredPingCount == ens1.EnsembleData.DesiredPingCount
    assert ens.EnsembleData.ActualPingCount == ens1.EnsembleData.ActualPingCount
    assert ens.EnsembleData.SerialNumber == ens1.EnsembleData.SerialNumber
    assert ens.EnsembleData.SysFirmwareMajor == ens1.EnsembleData.SysFirmwareMajor
    assert ens.EnsembleData.SysFirmwareMinor == ens1.EnsembleData.SysFirmwareMinor
    assert ens.EnsembleData.SysFirmwareRevision == ens1.EnsembleData.SysFirmwareRevision
    assert ens.EnsembleData.SysFirmwareSubsystemCode == ens1.EnsembleData.SysFirmwareSubsystemCode
    assert ens.EnsembleData.SubsystemConfig == ens1.EnsembleData.SubsystemConfig
    assert ens.EnsembleData.Status == ens1.EnsembleData.Status
    assert ens.EnsembleData.Year == ens1.EnsembleData.Year
    assert ens.EnsembleData.Month == ens1.EnsembleData.Month
    assert ens.EnsembleData.Day == ens1.EnsembleData.Day
    assert ens.EnsembleData.Hour == ens1.EnsembleData.Hour
    assert ens.EnsembleData.Minute == ens1.EnsembleData.Minute
    assert ens.EnsembleData.Second == ens1.EnsembleData.Second
    assert ens.EnsembleData.HSec == ens1.EnsembleData.HSec

    assert anc.FirstBinRange == pytest.approx(ens1.AncillaryData.FirstBinRange,
                                              0.1)
    assert anc.BinSize == pytest.approx(ens1.AncillaryData.BinSize, 0.1)
    assert anc.FirstPingTime == pytest.approx(ens1.AncillaryData.FirstPingTime,
                                              0.1)
    assert anc.LastPingTime == pytest.approx(ens1.AncillaryData.LastPingTime,
                                             0.1)
    assert anc.Heading == pytest.approx(ens1.AncillaryData.Heading, 0.1)
    assert anc.Pitch == pytest.approx(ens1.AncillaryData.Pitch, 0.1)
    assert anc.Roll == pytest.approx(ens1.AncillaryData.Roll, 0.1)
    assert anc.WaterTemp == pytest.approx(ens1.AncillaryData.WaterTemp, 0.1)
    assert anc.SystemTemp == pytest.approx(ens1.AncillaryData.SystemTemp, 0.1)
    assert anc.Salinity == pytest.approx(ens1.AncillaryData.Salinity, 0.1)
    assert anc.Pressure == pytest.approx(ens1.AncillaryData.Pressure, 0.1)
    assert anc.TransducerDepth == pytest.approx(
        ens1.AncillaryData.TransducerDepth, 0.1)
    assert anc.SpeedOfSound == pytest.approx(ens1.AncillaryData.SpeedOfSound,
                                             0.1)
    assert anc.RawMagFieldStrength == pytest.approx(
        ens1.AncillaryData.RawMagFieldStrength, 0.1)
    assert anc.PitchGravityVector == pytest.approx(
        ens1.AncillaryData.PitchGravityVector, 0.1)
    assert anc.RollGravityVector == pytest.approx(
        ens1.AncillaryData.RollGravityVector, 0.1)
    assert anc.VerticalGravityVector == pytest.approx(
        ens1.AncillaryData.VerticalGravityVector, 0.1)

    for beam in range(amp.element_multiplier):
        for bin_num in range(amp.num_elements):
            assert amp.Amplitude[bin_num][beam] == pytest.approx(
                ens1.Amplitude.Amplitude[bin_num][beam], 0.1)

    for beam in range(corr.element_multiplier):
        for bin_num in range(corr.num_elements):
            assert corr.Correlation[bin_num][beam] == pytest.approx(
                ens1.Correlation.Correlation[bin_num][beam], 0.1)

    for beam in range(beam_vel.element_multiplier):
        for bin_num in range(beam_vel.num_elements):
            assert beam_vel.Velocities[bin_num][beam] == pytest.approx(
                ens1.Wt.Velocities[bin_num][beam], 0.1)

    for beam in range(beam_vel.element_multiplier):
        for bin_num in range(beam_vel.num_elements):
            assert inst_vel.Velocities[bin_num][beam] == pytest.approx(
                ens1.InstrumentVelocity.Velocities[bin_num][beam], 0.1)

    for beam in range(beam_vel.element_multiplier):
        for bin_num in range(beam_vel.num_elements):
            assert earth_vel.Velocities[bin_num][beam] == pytest.approx(
                ens1.EarthVelocity.Velocities[bin_num][beam], 0.1)

    #for beam in range(gb.element_multiplier):
    #    for bin_num in range(gb.num_elements):
    #        assert gb.GoodBeam[bin_num][beam] == pytest.approx(ens1.GoodBeam.GoodBeam[bin_num][beam], 0.1)

    for beam in range(ge.element_multiplier):
        for bin_num in range(ge.num_elements):
            assert ge.GoodEarth[bin_num][beam] == pytest.approx(
                ens1.GoodEarth.GoodEarth[bin_num][beam], 0.1)

    assert bt.FirstPingTime == pytest.approx(ens1.BottomTrack.FirstPingTime)
    assert bt.LastPingTime == pytest.approx(ens1.BottomTrack.LastPingTime)
    assert bt.Heading == pytest.approx(ens1.BottomTrack.Heading)
    assert bt.Pitch == pytest.approx(ens1.BottomTrack.Pitch)
    assert bt.Roll == pytest.approx(ens1.BottomTrack.Roll)
    assert bt.WaterTemp == pytest.approx(ens1.BottomTrack.WaterTemp)
    assert bt.SystemTemp == pytest.approx(ens1.BottomTrack.SystemTemp)
    assert bt.Salinity == pytest.approx(ens1.BottomTrack.Salinity)
    assert bt.Pressure == pytest.approx(ens1.BottomTrack.Pressure)
    assert bt.TransducerDepth == pytest.approx(
        ens1.BottomTrack.TransducerDepth)
    assert bt.SpeedOfSound == pytest.approx(ens1.BottomTrack.SpeedOfSound)
    assert bt.Status == pytest.approx(ens1.BottomTrack.Status)
    assert bt.NumBeams == pytest.approx(ens1.BottomTrack.NumBeams)
    assert bt.ActualPingCount == pytest.approx(
        ens1.BottomTrack.ActualPingCount)
    assert bt.Range == pytest.approx(ens1.BottomTrack.Range)
    assert bt.SNR == pytest.approx(ens1.BottomTrack.SNR)
    assert bt.Amplitude == pytest.approx(ens1.BottomTrack.Amplitude)
    assert bt.Correlation == pytest.approx(ens1.BottomTrack.Correlation)
    assert bt.BeamVelocity == pytest.approx(ens1.BottomTrack.Wt)
    assert bt.BeamGood == pytest.approx(ens1.BottomTrack.BeamGood, 0.1)
    assert bt.InstrumentVelocity == pytest.approx(
        ens1.BottomTrack.InstrumentVelocity)
    assert bt.InstrumentGood == pytest.approx(ens1.BottomTrack.InstrumentGood,
                                              0.1)
    assert bt.EarthVelocity == pytest.approx(ens1.BottomTrack.EarthVelocity)
    assert bt.EarthGood == pytest.approx(ens1.BottomTrack.EarthGood, 0.1)
    assert bt.SNR_PulseCoherent == pytest.approx(
        ens1.BottomTrack.SNR_PulseCoherent, 0.1)
    assert bt.Amp_PulseCoherent == pytest.approx(
        ens1.BottomTrack.Amp_PulseCoherent, 0.1)
    assert bt.Vel_PulseCoherent == pytest.approx(
        ens1.BottomTrack.Vel_PulseCoherent, 0.1)
    assert bt.Noise_PulseCoherent == pytest.approx(
        ens1.BottomTrack.Noise_PulseCoherent, 0.1)
    assert bt.Corr_PulseCoherent == pytest.approx(
        ens1.BottomTrack.Corr_PulseCoherent, 0.1)

    assert rt.NumBeams == ens1.RangeTracking.NumBeams
    assert rt.Range == pytest.approx(ens1.RangeTracking.Range)
    assert rt.SNR == pytest.approx(ens1.RangeTracking.SNR)
    assert rt.Amplitude == pytest.approx(ens1.RangeTracking.Amplitude)
    assert rt.Correlation == pytest.approx(ens1.RangeTracking.Correlation)
    assert rt.BeamVelocity == pytest.approx(ens1.RangeTracking.Wt)
    assert rt.InstrumentVelocity == pytest.approx(
        ens1.RangeTracking.InstrumentVelocity)
    assert rt.EarthVelocity == pytest.approx(ens1.RangeTracking.EarthVelocity)

    assert nmea.nmea_sentences == ens1.NmeaData.nmea_sentences

    assert ss.BtSamplesPerSecond == pytest.approx(
        ens1.SystemSetup.BtSamplesPerSecond, 0.1)
    assert ss.BtSystemFreqHz == pytest.approx(ens1.SystemSetup.BtSystemFreqHz,
                                              0.1)
    assert ss.BtCPCE == pytest.approx(ens1.SystemSetup.BtCPCE, 0.1)
    assert ss.BtNCE == pytest.approx(ens1.SystemSetup.BtNCE, 0.1)
    assert ss.BtRepeatN == pytest.approx(ens1.SystemSetup.BtRepeatN, 0.1)
    assert ss.WpSamplesPerSecond == pytest.approx(
        ens1.SystemSetup.WpSamplesPerSecond, 0.1)
    assert ss.WpSystemFreqHz == pytest.approx(ens1.SystemSetup.WpSystemFreqHz,
                                              0.1)
    assert ss.WpCPCE == pytest.approx(ens1.SystemSetup.WpCPCE, 0.1)
    assert ss.WpNCE == pytest.approx(ens1.SystemSetup.WpNCE, 0.1)
    assert ss.WpRepeatN == pytest.approx(ens1.SystemSetup.WpRepeatN, 0.1)
    assert ss.WpLagSamples == pytest.approx(ens1.SystemSetup.WpLagSamples, 0.1)
    assert ss.Voltage == pytest.approx(ens1.SystemSetup.Voltage, 0.1)
    assert ss.XmtVoltage == pytest.approx(ens1.SystemSetup.XmtVoltage, 0.1)
    assert ss.BtBroadband == pytest.approx(ens1.SystemSetup.BtBroadband, 0.1)
    assert ss.BtLagLength == pytest.approx(ens1.SystemSetup.BtLagLength, 0.1)
    assert ss.BtNarrowband == pytest.approx(ens1.SystemSetup.BtNarrowband, 0.1)
    assert ss.BtBeamMux == pytest.approx(ens1.SystemSetup.BtBeamMux, 0.1)
    assert ss.WpBroadband == pytest.approx(ens1.SystemSetup.WpBroadband, 0.1)
    assert ss.WpLagLength == pytest.approx(ens1.SystemSetup.WpLagLength, 0.1)
    assert ss.WpTransmitBandwidth == pytest.approx(
        ens1.SystemSetup.WpTransmitBandwidth, 0.1)
    assert ss.WpReceiveBandwidth == pytest.approx(
        ens1.SystemSetup.WpReceiveBandwidth, 0.1)
Beispiel #8
0
def read_rtb_file(file_path):
    """
    Read data from one RTB .ENS file into xarray.

    Parameters
    ----------
    file_path : str
        File name and path.

    Returns
    -------
    xarray.Dataset
        As organized by mxtoolbox.read.adcp.adcp_init .

    """
    # Index the ensemble starts
    idx, enl, data = index_rtb_data(file_path)

    chunk = data[idx[0]:idx[1]]
    if BinaryCodec.verify_ens_data(chunk):
        ens = BinaryCodec.decode_data_sets(chunk)

    # Get coordinate sizes
    ens_count = len(idx)
    bin_count = ens.EnsembleData.NumBins
    bin_size = ens.AncillaryData.BinSize

    # Initialize xarray dataset
    ds = adcp_init(bin_count, ens_count)
    time = np.empty(ens_count, dtype='datetime64[ns]')

    # Read and store ensembles
    with tqdm(total=len(idx) - 1,
              desc="Processing " + file_path,
              unit=' ensembles') as pbar:
        for ii in range(len(idx)):

            # Get data binary chunck for one ensemble
            chunk = data[idx[ii]:idx[ii] + enl[ii]]

            # Check that chunk looks ok
            if BinaryCodec.verify_ens_data(chunk):

                # Decode data variables
                ens = BinaryCodec.decode_data_sets(chunk)

                CORR = np.array(ens.Correlation.Correlation)
                AMP = np.array(ens.Amplitude.Amplitude)
                PG = np.array(ens.GoodEarth.GoodEarth)

                time[ii] = ens.EnsembleData.datetime()
                ds.u.values[:, ii] = np.array(ens.EarthVelocity.Velocities)[:,
                                                                            0]
                ds.v.values[:, ii] = np.array(ens.EarthVelocity.Velocities)[:,
                                                                            1]
                ds.w.values[:, ii] = np.array(ens.EarthVelocity.Velocities)[:,
                                                                            2]
                ds.temp.values[ii] = ens.AncillaryData.WaterTemp
                ds.depth.values[ii] = ens.AncillaryData.TransducerDepth
                ds.heading.values[ii] = ens.AncillaryData.Heading
                ds.pitch.values[ii] = ens.AncillaryData.Pitch
                ds.roll_.values[ii] = ens.AncillaryData.Roll
                ds.corr.values[:, ii] = np.nanmean(CORR, axis=-1)
                ds.amp.values[:, ii] = np.nanmean(AMP, axis=-1)
                ds.pg.values[:, ii] = PG[:, 3]

                # Bottom track data
                if ens.IsBottomTrack:
                    ds.u_bt[ii] = np.array(ens.BottomTrack.EarthVelocity)[0]
                    ds.v_bt[ii] = np.array(ens.BottomTrack.EarthVelocity)[1]
                    ds.w_bt[ii] = np.array(ens.BottomTrack.EarthVelocity)[2]
                    ds.pg_bt[ii] = np.nanmean(ens.BottomTrack.BeamGood,
                                              axis=-1)
                    ds.corr_bt[ii] = np.nanmean(ens.BottomTrack.Correlation,
                                                axis=-1)
                    ds.range_bt[ii] = np.nanmean(ens.BottomTrack.Range,
                                                 axis=-1)
            pbar.update(1)

    # Determine up/down configuration
    mroll = np.abs(180 * circmean(np.pi * ds.roll_.values / 180) / np.pi)
    if mroll >= 0 and mroll < 30:
        ds.attrs['looking'] = 'up'
    else:
        ds.attrs['looking'] = 'down'

    # Determine bin depths
    if ds.attrs['looking'] == 'up':
        z = np.asarray(ds.depth.mean() - ens.AncillaryData.FirstBinRange -
                       np.arange(0, bin_count * bin_size, bin_size)).round(2)
    else:
        z = np.asarray(ens.AncillaryData.FirstBinRange +
                       np.arange(0, bin_count * bin_size, bin_size)).round(2)

    # Roll near zero means downwards (like RDI)
    roll_ = ds.roll_.values + 180
    roll_[roll_ > 180] -= 360
    ds['roll_'].values = roll_

    # Correlation between 0 and 255 (like RDI)
    ds['corr'].values *= 255

    # Set coordinates and attributes
    z_attrs, t_attrs = ds.z.attrs, ds.time.attrs
    ds = ds.assign_coords(z=z, time=time)
    ds['z'].attrs = z_attrs
    ds['time'].attrs = t_attrs

    # Get beam angle
    if ens.EnsembleData.SerialNumber[1] in '12345678DEFGbcdefghi':
        ds.attrs['beam_angle'] = 20
    elif ens.EnsembleData.SerialNumber[1] in 'OPQRST':
        ds.attrs['beam_angle'] = 15
    elif ens.EnsembleData.SerialNumber[1] in 'IJKLMNjklmnopqrstuvwxy':
        ds.attrs['beam_angle'] = 30
    elif ens.EnsembleData.SerialNumber[1] in '9ABCUVWXYZ':
        ds.attrs['beam_angle'] = 0
    else:
        raise ValueError("Could not determine beam angle.")

    # Manage coordinates and remaining attributes
    ds.attrs['bin_size'] = bin_size
    ds.attrs['instrument_serial'] = ens.EnsembleData.SerialNumber
    ds.attrs['ping_frequency'] = ens.SystemSetup.WpSystemFreqHz

    return ds