@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 CHANGED
@@ -1884,7 +1884,7 @@ let Quat$1 = class Quat {
1884
1884
  }
1885
1885
  };
1886
1886
 
1887
- var version$1 = "0.6.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
- initializeCentroids(points, centroids, row);
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
- calcAverage(points, groups[i], row);
89774
- centroids.setRow(i, row);
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) / 16) * 16;
89824
- const height = Math.ceil(numRows / width / 16) * 16;
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
- const write = (filename, data, w = width, h = height) => {
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
- return sharp(data, { raw: { width: w, height: h, channels } })
89830
- .webp({ lossless: true })
89831
- .toFile(pathname);
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
- // scales
89902
- const scales = new Uint8Array(width * height * channels);
89903
- const scaleNames = ['scale_0', 'scale_1', 'scale_2'];
89904
- const scaleColumns = scaleNames.map(name => dataTable.getColumnByName(name));
89905
- const scaleMinMax = calcMinMax(dataTable, scaleNames, indices);
89906
- for (let i = 0; i < indices.length; ++i) {
89907
- dataTable.getRow(indices[i], row, scaleColumns);
89908
- const ti = layout(i);
89909
- scales[ti * 4] = 255 * (row.scale_0 - scaleMinMax[0][0]) / (scaleMinMax[0][1] - scaleMinMax[0][0]);
89910
- scales[ti * 4 + 1] = 255 * (row.scale_1 - scaleMinMax[1][0]) / (scaleMinMax[1][1] - scaleMinMax[1][0]);
89911
- scales[ti * 4 + 2] = 255 * (row.scale_2 - scaleMinMax[2][0]) / (scaleMinMax[2][1] - scaleMinMax[2][0]);
89912
- scales[ti * 4 + 3] = 0xff;
89913
- }
89914
- await write('scales.webp', scales);
89915
- // colors
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
- shape: [numRows, 3],
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
- shape: [numRows, 1, 4],
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
- centroids.getRow(i, centroidsRow);
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] = 255 * ((x - centroidsMin) / (centroidsMax - centroidsMin));
89992
- centroidsBuf[i * shCoeffs * 4 + j * 4 + 1] = 255 * ((y - centroidsMin) / (centroidsMax - centroidsMin));
89993
- centroidsBuf[i * shCoeffs * 4 + j * 4 + 2] = 255 * ((z - centroidsMin) / (centroidsMax - centroidsMin));
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] = label & 0xff;
90004
- labelsBuf[ti * 4 + 1] = (label >> 8) & 0xff;
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
- shape: [indices.length, shCoeffs],
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
- await fileHandle.write((new TextEncoder()).encode(JSON.stringify(meta, null, 4)));
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 (SOGS) .csv
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)