pcd-format
Version:
Encode & Decode PCD file
652 lines (525 loc) • 19.6 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.pcd = {})));
}(this, (function (exports) { 'use strict';
function ab2str(array) {
var s = '';
for (var i = 0, il = array.length; i < il; i++) {
s += String.fromCharCode(array[i]);
}
return s;
}
function str2ab(str) {
var array = new Uint8Array(str.length);
for (var i = 0; i < str.length; i++) {
array[i] = str.charCodeAt(i);
}
return array.buffer;
}
function getWithTypeFromText(text, type) {
switch (type) {
case 'F':
return parseFloat(text);
case 'U':
case 'I':
return parseInt(text);
default:
break;
}
throw "PCD-Format: parse data failed";
}
function getWithTypeFromDataView(dataview, offset, littleEndian, type, size) {
switch (type) {
case 'F':
switch (size) {
case 4:
return dataview.getFloat32(offset, littleEndian);
case 8:
return dataview.getFloat64(offset, littleEndian);
default:
break;
}
break;
case 'U':
switch (size) {
case 1:
return dataview.getUint8(offset, littleEndian);
case 2:
return dataview.getUint16(offset, littleEndian);
case 4:
return dataview.getUint32(offset, littleEndian);
default:
break;
}
break;
case 'I':
switch (size) {
case 1:
return dataview.getInt8(offset, littleEndian);
case 2:
return dataview.getInt16(offset, littleEndian);
case 4:
return dataview.getInt32(offset, littleEndian);
break;
default:
break;
}
break;
default:
break;
}
throw "PCD-Format: parse data failed";
}
function setWithTypeToDataView(dataview, offset, littleEndian, type, size, data) {
switch (type) {
case 'F':
switch (size) {
case 4:
dataview.setFloat32(offset, data, littleEndian);
return;
case 8:
dataview.setFloat64(offset, data, littleEndian);
return;
default:
break;
}
break;
case 'U':
switch (size) {
case 1:
dataview.setUint8(offset, data, littleEndian);
return;
case 2:
dataview.setUint16(offset, data, littleEndian);
return;
case 4:
dataview.setUint32(offset, data, littleEndian);
return;
default:
break;
}
break;
case 'I':
switch (size) {
case 1:
dataview.setInt8(offset, data, littleEndian);
return;
case 2:
dataview.setInt16(offset, data, littleEndian);
return;
case 4:
dataview.setInt32(offset, data, littleEndian);
return;
default:
break;
}
break;
default:
break;
}
throw "PCD-Format: set data failed";
}
// PCL LZF https://github.com/PointCloudLibrary/pcl/blob/master/io/src/lzf.cpp
var _HLOG = 13,
_HSIZE = 1 << _HLOG,
_MAX_LIT = 1 << 5,
_MAX_OFFSET = 1 << 13,
_MAX_REFRENCE = (1 << 8) + (1 << 3),
_FRST = function _FRST(p, input) {
return input[p] << 8 | input[p + 1];
},
_NEXT = function _NEXT(v, p, input) {
return v << 8 | input[p + 2];
},
_IDX = function _IDX(h) {
return (h >> 3 * 8 - _HLOG) - h & _HSIZE - 1;
};
function compress(data) {
var input = new Uint8Array(data),
output = [],
iend = input.byteLength,
iidx = 0,
oidx = 1,
htab = [],
// save the repeatable data
lit = 0,
hval,
hslot,
reference,
offset,
len,
maxlen;
hval = _FRST(iidx, input);
while (iidx < iend - 2) {
hval = _NEXT(hval, iidx, input);
hslot = _IDX(hval);
reference = htab[hslot] ? htab[hslot] : 0;
htab[hslot] = iidx;
if (reference < iidx && (offset = iidx - reference - 1) < _MAX_OFFSET && iidx + 4 < iend && reference > 0 && input[reference] === input[iidx] && input[reference + 1] === input[iidx + 1] && input[reference + 2] === input[iidx + 2]) {
len = 2;
maxlen = iend - iidx - len;
maxlen = maxlen > _MAX_REFRENCE ? _MAX_REFRENCE : maxlen;
if (lit > 0) output[oidx - lit - 1] = lit - 1 & 255;else --oidx;
while (true) {
if (maxlen > 16) {
var c = 0;
do {
++len;
++c;
} while (c <= 16 && input[reference + len] == input[iidx + len]);
break;
} else {
do {
len++;
} while (len < maxlen && input[reference + len] == input[iidx + len]);
break;
}
}
len -= 2;
++iidx; // len & offset
if (len < 7) output[oidx++] = (offset >> 8) + (len << 5) & 255;else {
output[oidx++] = (offset >> 8) + (7 << 5) & 255;
output[oidx++] = len - 7 & 255;
}
output[oidx++] = offset & 255;
lit = 0;
++oidx;
iidx += len + 1;
if (iidx >= iend - 2) break;
--iidx;
hval = _FRST(iidx, input);
hval = _NEXT(hval, iidx, input);
htab[_IDX(hval)] = iidx++;
} else {
++lit;
output[oidx++] = input[iidx++] & 255;
if (lit === _MAX_LIT) {
output[oidx - lit - 1] = lit - 1 & 255;
lit = 0;
++oidx;
}
}
}
while (iidx < iend) {
++lit;
output[oidx++] = input[iidx++] & 255;
if (lit === _MAX_LIT) {
output[oidx - lit - 1] = lit - 1 & 255;
lit = 0;
++oidx;
}
}
input = null;
if (lit > 0) output[oidx - lit - 1] = lit - 1 & 255;
return new Uint8Array(output).buffer;
}
function decompress(data) {
var input = new Uint8Array(data),
output = [],
iidx = 0,
iend = input.byteLength,
oidx = 0,
ctrl,
len,
reference;
do {
ctrl = input[iidx++];
if (ctrl < 1 << 5) {
ctrl++;
if (iidx + ctrl > iend) throw "LZF: Input buffer is not large enough when input index + ctrl "; // copy uncompressed data
while (ctrl > 0) {
output[oidx++] = input[iidx++];
ctrl--;
}
} else {
len = ctrl >> 5;
reference = oidx - ((ctrl & 0x1f) << 8) - 1;
if (len == 7) {
// add more len
len += input[iidx++];
if (iidx >= iend) throw "LZF: Input buffer is not large enough ";
}
reference -= input[iidx++];
if (reference < 0) throw "LZF: reference is less than 0";
if (reference >= oidx) throw "LZF: reference is bigger than output index ";
len += 2; // copy compressed data
while (len > 0) {
output[oidx++] = output[reference++];
len--;
}
}
} while (iidx < iend);
return new Uint8Array(output).buffer;
}
function parseHeader(textData) {
var header = {};
var dataStart = textData.search(/[\r\n]DATA\s(\S*)[\f\r\t\v]*\n/i);
if (dataStart == -1) {
throw "PCD-Format: not found DATA";
}
var result = /[\r\n]DATA\s(\S*)[\f\r\t\v]*\n/i.exec(textData);
header.raw = textData.substr(0, dataStart + result[0].length);
header.str = header.raw.replace(/\#.*/gi, ''); // parse
header.version = /VERSION (.*)/i.exec(header.str);
header.fields = /FIELDS (.*)/i.exec(header.str);
header.size = /SIZE (.*)/i.exec(header.str);
header.type = /TYPE (.*)/i.exec(header.str);
header.count = /COUNT (.*)/i.exec(header.str);
header.width = /WIDTH (.*)/i.exec(header.str);
header.height = /HEIGHT (.*)/i.exec(header.str);
header.viewpoint = /VIEWPOINT (.*)/i.exec(header.str);
header.points = /POINTS (.*)/i.exec(header.str);
header.data = /DATA (.*)/i.exec(header.str); // evaluate
if (header.version !== null) header.version = parseFloat(header.version[1]);
if (header.fields !== null) header.fields = header.fields[1].split(' ');
if (header.type !== null) header.type = header.type[1].split(' ');
if (header.width !== null) header.width = parseInt(header.width[1]);
if (header.height !== null) header.height = parseInt(header.height[1]);
if (header.viewpoint !== null) header.viewpoint = header.viewpoint[1].split(' ').map(parseFloat);
if (header.points !== null) header.points = parseInt(header.points[1], 10);
if (header.points === null) header.points = header.width * header.height;
if (header.data !== null) header.data = header.data[1];
if (header.size !== null) {
header.size = header.size[1].split(' ').map(function (x) {
return parseInt(x, 10);
});
}
if (header.count !== null) {
header.count = header.count[1].split(' ').map(function (x) {
return parseInt(x, 10);
});
} else {
header.count = [];
for (var i = 0, l = header.fields.length; i < l; i++) {
header.count.push(1);
}
}
header.offset = [];
var sizeSum = 0;
for (var _i = 0, _l = header.fields.length; _i < _l; _i++) {
header.offset.push(sizeSum);
if (header.data === 'ascii') {
sizeSum += header.count[_i];
} else {
sizeSum += header.size[_i] * header.count[_i];
}
} // for binary only
header.rowSize = sizeSum;
return header;
}
function getPointsFromTextData(textData, header) {
var asList = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
var points = [];
var lines = textData.split('\n');
for (var i = 0; i < lines.length; i++) {
if (lines[i] === '') continue;
var line = lines[i].split(' ');
var point = asList ? [] : {};
for (var j = 0; j < header.fields.length; j++) {
var type = header.type[j];
var count = header.count[j];
var offset = header.offset[j];
var item = void 0;
if (count == 1) {
var text = line[offset];
item = getWithTypeFromText(text, type);
} else if (count > 1) {
item = [];
for (var c = 0; c < count; c++) {
var _text = line[offset + c];
item.push(getWithTypeFromText(_text, type));
}
}
if (asList) {
point.push(item);
} else {
point[header.fields[j]] = item;
}
}
points.push(point);
}
return points;
}
function setPointsToTextData(points, header) {
var asList = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
var textData = '';
var _loop = function _loop(i) {
if (asList) {
textData += points[i].map(function (p) {
return p instanceof Array ? p.join(' ') : p;
}).join(' ');
} else {
textData += header.fields.map(function (f) {
return points[i][f] instanceof Array ? points[i][f].join(' ') : points[i][f];
}).join(' ');
}
textData += '\n';
};
for (var i = 0; i < points.length; i++) {
_loop(i);
}
return textData;
}
function getPointsFromDataView(dataview, header) {
var asList = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
var littleEndian = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
var columnar = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
var points = [];
for (var i = 0; i < header.points; i++) {
var point = asList ? [] : {};
for (var j = 0; j < header.fields.length; j++) {
var type = header.type[j];
var size = header.size[j];
var count = header.count[j];
var offset = void 0,
item = void 0;
if (count == 1) {
if (columnar) {
offset = header.offset[j] * header.points + i * size;
} else {
offset = i * header.rowSize + header.offset[j];
}
item = getWithTypeFromDataView(dataview, offset, littleEndian, type, size);
} else if (count > 1) {
item = [];
for (var c = 0; c < count; c++) {
if (columnar) {
offset = (header.offset[j] + c * size) * header.points + i * size;
} else {
offset = i * header.rowSize + header.offset[j] + c * size;
}
item.push(getWithTypeFromDataView(dataview, offset, littleEndian, type, size));
}
}
if (asList) {
point.push(item);
} else {
point[header.fields[j]] = item;
}
}
points.push(point);
}
return points;
}
function setPointsToDataView(points, dataview, header) {
var asList = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
var littleEndian = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true;
var columnar = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false;
for (var i = 0; i < points.length; i++) {
var point = points[i];
for (var j = 0; j < header.fields.length; j++) {
var type = header.type[j];
var size = header.size[j];
var count = header.count[j];
var offset = void 0;
if (count == 1) {
if (columnar) {
offset = header.offset[j] * header.points + i * size;
} else {
offset = i * header.rowSize + header.offset[j];
}
if (asList) {
setWithTypeToDataView(dataview, offset, littleEndian, type, size, point[j]);
} else {
setWithTypeToDataView(dataview, offset, littleEndian, type, size, point[header.fields[j]]);
}
} else if (count > 1) {
for (var c = 0; c < count; c++) {
if (columnar) {
offset = (header.offset[j] + c * size) * header.points + i * size;
} else {
offset = i * header.rowSize + header.offset[j] + c * size;
}
if (asList) {
setWithTypeToDataView(dataview, offset, littleEndian, type, size, point[j][c]);
} else {
setWithTypeToDataView(dataview, offset, littleEndian, type, size, point[header.fields[j]][c]);
}
}
} else {
throw "PCD-Format: set data failed";
}
}
}
}
/**
* Parse ArrayBuffer and return PCD Header and Points
* @param {ArrayBuffer} arrayBuffer
* @param {boolean} asList
* @param {boolean} littleEndian
*
* @returns {Object}
*/
function parse(arrayBuffer) {
var asList = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
var littleEndian = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
var textData = ab2str(new Uint8Array(arrayBuffer));
var header = parseHeader(textData); // parse data
var points = [];
if (header.data === 'ascii') {
var pcdData = textData.substr(header.raw.length);
points = getPointsFromTextData(pcdData, header, asList);
} else if (header.data === 'binary') {
var dataview = new DataView(arrayBuffer, header.raw.length);
points = getPointsFromDataView(dataview, header, asList, littleEndian, false);
} else if (header.data === 'binary_compressed') {
var sizes = new Uint32Array(arrayBuffer.slice(header.raw.length, header.raw.length + 8));
var decompressed = decompress(new Uint8Array(arrayBuffer, header.raw.length + 8));
var _dataview = new DataView(decompressed);
points = getPointsFromDataView(_dataview, header, asList, littleEndian, true);
}
return {
header: header,
points: points
};
}
/**
* Stringify PCD and return ArrayBuffer
* @param {Object} header
* @param {Array} points
* @param {boolean} asList
* @param {boolean} littleEndian
*
* @returns {ArrayBuffer}
*/
function stringify(header, points) {
var asList = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
var littleEndian = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
if (header.data === 'ascii') {
var textData = '' + header.raw;
textData += setPointsToTextData(points, header, asList);
return str2ab(textData);
} else if (header.data === 'binary') {
var bufferSize = header.raw.length + header.points * header.rowSize;
var arrayBuffer = new ArrayBuffer(bufferSize); // write header
var headerView = new Uint8Array(arrayBuffer, 0, header.raw.length);
for (var i = 0; i < header.raw.length; i++) {
headerView[i] = header.raw.charCodeAt(i);
} // write data
var dataview = new DataView(arrayBuffer, header.raw.length);
setPointsToDataView(points, dataview, header, asList, littleEndian, false);
return arrayBuffer;
} else if (header.data === 'binary_compressed') {
// write data
var dataArrayBuffer = new ArrayBuffer(header.points * header.rowSize);
var _dataview2 = new DataView(dataArrayBuffer);
setPointsToDataView(points, _dataview2, header, asList, littleEndian, true);
var compressed = compress(dataArrayBuffer);
var _arrayBuffer = new ArrayBuffer(header.raw.length + 8 + compressed.byteLength); // write header
var _headerView = new Uint8Array(_arrayBuffer, 0, header.raw.length);
for (var _i2 = 0; _i2 < header.raw.length; _i2++) {
_headerView[_i2] = header.raw.charCodeAt(_i2);
} // write size
var sizes = new DataView(_arrayBuffer, header.raw.length, 8);
sizes.setInt32(0, compressed.byteLength, littleEndian);
sizes.setInt32(4, header.points * header.rowSize, littleEndian); // write data
var tmp = new Uint8Array(_arrayBuffer);
tmp.set(new Uint8Array(compressed), header.raw.length + 8);
return _arrayBuffer;
}
}
exports.parse = parse;
exports.stringify = stringify;
Object.defineProperty(exports, '__esModule', { value: true });
})));