@foxglove/velodyne-cloud
Version:
TypeScript library for converting Velodyne LIDAR packet data to point clouds
137 lines (125 loc) • 5.36 kB
text/typescript
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/
import { CalibrationData, loadCalibrationData } from "./CalibrationData";
import { LaserCorrection, Model } from "./VelodyneTypes";
// Takes a calibration data file as input and computes cached lookup tables for
// use in online point cloud conversion
export class Calibration {
static ROTATION_RESOLUTION = 0.01; // [deg]
static ROTATION_MAX_UNITS = 36000; // [deg/100]
static VLP16_FIRINGS_PER_BLOCK = 2;
static VLP16_SCANS_PER_FIRING = 16;
static VLP16_BLOCK_TDURATION = 110.592; // [µs]
static VLP16_DSR_TOFFSET = 2.304; // [µs]
static VLP16_FIRING_TOFFSET = 55.296; // [µs]
static HDL32E_DSR_TOFFSET = 1.152; // [µs]
static HDL32E_FIRING_TOFFSET = 46.08; // [µs]
static VLS128_DSR_TOFFSET = 2.665; // [µs]
static VLS128_FIRING_TOFFSET = 53.5; // [µs]
readonly model: Model;
readonly laserCorrections: LaserCorrection[];
readonly distanceResolution: number; // [m]
readonly timingOffsets: number[][];
readonly sinRotTable: number[];
readonly cosRotTable: number[];
readonly vls128LaserAzimuthCache: number[];
constructor(model: Model, calibrationData: CalibrationData = loadCalibrationData(model)) {
this.model = model;
this.laserCorrections = calibrationData.lasers.map((v) => {
return {
laserId: v.laser_id,
rotCorrection: v.rot_correction,
vertCorrection: v.vert_correction,
distCorrection: v.dist_correction,
twoPtCorrectionAvailable: v.two_pt_correction_available ?? false,
distCorrectionX: v.dist_correction_x,
distCorrectionY: v.dist_correction_y,
vertOffsetCorrection: v.vert_offset_correction,
horizOffsetCorrection: v.horiz_offset_correction,
maxIntensity: v.max_intensity ?? 255,
minIntensity: v.min_intensity ?? 0,
focalDistance: v.focal_distance,
focalSlope: v.focal_slope,
cosRotCorrection: Math.cos(v.rot_correction),
sinRotCorrection: Math.sin(v.rot_correction),
cosVertCorrection: Math.cos(v.vert_correction),
sinVertCorrection: Math.sin(v.vert_correction),
};
});
this.distanceResolution = calibrationData.distance_resolution;
this.timingOffsets = Calibration.BuildTimingsFor(model);
// Set up cached values for sin and cos of all the possible headings
this.cosRotTable = Array<number>(Calibration.ROTATION_MAX_UNITS);
this.sinRotTable = Array<number>(Calibration.ROTATION_MAX_UNITS);
for (let i = 0; i < Calibration.ROTATION_MAX_UNITS; i++) {
const rotation = deg2rad(Calibration.ROTATION_RESOLUTION * i);
this.cosRotTable[i] = Math.cos(rotation);
this.sinRotTable[i] = Math.sin(rotation);
}
this.vls128LaserAzimuthCache = Array<number>(16).fill(0);
const VLS128_CHANNEL_TDURATION = 2.665; // [µs] Corresponds to one laser firing
const VLS128_SEQ_TDURATION = 53.3; // [µs] A set of laser firings including recharging
for (let i = 0; i < 16; i++) {
this.vls128LaserAzimuthCache[i] =
(VLS128_CHANNEL_TDURATION / VLS128_SEQ_TDURATION) * (i + i / 8);
}
}
// Build a timing table with cells for each channel (laser)
static BuildTimingsFor(model: Model): number[][] {
const block1 = (x: number, _y: number) => x;
const block16 = (x: number, y: number) => x * 2 + y / 16;
const point1 = (_x: number, y: number) => y;
const point2 = (_x: number, y: number) => y / 2;
const point16 = (_x: number, y: number) => y % 16;
switch (model) {
case Model.VLP16:
case Model.VLP16HiRes: {
const full = Calibration.VLP16_FIRING_TOFFSET;
const single = Calibration.VLP16_DSR_TOFFSET;
return Calibration.BuildTimings(12, 32, full, single, 0, block16, point16);
}
case Model.VLP32C: {
const full = Calibration.VLP16_FIRING_TOFFSET;
const single = Calibration.VLP16_DSR_TOFFSET;
return Calibration.BuildTimings(12, 32, full, single, 0, block1, point2);
}
case Model.HDL32E: {
const full = Calibration.HDL32E_FIRING_TOFFSET;
const single = Calibration.HDL32E_DSR_TOFFSET;
return Calibration.BuildTimings(12, 32, full, single, 0, block1, point2);
}
case Model.VLS128: {
const full = Calibration.VLS128_FIRING_TOFFSET;
const single = Calibration.VLS128_DSR_TOFFSET;
return Calibration.BuildTimings(3, 17, full, single, -8.7, block1, point1);
}
default:
return [];
}
}
static BuildTimings(
rows: number,
cols: number,
fullFiringUs: number, // [µs]
singleFiringUs: number, // [µs]
offsetUs: number, // [µs]
block: IndexCalc,
point: IndexCalc,
): number[][] {
const fullFiring = fullFiringUs * 1e-6;
const singleFiring = singleFiringUs * 1e-6;
const offset = offsetUs * 1e-6;
return Array(rows)
.fill(0)
.map((_row, x) =>
Array(cols)
.fill(0)
.map((_col, y) => fullFiring * block(x, y) + singleFiring * point(x, y) + offset),
);
}
}
type IndexCalc = (x: number, y: number) => number;
function deg2rad(degrees: number): number {
return degrees * (Math.PI / 180);
}