@playcanvas/splat-transform 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +303 -74
- package/dist/index.mjs.map +1 -1
- package/package.json +18 -10
package/dist/index.mjs
CHANGED
|
@@ -1884,7 +1884,7 @@ let Quat$1 = class Quat {
|
|
|
1884
1884
|
}
|
|
1885
1885
|
};
|
|
1886
1886
|
|
|
1887
|
-
var version$1 = "0.
|
|
1887
|
+
var version$1 = "0.7.0";
|
|
1888
1888
|
|
|
1889
1889
|
class Column {
|
|
1890
1890
|
name;
|
|
@@ -89417,6 +89417,164 @@ const createDevice = async () => {
|
|
|
89417
89417
|
return new GpuDevice(app, backbuffer);
|
|
89418
89418
|
};
|
|
89419
89419
|
|
|
89420
|
+
// write data to a file stream
|
|
89421
|
+
class FileWriter {
|
|
89422
|
+
write;
|
|
89423
|
+
close;
|
|
89424
|
+
constructor(stream) {
|
|
89425
|
+
let cursor = 0;
|
|
89426
|
+
this.write = async (data) => {
|
|
89427
|
+
cursor += data.byteLength;
|
|
89428
|
+
await stream.write(data);
|
|
89429
|
+
};
|
|
89430
|
+
this.close = async () => {
|
|
89431
|
+
await stream.truncate(cursor);
|
|
89432
|
+
return true;
|
|
89433
|
+
};
|
|
89434
|
+
}
|
|
89435
|
+
}
|
|
89436
|
+
|
|
89437
|
+
const crc32_table = (() => {
|
|
89438
|
+
const tbl = [];
|
|
89439
|
+
let c;
|
|
89440
|
+
for (let n = 0; n < 256; n++) {
|
|
89441
|
+
c = n;
|
|
89442
|
+
for (let k = 0; k < 8; k++) {
|
|
89443
|
+
c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
|
|
89444
|
+
}
|
|
89445
|
+
tbl[n] = c;
|
|
89446
|
+
}
|
|
89447
|
+
return tbl;
|
|
89448
|
+
})();
|
|
89449
|
+
class Crc {
|
|
89450
|
+
reset;
|
|
89451
|
+
update;
|
|
89452
|
+
value;
|
|
89453
|
+
constructor() {
|
|
89454
|
+
let bits = -1;
|
|
89455
|
+
this.update = (data) => {
|
|
89456
|
+
for (let i = 0; i < data.length; i++) {
|
|
89457
|
+
bits = (bits >>> 8) ^ crc32_table[(bits ^ data[i]) & 0xFF];
|
|
89458
|
+
}
|
|
89459
|
+
};
|
|
89460
|
+
this.value = () => (bits ^ (-1)) >>> 0;
|
|
89461
|
+
}
|
|
89462
|
+
}
|
|
89463
|
+
|
|
89464
|
+
// https://gist.github.com/rvaiya/4a2192df729056880a027789ae3cd4b7
|
|
89465
|
+
// zip archive writer. data is streamed to the output writer.
|
|
89466
|
+
class ZipWriter {
|
|
89467
|
+
// start a new file
|
|
89468
|
+
start;
|
|
89469
|
+
// write func
|
|
89470
|
+
write;
|
|
89471
|
+
// finish the archive by writing the footer
|
|
89472
|
+
close;
|
|
89473
|
+
// helper function to start and write file contents
|
|
89474
|
+
async file(filename, content) {
|
|
89475
|
+
// start a new file
|
|
89476
|
+
await this.start(filename);
|
|
89477
|
+
// write file content
|
|
89478
|
+
if (typeof content === 'string') {
|
|
89479
|
+
await this.write(new TextEncoder().encode(content));
|
|
89480
|
+
}
|
|
89481
|
+
else if (content instanceof Uint8Array) {
|
|
89482
|
+
await this.write(content);
|
|
89483
|
+
}
|
|
89484
|
+
else {
|
|
89485
|
+
for (let i = 0; i < content.length; i++) {
|
|
89486
|
+
await this.write(content[i]);
|
|
89487
|
+
}
|
|
89488
|
+
}
|
|
89489
|
+
}
|
|
89490
|
+
// write uncompressed data to a zip file using the passed-in writer
|
|
89491
|
+
constructor(writer) {
|
|
89492
|
+
const textEncoder = new TextEncoder();
|
|
89493
|
+
const files = [];
|
|
89494
|
+
const date = new Date();
|
|
89495
|
+
const dosTime = (date.getHours() << 11) | (date.getMinutes() << 5) | Math.floor(date.getSeconds() / 2);
|
|
89496
|
+
const dosDate = ((date.getFullYear() - 1980) << 9) | ((date.getMonth() + 1) << 5) | date.getDate();
|
|
89497
|
+
const writeHeader = async (filename) => {
|
|
89498
|
+
const filenameBuf = textEncoder.encode(filename);
|
|
89499
|
+
const nameLen = filenameBuf.length;
|
|
89500
|
+
const header = new Uint8Array(30 + nameLen);
|
|
89501
|
+
const view = new DataView(header.buffer);
|
|
89502
|
+
view.setUint32(0, 0x04034b50, true);
|
|
89503
|
+
view.setUint16(4, 20, true); // version needed to extract = 2.0
|
|
89504
|
+
view.setUint16(6, 0x8 | 0x800, true); // indicate crc and size comes after, utf-8 encoding
|
|
89505
|
+
view.setUint16(8, 0, true); // method = 0 (store)
|
|
89506
|
+
view.setUint16(10, dosTime, true);
|
|
89507
|
+
view.setUint16(12, dosDate, true);
|
|
89508
|
+
view.setUint16(26, nameLen, true);
|
|
89509
|
+
header.set(filenameBuf, 30);
|
|
89510
|
+
await writer.write(header);
|
|
89511
|
+
files.push({ filename: filenameBuf, crc: new Crc(), sizeBytes: 0 });
|
|
89512
|
+
};
|
|
89513
|
+
const writeFooter = async () => {
|
|
89514
|
+
const file = files[files.length - 1];
|
|
89515
|
+
const { crc, sizeBytes } = file;
|
|
89516
|
+
const data = new Uint8Array(16);
|
|
89517
|
+
const view = new DataView(data.buffer);
|
|
89518
|
+
view.setUint32(0, 0x08074b50, true);
|
|
89519
|
+
view.setUint32(4, crc.value(), true);
|
|
89520
|
+
view.setUint32(8, sizeBytes, true);
|
|
89521
|
+
view.setUint32(12, sizeBytes, true);
|
|
89522
|
+
await writer.write(data);
|
|
89523
|
+
};
|
|
89524
|
+
this.start = async (filename) => {
|
|
89525
|
+
// write previous file footer
|
|
89526
|
+
if (files.length > 0) {
|
|
89527
|
+
await writeFooter();
|
|
89528
|
+
}
|
|
89529
|
+
await writeHeader(filename);
|
|
89530
|
+
};
|
|
89531
|
+
this.write = async (data) => {
|
|
89532
|
+
const file = files[files.length - 1];
|
|
89533
|
+
file.sizeBytes += data.length;
|
|
89534
|
+
file.crc.update(data);
|
|
89535
|
+
await writer.write(data);
|
|
89536
|
+
};
|
|
89537
|
+
this.close = async () => {
|
|
89538
|
+
// write last file's footer
|
|
89539
|
+
await writeFooter();
|
|
89540
|
+
// write cd records
|
|
89541
|
+
let offset = 0;
|
|
89542
|
+
for (const file of files) {
|
|
89543
|
+
const { filename, crc, sizeBytes } = file;
|
|
89544
|
+
const nameLen = filename.length;
|
|
89545
|
+
const cdr = new Uint8Array(46 + nameLen);
|
|
89546
|
+
const view = new DataView(cdr.buffer);
|
|
89547
|
+
view.setUint32(0, 0x02014b50, true);
|
|
89548
|
+
view.setUint16(4, 20, true);
|
|
89549
|
+
view.setUint16(6, 20, true);
|
|
89550
|
+
view.setUint16(8, 0x8 | 0x800, true);
|
|
89551
|
+
view.setUint16(10, 0, true);
|
|
89552
|
+
view.setUint16(12, dosTime, true);
|
|
89553
|
+
view.setUint16(14, dosDate, true);
|
|
89554
|
+
view.setUint32(16, crc.value(), true);
|
|
89555
|
+
view.setUint32(20, sizeBytes, true);
|
|
89556
|
+
view.setUint32(24, sizeBytes, true);
|
|
89557
|
+
view.setUint16(28, nameLen, true);
|
|
89558
|
+
view.setUint32(42, offset, true);
|
|
89559
|
+
cdr.set(filename, 46);
|
|
89560
|
+
await writer.write(cdr);
|
|
89561
|
+
offset += 30 + nameLen + sizeBytes + 16; // 30 local header + name + data + 16 descriptor
|
|
89562
|
+
}
|
|
89563
|
+
const filenameLength = files.reduce((tot, file) => tot + file.filename.length, 0);
|
|
89564
|
+
const dataLength = files.reduce((tot, file) => tot + file.sizeBytes, 0);
|
|
89565
|
+
// write eocd record
|
|
89566
|
+
const eocd = new Uint8Array(22);
|
|
89567
|
+
const eocdView = new DataView(eocd.buffer);
|
|
89568
|
+
eocdView.setUint32(0, 0x06054b50, true);
|
|
89569
|
+
eocdView.setUint16(8, files.length, true);
|
|
89570
|
+
eocdView.setUint16(10, files.length, true);
|
|
89571
|
+
eocdView.setUint32(12, filenameLength + files.length * 46, true);
|
|
89572
|
+
eocdView.setUint32(16, filenameLength + files.length * (30 + 16) + dataLength, true);
|
|
89573
|
+
await writer.write(eocd);
|
|
89574
|
+
};
|
|
89575
|
+
}
|
|
89576
|
+
}
|
|
89577
|
+
|
|
89420
89578
|
class KdTree {
|
|
89421
89579
|
centroids;
|
|
89422
89580
|
root;
|
|
@@ -89699,6 +89857,24 @@ const initializeCentroids = (dataTable, centroids, row) => {
|
|
|
89699
89857
|
centroids.setRow(i, row);
|
|
89700
89858
|
}
|
|
89701
89859
|
};
|
|
89860
|
+
// in the 1d case we can initialize centroids evenly over the input range
|
|
89861
|
+
const initializeCentroids1D = (dataTable, centroids) => {
|
|
89862
|
+
// calculate min/max
|
|
89863
|
+
let m = Infinity;
|
|
89864
|
+
let M = -Infinity;
|
|
89865
|
+
const data = dataTable.getColumn(0).data;
|
|
89866
|
+
for (let i = 0; i < dataTable.numRows; ++i) {
|
|
89867
|
+
const value = data[i];
|
|
89868
|
+
if (value < m)
|
|
89869
|
+
m = value;
|
|
89870
|
+
if (value > M)
|
|
89871
|
+
M = value;
|
|
89872
|
+
}
|
|
89873
|
+
const centroidsData = centroids.getColumn(0).data;
|
|
89874
|
+
for (let i = 0; i < centroids.numRows; ++i) {
|
|
89875
|
+
centroidsData[i] = m + (M - m) * i / (centroids.numRows - 1);
|
|
89876
|
+
}
|
|
89877
|
+
};
|
|
89702
89878
|
const calcAverage = (dataTable, cluster, row) => {
|
|
89703
89879
|
const keys = dataTable.columnNames;
|
|
89704
89880
|
for (let i = 0; i < keys.length; ++i) {
|
|
@@ -89754,7 +89930,12 @@ const kmeans = async (points, k, iterations, device) => {
|
|
|
89754
89930
|
const row = {};
|
|
89755
89931
|
// construct centroids data table and assign initial values
|
|
89756
89932
|
const centroids = new DataTable(points.columns.map(c => new Column(c.name, new Float32Array(k))));
|
|
89757
|
-
|
|
89933
|
+
if (points.numColumns === 1) {
|
|
89934
|
+
initializeCentroids1D(points, centroids);
|
|
89935
|
+
}
|
|
89936
|
+
else {
|
|
89937
|
+
initializeCentroids(points, centroids, row);
|
|
89938
|
+
}
|
|
89758
89939
|
const gpuClustering = device && new GpuClustering(device, points.numColumns, k);
|
|
89759
89940
|
const labels = new Uint32Array(points.numRows);
|
|
89760
89941
|
let converged = false;
|
|
@@ -89770,8 +89951,16 @@ const kmeans = async (points, k, iterations, device) => {
|
|
|
89770
89951
|
// calculate the new centroid positions
|
|
89771
89952
|
const groups = groupLabels(labels, k);
|
|
89772
89953
|
for (let i = 0; i < centroids.numRows; ++i) {
|
|
89773
|
-
|
|
89774
|
-
|
|
89954
|
+
if (groups[i].length === 0) {
|
|
89955
|
+
// re-seed this centroid to a random point to avoid zero vector
|
|
89956
|
+
const idx = Math.floor(Math.random() * points.numRows);
|
|
89957
|
+
points.getRow(idx, row);
|
|
89958
|
+
centroids.setRow(i, row);
|
|
89959
|
+
}
|
|
89960
|
+
else {
|
|
89961
|
+
calcAverage(points, groups[i], row);
|
|
89962
|
+
centroids.setRow(i, row);
|
|
89963
|
+
}
|
|
89775
89964
|
}
|
|
89776
89965
|
steps++;
|
|
89777
89966
|
if (steps >= iterations) {
|
|
@@ -89818,20 +90007,82 @@ const generateIndices = (dataTable) => {
|
|
|
89818
90007
|
generateOrdering(dataTable, result);
|
|
89819
90008
|
return result;
|
|
89820
90009
|
};
|
|
90010
|
+
// convert a dataTable with multiple columns into a single column
|
|
90011
|
+
// calculate 256 clusters using kmeans
|
|
90012
|
+
// return
|
|
90013
|
+
// - the resulting labels in a new datatable having same shape as the input
|
|
90014
|
+
// - array of 256 centroids
|
|
90015
|
+
const cluster1d = async (dataTable, iterations, device) => {
|
|
90016
|
+
const { numColumns, numRows } = dataTable;
|
|
90017
|
+
// construct 1d points from the columns of data
|
|
90018
|
+
const data = new Float32Array(numRows * numColumns);
|
|
90019
|
+
for (let i = 0; i < numColumns; ++i) {
|
|
90020
|
+
data.set(dataTable.getColumn(i).data, i * numRows);
|
|
90021
|
+
}
|
|
90022
|
+
const src = new DataTable([new Column('data', data)]);
|
|
90023
|
+
const { centroids, labels } = await kmeans(src, 256, iterations, device);
|
|
90024
|
+
// order centroids smallest to largest
|
|
90025
|
+
const centroidsData = centroids.getColumn(0).data;
|
|
90026
|
+
const order = centroidsData.map((_, i) => i);
|
|
90027
|
+
order.sort((a, b) => centroidsData[a] - centroidsData[b]);
|
|
90028
|
+
// reorder centroids
|
|
90029
|
+
const tmp = centroidsData.slice();
|
|
90030
|
+
for (let i = 0; i < order.length; ++i) {
|
|
90031
|
+
centroidsData[i] = tmp[order[i]];
|
|
90032
|
+
}
|
|
90033
|
+
const invOrder = [];
|
|
90034
|
+
for (let i = 0; i < order.length; ++i) {
|
|
90035
|
+
invOrder[order[i]] = i;
|
|
90036
|
+
}
|
|
90037
|
+
// reorder labels
|
|
90038
|
+
for (let i = 0; i < labels.length; i++) {
|
|
90039
|
+
labels[i] = invOrder[labels[i]];
|
|
90040
|
+
}
|
|
90041
|
+
const result = new DataTable(dataTable.columnNames.map(name => new Column(name, new Uint8Array(numRows))));
|
|
90042
|
+
for (let i = 0; i < numColumns; ++i) {
|
|
90043
|
+
result.getColumn(i).data.set(labels.subarray(i * numRows, (i + 1) * numRows));
|
|
90044
|
+
}
|
|
90045
|
+
return {
|
|
90046
|
+
centroids,
|
|
90047
|
+
labels: result
|
|
90048
|
+
};
|
|
90049
|
+
};
|
|
89821
90050
|
const writeSog = async (fileHandle, dataTable, outputFilename, shIterations = 10, shMethod, indices = generateIndices(dataTable)) => {
|
|
90051
|
+
// initialize output stream
|
|
90052
|
+
const isBundle = outputFilename.toLowerCase().endsWith('.sog');
|
|
90053
|
+
const fileWriter = isBundle && new FileWriter(fileHandle);
|
|
90054
|
+
const zipWriter = fileWriter && new ZipWriter(fileWriter);
|
|
89822
90055
|
const numRows = indices.length;
|
|
89823
|
-
const width = Math.ceil(Math.sqrt(numRows) /
|
|
89824
|
-
const height = Math.ceil(numRows / width /
|
|
90056
|
+
const width = Math.ceil(Math.sqrt(numRows) / 4) * 4;
|
|
90057
|
+
const height = Math.ceil(numRows / width / 4) * 4;
|
|
89825
90058
|
const channels = 4;
|
|
89826
|
-
|
|
90059
|
+
// the layout function determines how the data is packed into the output texture.
|
|
90060
|
+
const layout = identity; // rectChunks;
|
|
90061
|
+
const write = async (filename, data, w = width, h = height) => {
|
|
89827
90062
|
const pathname = resolve$2(dirname(outputFilename), filename);
|
|
89828
90063
|
console.log(`writing '${pathname}'...`);
|
|
89829
|
-
|
|
89830
|
-
|
|
89831
|
-
.
|
|
90064
|
+
const webp = sharp(data, { raw: { width: w, height: h, channels } }).webp({ lossless: true });
|
|
90065
|
+
if (zipWriter) {
|
|
90066
|
+
await zipWriter.file(filename, await webp.toBuffer());
|
|
90067
|
+
}
|
|
90068
|
+
else {
|
|
90069
|
+
await webp.toFile(pathname);
|
|
90070
|
+
}
|
|
90071
|
+
};
|
|
90072
|
+
const writeTableData = (filename, dataTable, w = width, h = height) => {
|
|
90073
|
+
const data = new Uint8Array(w * h * channels);
|
|
90074
|
+
const columns = dataTable.columns.map(c => c.data);
|
|
90075
|
+
const numColumns = columns.length;
|
|
90076
|
+
for (let i = 0; i < indices.length; ++i) {
|
|
90077
|
+
const idx = indices[i];
|
|
90078
|
+
const ti = layout(i);
|
|
90079
|
+
data[ti * channels + 0] = columns[0][idx];
|
|
90080
|
+
data[ti * channels + 1] = numColumns > 1 ? columns[1][idx] : 0;
|
|
90081
|
+
data[ti * channels + 2] = numColumns > 2 ? columns[2][idx] : 0;
|
|
90082
|
+
data[ti * channels + 3] = numColumns > 3 ? columns[3][idx] : 255;
|
|
90083
|
+
}
|
|
90084
|
+
return write(filename, data, w, h);
|
|
89832
90085
|
};
|
|
89833
|
-
// the layout function determines how the data is packed into the output texture.
|
|
89834
|
-
const layout = identity; // rectChunks;
|
|
89835
90086
|
const row = {};
|
|
89836
90087
|
// convert position/means
|
|
89837
90088
|
const meansL = new Uint8Array(width * height * channels);
|
|
@@ -89898,39 +90149,25 @@ const writeSog = async (fileHandle, dataTable, outputFilename, shIterations = 10
|
|
|
89898
90149
|
quats[ti * 4 + 3] = 252 + maxComp;
|
|
89899
90150
|
}
|
|
89900
90151
|
await write('quats.webp', quats);
|
|
89901
|
-
|
|
89902
|
-
|
|
89903
|
-
const
|
|
89904
|
-
|
|
89905
|
-
|
|
89906
|
-
|
|
89907
|
-
|
|
89908
|
-
|
|
89909
|
-
|
|
89910
|
-
|
|
89911
|
-
|
|
89912
|
-
|
|
89913
|
-
|
|
89914
|
-
await
|
|
89915
|
-
//
|
|
89916
|
-
const sh0 = new Uint8Array(width * height * channels);
|
|
89917
|
-
const sh0Names = ['f_dc_0', 'f_dc_1', 'f_dc_2', 'opacity'];
|
|
89918
|
-
const sh0Columns = sh0Names.map(name => dataTable.getColumnByName(name));
|
|
89919
|
-
const sh0MinMax = calcMinMax(dataTable, sh0Names, indices);
|
|
89920
|
-
for (let i = 0; i < indices.length; ++i) {
|
|
89921
|
-
dataTable.getRow(indices[i], row, sh0Columns);
|
|
89922
|
-
const ti = layout(i);
|
|
89923
|
-
sh0[ti * 4] = 255 * (row.f_dc_0 - sh0MinMax[0][0]) / (sh0MinMax[0][1] - sh0MinMax[0][0]);
|
|
89924
|
-
sh0[ti * 4 + 1] = 255 * (row.f_dc_1 - sh0MinMax[1][0]) / (sh0MinMax[1][1] - sh0MinMax[1][0]);
|
|
89925
|
-
sh0[ti * 4 + 2] = 255 * (row.f_dc_2 - sh0MinMax[2][0]) / (sh0MinMax[2][1] - sh0MinMax[2][0]);
|
|
89926
|
-
sh0[ti * 4 + 3] = 255 * (row.opacity - sh0MinMax[3][0]) / (sh0MinMax[3][1] - sh0MinMax[3][0]);
|
|
89927
|
-
}
|
|
89928
|
-
await write('sh0.webp', sh0);
|
|
89929
|
-
// write meta.json
|
|
90152
|
+
const gpuDevice = shMethod === 'gpu' ? await createDevice() : null;
|
|
90153
|
+
// convert scale
|
|
90154
|
+
const scaleData = await cluster1d(new DataTable(['scale_0', 'scale_1', 'scale_2'].map(name => dataTable.getColumnByName(name))), shIterations, gpuDevice);
|
|
90155
|
+
await writeTableData('scales.webp', scaleData.labels);
|
|
90156
|
+
// color and opacity
|
|
90157
|
+
const colorData = await cluster1d(new DataTable(['f_dc_0', 'f_dc_1', 'f_dc_2'].map(name => dataTable.getColumnByName(name))), shIterations, gpuDevice);
|
|
90158
|
+
// generate and store sigmoid(opacity) [0..1]
|
|
90159
|
+
const opacity = dataTable.getColumnByName('opacity').data;
|
|
90160
|
+
const opacityData = new Uint8Array(opacity.length);
|
|
90161
|
+
for (let i = 0; i < numRows; ++i) {
|
|
90162
|
+
opacityData[i] = Math.max(0, Math.min(255, sigmoid(opacity[i]) * 255));
|
|
90163
|
+
}
|
|
90164
|
+
colorData.labels.addColumn(new Column('opacity', opacityData));
|
|
90165
|
+
await writeTableData('sh0.webp', colorData.labels);
|
|
90166
|
+
// construct meta.json
|
|
89930
90167
|
const meta = {
|
|
90168
|
+
version: 2,
|
|
90169
|
+
count: numRows,
|
|
89931
90170
|
means: {
|
|
89932
|
-
shape: [numRows, 3],
|
|
89933
|
-
dtype: 'float32',
|
|
89934
90171
|
mins: meansMinMax.map(v => v[0]),
|
|
89935
90172
|
maxs: meansMinMax.map(v => v[1]),
|
|
89936
90173
|
files: [
|
|
@@ -89939,29 +90176,19 @@ const writeSog = async (fileHandle, dataTable, outputFilename, shIterations = 10
|
|
|
89939
90176
|
]
|
|
89940
90177
|
},
|
|
89941
90178
|
scales: {
|
|
89942
|
-
|
|
89943
|
-
dtype: 'float32',
|
|
89944
|
-
mins: scaleMinMax.map(v => v[0]),
|
|
89945
|
-
maxs: scaleMinMax.map(v => v[1]),
|
|
90179
|
+
codebook: Array.from(scaleData.centroids.getColumn(0).data),
|
|
89946
90180
|
files: ['scales.webp']
|
|
89947
90181
|
},
|
|
89948
90182
|
quats: {
|
|
89949
|
-
shape: [numRows, 4],
|
|
89950
|
-
dtype: 'uint8',
|
|
89951
|
-
encoding: 'quaternion_packed',
|
|
89952
90183
|
files: ['quats.webp']
|
|
89953
90184
|
},
|
|
89954
90185
|
sh0: {
|
|
89955
|
-
|
|
89956
|
-
dtype: 'float32',
|
|
89957
|
-
mins: sh0MinMax.map(v => v[0]),
|
|
89958
|
-
maxs: sh0MinMax.map(v => v[1]),
|
|
90186
|
+
codebook: Array.from(colorData.centroids.getColumn(0).data),
|
|
89959
90187
|
files: ['sh0.webp']
|
|
89960
90188
|
}
|
|
89961
90189
|
};
|
|
89962
90190
|
// spherical harmonics
|
|
89963
90191
|
const shBands = { '9': 1, '24': 2, '-1': 3 }[shNames.findIndex(v => !dataTable.hasColumn(v))] ?? 0;
|
|
89964
|
-
// @ts-ignore
|
|
89965
90192
|
if (shBands > 0) {
|
|
89966
90193
|
const shCoeffs = [0, 3, 8, 15][shBands];
|
|
89967
90194
|
const shColumnNames = shNames.slice(0, shCoeffs * 3);
|
|
@@ -89974,23 +90201,21 @@ const writeSog = async (fileHandle, dataTable, outputFilename, shIterations = 10
|
|
|
89974
90201
|
const shDataTable = new DataTable(shColumns);
|
|
89975
90202
|
const paletteSize = Math.min(64, 2 ** Math.floor(Math.log2(indices.length / 1024))) * 1024;
|
|
89976
90203
|
// calculate kmeans
|
|
89977
|
-
const gpuDevice = shMethod === 'gpu' ? await createDevice() : null;
|
|
89978
90204
|
const { centroids, labels } = await kmeans(shDataTable, paletteSize, shIterations, gpuDevice);
|
|
90205
|
+
// construct a codebook for all spherical harmonic coefficients
|
|
90206
|
+
const codebook = await cluster1d(centroids, shIterations, gpuDevice);
|
|
89979
90207
|
// write centroids
|
|
89980
90208
|
const centroidsBuf = new Uint8Array(64 * shCoeffs * Math.ceil(centroids.numRows / 64) * channels);
|
|
89981
|
-
const centroidsMinMax = calcMinMax(shDataTable, shColumnNames, indices);
|
|
89982
|
-
const centroidsMin = centroidsMinMax.map(v => v[0]).reduce((a, b) => Math.min(a, b));
|
|
89983
|
-
const centroidsMax = centroidsMinMax.map(v => v[1]).reduce((a, b) => Math.max(a, b));
|
|
89984
90209
|
const centroidsRow = {};
|
|
89985
90210
|
for (let i = 0; i < centroids.numRows; ++i) {
|
|
89986
|
-
|
|
90211
|
+
codebook.labels.getRow(i, centroidsRow);
|
|
89987
90212
|
for (let j = 0; j < shCoeffs; ++j) {
|
|
89988
90213
|
const x = centroidsRow[shColumnNames[shCoeffs * 0 + j]];
|
|
89989
90214
|
const y = centroidsRow[shColumnNames[shCoeffs * 1 + j]];
|
|
89990
90215
|
const z = centroidsRow[shColumnNames[shCoeffs * 2 + j]];
|
|
89991
|
-
centroidsBuf[i * shCoeffs * 4 + j * 4 + 0] =
|
|
89992
|
-
centroidsBuf[i * shCoeffs * 4 + j * 4 + 1] =
|
|
89993
|
-
centroidsBuf[i * shCoeffs * 4 + j * 4 + 2] =
|
|
90216
|
+
centroidsBuf[i * shCoeffs * 4 + j * 4 + 0] = x;
|
|
90217
|
+
centroidsBuf[i * shCoeffs * 4 + j * 4 + 1] = y;
|
|
90218
|
+
centroidsBuf[i * shCoeffs * 4 + j * 4 + 2] = z;
|
|
89994
90219
|
centroidsBuf[i * shCoeffs * 4 + j * 4 + 3] = 0xff;
|
|
89995
90220
|
}
|
|
89996
90221
|
}
|
|
@@ -90000,25 +90225,29 @@ const writeSog = async (fileHandle, dataTable, outputFilename, shIterations = 10
|
|
|
90000
90225
|
for (let i = 0; i < indices.length; ++i) {
|
|
90001
90226
|
const label = labels[indices[i]];
|
|
90002
90227
|
const ti = layout(i);
|
|
90003
|
-
labelsBuf[ti * 4] =
|
|
90004
|
-
labelsBuf[ti * 4 + 1] = (label >> 8)
|
|
90228
|
+
labelsBuf[ti * 4 + 0] = 0xff & label;
|
|
90229
|
+
labelsBuf[ti * 4 + 1] = 0xff & (label >> 8);
|
|
90005
90230
|
labelsBuf[ti * 4 + 2] = 0;
|
|
90006
90231
|
labelsBuf[ti * 4 + 3] = 0xff;
|
|
90007
90232
|
}
|
|
90008
90233
|
await write('shN_labels.webp', labelsBuf);
|
|
90009
90234
|
meta.shN = {
|
|
90010
|
-
|
|
90011
|
-
dtype: 'float32',
|
|
90012
|
-
mins: centroidsMin,
|
|
90013
|
-
maxs: centroidsMax,
|
|
90014
|
-
quantization: 8,
|
|
90235
|
+
codebook: Array.from(codebook.centroids.getColumn(0).data),
|
|
90015
90236
|
files: [
|
|
90016
90237
|
'shN_centroids.webp',
|
|
90017
90238
|
'shN_labels.webp'
|
|
90018
90239
|
]
|
|
90019
90240
|
};
|
|
90020
90241
|
}
|
|
90021
|
-
|
|
90242
|
+
const metaJson = (new TextEncoder()).encode(JSON.stringify(meta));
|
|
90243
|
+
if (zipWriter) {
|
|
90244
|
+
await zipWriter.file('meta.json', metaJson);
|
|
90245
|
+
await zipWriter.close();
|
|
90246
|
+
await fileWriter.close();
|
|
90247
|
+
}
|
|
90248
|
+
else {
|
|
90249
|
+
await fileHandle.write(metaJson);
|
|
90250
|
+
}
|
|
90022
90251
|
};
|
|
90023
90252
|
|
|
90024
90253
|
const readFile = async (filename) => {
|
|
@@ -90056,7 +90285,7 @@ const getOutputFormat = (filename) => {
|
|
|
90056
90285
|
if (lowerFilename.endsWith('.csv')) {
|
|
90057
90286
|
return 'csv';
|
|
90058
90287
|
}
|
|
90059
|
-
else if (lowerFilename.endsWith('meta.json')) {
|
|
90288
|
+
else if (lowerFilename.endsWith('.sog') || lowerFilename.endsWith('meta.json')) {
|
|
90060
90289
|
return 'sog';
|
|
90061
90290
|
}
|
|
90062
90291
|
else if (lowerFilename.endsWith('.compressed.ply')) {
|
|
@@ -90303,10 +90532,10 @@ USAGE
|
|
|
90303
90532
|
interpreted as actions that modify the final result.
|
|
90304
90533
|
|
|
90305
90534
|
SUPPORTED INPUTS
|
|
90306
|
-
.ply .splat .ksplat
|
|
90535
|
+
.ply .compressed.ply .splat .ksplat
|
|
90307
90536
|
|
|
90308
90537
|
SUPPORTED OUTPUTS
|
|
90309
|
-
.ply .compressed.ply meta.json (
|
|
90538
|
+
.ply .compressed.ply meta.json (SOG) .sog .csv
|
|
90310
90539
|
|
|
90311
90540
|
ACTIONS (can be repeated, in any order)
|
|
90312
90541
|
-t, --translate x,y,z Translate splats by (x, y, z)
|