Ping360 discrepancy between pingviewer and brping python

i am trying to make an equivalent of ping viewer in python, but the cloud looks kinda off
pingviewer looks like this

code outcome looks like this

any help to debug would be appreciated


# Connect to and initialize device
p = Ping360()
p.connect_serial('/dev/ttyUSB0', 2000000)
print(f'{p.initialize() = }')

# Set some settings
print(p.set_number_of_samples(500))
print(p.set_gain_setting(1))
print(p.set_transmit_duration(27))
print(p.set_transmit_frequency(750))

def meters_per_sample(ping_message, v_sound=1500):
    """ Returns the target distance per sample, in meters. 
    
    'ping_message' is the message being analysed.
    'v_sound' is the operating speed of sound [m/s]. Default 1500.

    """
    # sample_period is in 25ns increments
    # time of flight includes there and back, so divide by 2
    return v_sound * ping_message.sample_period * 12.5e-9

using this logic and filtering intensities>200

As far as I recall there’s a bit more logic that goes into configuration. You need to select a scanning distance then base your calculations off that. At minimum we have this in our driver, now you’ll have to modify it as this was taken out of a class, but the logic is sound. You’ll also have to configure the transducer to use these calculated parameters.


def calculate_sample_period(self):
        self.sample_period = int((2 * self.range) / (self.number_of_samples * self.speed_of_sound * 25*(10**-9)))
        
def calculate_transmit_duration(self):
        """
        Per firmware engineer:
            1. Starting point is TxPulse in usec = ((one-way range in metres) * 8000) / (Velocity of sound in metres per second)
            2. Then check that TxPulse is wide enough for currently selected sample interval in usec, i.e.,
                if TxPulse < (2.5 * sample interval) then TxPulse = (2.5 * sample interval)
            3. Perform limit checking
        """
        inital_transmit = round((self.range * 8000) / self.speed_of_sound)
        duration = max((2.5 * self.sample_period) / 1000, inital_transmit)
        sonar_min_duration = 5
        transmit_max = min(self.sample_period*(64*10**6), 500)
        self.transmit_duration = max(sonar_min_duration, min(duration, transmit_max))

Calculating your meters per sample should be as simple as dividing your range, in meters of course, by number of samples. I hope this helps!

Hi @axppxsky,

I’ve previously written this code to visualise Ping360 scans (where each scan is a single image), which may be worth referring to:

import numpy as np
import colorcet as cc
import matplotlib.pyplot as plt


def visualise_scan(ping_messages=None, title=None,
                   ax=None, show=True, v_sound=1500, indices=None,
                   points=None, ignore_dist=None):
    ''' Plot a static image of a sonar scan.

    plot is independent of telemetry data, so includes no heading compensation.

    'ping_messages' is a list of ping-protocol (auto_/)device_data messages.
    'indices' is a list of indices to draw radial lines for, e.g. for displaying
      a region of interest.
    'points' is a numpy array of (x,y) points to add to the plot, where y points
      in the sonar's forwards direction.
    'ignore_dist' is a distance in meters to cover in black, generally to hide
      the sonar's internal-reflection region.
    '''
    if ax is None:
        fig = plt.figure()
        ax = fig.add_subplot(polar=True)

    if title:
        ax.set_title(title)

    settings = ping_messages[0]
    sample_sep = settings.sample_period * 12.5e-9 * v_sound
    dists = sample_sep * np.arange(len(settings.data))
    angles = []
    values = []
    for message in ping_messages:
        angles.append(message.angle)
        values.append(np.frombuffer(message.data, dtype=np.uint8))

    # convert ping360 gradians into plottable radians
    radians = np.unwrap(np.array(angles) * (2 * np.pi / 400))
    R, Theta = np.meshgrid(dists, radians)

    ax.pcolormesh(Theta, R, values, cmap=cc.m_rainbow, shading='nearest')

    if ignore_dist:
        theta = (radians[-1] + radians[0]) / 2
        width = abs(radians[-1] - radians[0])
        ax.bar(theta, ignore_dist, width=width, bottom=0, color='k', alpha=0.5)
    
    if indices:
        ax.vlines(radians[indices], 0, dists[-1], linewidth=1, color='k')
        
    if points is not None:
        # convert to radians, magnitudes for plotting on polar axes
        # subtract a half-turn to de-compensate for angular compensation in
        #  points (ping360 0-angle is on cable side, 'front' is at 180 degrees)
        ax.scatter(np.arctan2(points[:,1], points[:,0]) - np.pi,
                   np.linalg.norm(points), 1, 'k')

    if show:
        plt.show()

    return ax, radians, dists