UNPKG

pcd-format

Version:
652 lines (525 loc) 19.6 kB
(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 }); })));