DataSamples (web api) - 1080Motion/API GitHub Wiki
Note: this page does not apply to the grpc API. gRPC uses protobuf for serialization
When the raw data samples are returned by the web api they provided as a base64 encoded string of binary data. This is done to save bandwidth since the same data in real JSON format would be enormous.
The binary data represent the samples packed into a byte array:
- Each sample consists of five 32bit floating point values in the following order:
- TimeSinceStart
- Position
- Speed
- Acceleration
- Force
- This means that each sample is occupying
5 * 4 = 20 bytes
- Total number of samples is therefore
byte array length / 20
See Data Structure/Samples for more details about the individual values.
In C#, it's easiest to use the BinaryReader to extract the data
private const int BytesPerSample = 5 * sizeof(float);
private static PublicDataSample DeserializeSingleSample(BinaryReader reader)
{
var sample = new PublicDataSample
{
TimeSinceStart = reader.ReadSingle(),
Position = reader.ReadSingle(),
Speed = reader.ReadSingle(),
Acceleration = reader.ReadSingle(),
Force = reader.ReadSingle(),
};
return sample;
}
/// <summary>
/// Takes a byte array returned
/// original list of samples.
/// </summary>
/// <param name="bytes">Byte array holding the serialized list of samples</param>
public static PublicDataSample[]? DeserializeList(string? base64data)
{
if (base64data == null)
return null;
byte[] bytes = Convert.FromBase64String(base64data);
if (bytes.Length == 0)
return Array.Empty<PublicDataSample>();
if (bytes.Length % BytesPerSample != 0)
throw new Exception("Incorrect length of byte[]");
int numSamples = bytes.Length / BytesPerSample;
using var reader = new BinaryReader(new MemoryStream(bytes));
return DeserializeList(reader, numSamples);
}
private static PublicDataSample[] DeserializeList(BinaryReader reader, int numSamples)
{
var samples = new PublicDataSample[numSamples];
for (int i = 0; i < samples.Length; i++)
{
samples[i] = DeserializeSingleSample(reader);
}
return samples;
}
These examples uses base64.b64decode
to convert the string to a byte array and then struct.unpack
to extract the individual values
The first example comes from the Python sample.
# Decode sample data
# :param sample_data: sampledata is base64 encoded. Each sample consists of 5 floating point values (32bit); i.e., * 4 = 20 bytes. Note: the total number of samples is therefore "byte array length / 20"
def _decode_sample_data(self, sample_data):
decoded_data = base64.b64decode(sample_data)
# Use struct.unpack to unpack all data at once into a big array of 32bit floating point numbers
n_samples = len(decoded_data) // 20
unpacked_data = struct.unpack(f"<{n_samples * 5}f", decoded_data)
# Extract the individual parts using slicing (Take every fifth element and returns as a new array)
time_since_start = unpacked_data[0::5]
position = unpacked_data[1::5]
speed = unpacked_data[2::5]
acceleration = unpacked_data[3::5]
force = unpacked_data[4::5]
return time_since_start, position, speed, acceleration, force
Here is another version that loops over the data and extracts one sample at a time
# Decode sample data
# :param sample_data: sampledata is base64 encoded. Each sample consists of 5 floating point values (32bit); i.e., * 4 = 20 bytes. Note: the total number of samples is therefore "byte array length / 20"
def _decode_sample_data(self, sample_data):
sample_data = base64.b64decode(sample_data)
n_samples = int(len(sample_data)/20) # 20: 5 floats [TimeSinceStart, Position, Speed, Acceleration, Force] -> 5 x 4 (float = 32bits = 4 bytes)
time_since_start = []
position = []
speed = []
acceleration = []
force = []
for i in range(n_samples):
time_since_start.append(struct.unpack("<f", sample_data[i*20:i*20+4])[0])
position.append(struct.unpack("<f", sample_data[i*20+4:i*20+8])[0])
speed.append(struct.unpack("<f", sample_data[i*20+8:i*20+12])[0])
acceleration.append(struct.unpack("<f", sample_data[i*20+12:i*20+16])[0])
force.append(struct.unpack("<f", sample_data[i*20+16:i*20+20])[0])
return time_since_start, position, speed, acceleration, force
This is part of the R sample
# Decode sample data from base64
decode_sample_data <- function(sample_data) {
# Decode the base64 encoded string
decoded_data <- base64enc::base64decode(sample_data)
# Calculate the number of samples (each sample is 20 bytes)
num_samples <- length(decoded_data) / 20
# raw data -> raw vector
raw_vector <- as.raw(decoded_data)
unpacked_data <- readBin(raw_vector, what = "numeric", n = num_samples * 5, size = 4, endian = "little")
# Extract the data
time_since_start <- unpacked_data[seq(1, length(unpacked_data), by = 5)]
position <- unpacked_data[seq(2, length(unpacked_data), by = 5)]
speed <- unpacked_data[seq(3, length(unpacked_data), by = 5)]
acceleration <- unpacked_data[seq(4, length(unpacked_data), by = 5)]
force <- unpacked_data[seq(5, length(unpacked_data), by = 5)]
# Return the results as a list
list(time_since_start = time_since_start, position = position, speed = speed, acceleration = acceleration, force = force)
}
If you have access the API using another language than above, consider sharing your code with 1080 motion so it can be put here to benefit other users. Just add an issue here on github with your code.