boofcv.io.points.impl.PlyCodec Maven / Gradle / Ivy
/*
* Copyright (c) 2011-2020, Peter Abeles. All Rights Reserved.
*
* This file is part of BoofCV (http://boofcv.org).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package boofcv.io.points.impl;
import boofcv.alg.cloud.PointCloudReader;
import boofcv.alg.cloud.PointCloudWriter;
import boofcv.io.UtilIO;
import georegression.struct.point.Point3D_F64;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
/**
* For reading PLY point files
*
* @author Peter Abeles
*/
public class PlyCodec {
public static void saveAscii(PointCloudReader cloud , boolean saveRgb , Writer outputWriter ) throws IOException {
outputWriter.write("ply\n");
outputWriter.write("format ascii 1.0\n");
outputWriter.write("comment Created using BoofCV!\n");
outputWriter.write("element vertex "+cloud.size()+"\n" +
"property float x\n" +
"property float y\n" +
"property float z\n");
if( saveRgb ) {
outputWriter.write(
"property uchar red\n" +
"property uchar green\n" +
"property uchar blue\n");
}
outputWriter.write("end_header\n");
Point3D_F64 p = new Point3D_F64();
for (int i = 0; i < cloud.size(); i++) {
cloud.get(i,p);
if( saveRgb ) {
int rgb = cloud.getRGB(i);
int r = (rgb >> 16)&0xFF;
int g = (rgb >> 8)&0xFF;
int b = rgb&0xFF;
outputWriter.write(String.format("%f %f %f %d %d %d\n",p.x,p.y,p.z,r,g,b));
} else {
outputWriter.write(String.format("%f %f %f\n", p.x, p.y, p.z));
}
}
outputWriter.flush();
}
/**
* Saves data in binary format
*
* @param cloud (Input) Point cloud data
* @param order The byte order of the binary data. ByteOrder.BIG_ENDIAN is recommended
* @param saveRgb if true it will save RGB information
* @param saveAsFloat if true it will save it as a 4-byte float and if false as an 8-byte double
* @param outputWriter Stream it will write to
* @throws IOException
*/
public static void saveBinary(PointCloudReader cloud , ByteOrder order, boolean saveRgb , boolean saveAsFloat ,
OutputStream outputWriter ) throws IOException {
String format = "UTF-8";
String dataType = saveAsFloat ? "float" : "double";
int dataLength = saveAsFloat ? 4 : 8;
outputWriter.write("ply\n".getBytes(format));
outputWriter.write("format binary_big_endian 1.0\n".getBytes(format));
outputWriter.write("comment Created using BoofCV!\n".getBytes(format));
outputWriter.write(("element vertex "+cloud.size()+"\n").getBytes(format));
outputWriter.write((
"property "+dataType+" x\n" +
"property "+dataType+" y\n" +
"property "+dataType+" z\n").getBytes(format));
if( saveRgb ) {
outputWriter.write(
("property uchar red\n" +
"property uchar green\n" +
"property uchar blue\n").getBytes(format));
}
outputWriter.write("end_header\n".getBytes(format));
int end = dataLength*3;
var bytes = ByteBuffer.allocate(dataLength*3 + (saveRgb?3:0));
bytes.order(order);
Point3D_F64 p = new Point3D_F64();
for (int i = 0; i < cloud.size(); i++) {
cloud.get(i,p);
if( saveAsFloat ) {
bytes.putFloat(0, (float) p.x);
bytes.putFloat(4, (float) p.y);
bytes.putFloat(8, (float) p.z);
} else {
bytes.putDouble(0, p.x);
bytes.putDouble(8, p.y);
bytes.putDouble(16, p.z);
}
if( saveRgb ) {
int rgb = cloud.getRGB(i);
int r = (rgb >> 16)&0xFF;
int g = (rgb >> 8)&0xFF;
int b = rgb&0xFF;
bytes.put(end,(byte)r);
bytes.put(end+1,(byte)g);
bytes.put(end+2,(byte)b);
}
outputWriter.write(bytes.array());
}
outputWriter.flush();
}
private static String readNextPly(InputStream reader , boolean failIfNull , StringBuffer buffer ) throws IOException {
String line = UtilIO.readLine(reader,buffer);
while( line.length() != 0 ) {
if( line.startsWith("comment") )
line = UtilIO.readLine(reader,buffer);
else {
return line;
}
}
if( failIfNull )
throw new IOException("Unexpected end of file");
return line;
}
public static void read(InputStream input, PointCloudWriter output ) throws IOException {
StringBuffer buffer = new StringBuffer();
String line = UtilIO.readLine(input,buffer);
if( line.length() == 0 ) throw new IOException("Missing first line");
if( line.compareToIgnoreCase("ply")!=0 ) throw new IOException("Expected PLY at start of file");
var dataWords = new ArrayList();
int vertexCount = -1;
Format format = null;
boolean rgb = false;
line = readNextPly(input,true, buffer);
while( line.length() != 0) {
if( line.equals("end_header") )
break;
String[] words = line.split("\\s+");
if( words.length == 1 )
throw new IOException("Expected more than one word");
if( line.startsWith("format")) {
switch(words[1]) {
case "ascii":format = Format.ASCII;break;
case "binary_little_endian":format = Format.BINARY_LITTLE;break;
case "binary_big_endian":format = Format.BINARY_BIG;break;
default: throw new IOException("Unknown format "+words[1]);
}
} else if( line.startsWith("element")) {
if( words[1].equals("vertex")) {
vertexCount = Integer.parseInt(words[2]);
}
} else if( words[0].equals("property") ) {
DataType d;
switch( words[1].toLowerCase() ) {
case "float": d=DataType.FLOAT;break;
case "double":d=DataType.DOUBLE;break;
case "char": d=DataType.CHAR;break;
case "short": d=DataType.SHORT;break;
case "int": d=DataType.INT;break;
case "uchar": d=DataType.UCHAR;break;
case "ushort":d=DataType.USHORT;break;
case "uint": d=DataType.UINT;break;
default: throw new RuntimeException("Add support for "+words[1]);
}
VarType v;
switch( words[2].toLowerCase() ) {
case "x": v=VarType.X; break;
case "y": v=VarType.Y; break;
case "z": v=VarType.Z; break;
case "red": v=VarType.R; rgb = true;break;
case "green": v=VarType.G; rgb = true;break;
case "blue": v=VarType.B; rgb = true;break;
default: v=VarType.UNKNOWN; break;
}
dataWords.add( new DataWord(v,d));
} else {
throw new IOException("Unknown header element");
}
line = readNextPly(input,true, buffer);
}
if( vertexCount == -1 )
throw new IOException("File is missing vertex count");
if( format == null )
throw new IOException("Format is never specified");
output.init(vertexCount);
switch (format) {
case ASCII:readAscii(output, input, dataWords, buffer, vertexCount, rgb);break;
case BINARY_LITTLE:readBinary(output, input, dataWords, ByteOrder.LITTLE_ENDIAN, vertexCount, rgb);break;
case BINARY_BIG:readBinary(output, input, dataWords, ByteOrder.BIG_ENDIAN, vertexCount, rgb);break;
default: throw new RuntimeException("BUG!");
}
}
private static void readAscii(PointCloudWriter output, InputStream reader, List dataWords,
StringBuffer buffer, int vertexCount, boolean rgb) throws IOException
{
// storage for read in values
int I32=-1;
double F64=-1;
// values that are writen to that we care about
int r=-1,g=-1,b=-1;
double x=-1,y=-1,z=-1;
for (int i = 0; i < vertexCount; i++) {
String line = readNextPly(reader,true, buffer);
String[] words = line.split("\\s+");
if( words.length != dataWords.size())
throw new IOException("unexpected number of words. "+line);
for (int j = 0; j < dataWords.size(); j++) {
DataWord d = dataWords.get(j);
String word = words[j];
switch( d.data ) {
case FLOAT:
case DOUBLE: F64 = Double.parseDouble(word); break;
case UINT:
case INT:
case USHORT:
case SHORT:
case UCHAR:
case CHAR: I32 = Integer.parseInt(word); break;
default: throw new RuntimeException("Unsupported");
}
switch( d.var ) {
case X: x = F64; break;
case Y: y = F64; break;
case Z: z = F64; break;
case R: r = I32; break;
case G: g = I32; break;
case B: b = I32; break;
}
}
if( rgb ) {
output.add(x,y,z, r << 16 | g << 8 | b);
} else {
output.add(x,y,z);
}
}
}
private static void readBinary(PointCloudWriter output, InputStream reader, List dataWords,
ByteOrder order,
int vertexCount, boolean rgb) throws IOException {
int totalBytes = 0;
for (int i = 0; i < dataWords.size(); i++) {
totalBytes += dataWords.get(i).data.size;
}
final byte[] line = new byte[totalBytes];
final ByteBuffer bb = ByteBuffer.wrap(line);
bb.order(order);
// storage for read in values
int I32=-1;
double F64=-1;
// values that are writen to that we care about
int r=-1,g=-1,b=-1;
double x=-1,y=-1,z=-1;
for (int i = 0; i < vertexCount; i++) {
int found = reader.read(line);
if( line.length != found )
throw new IOException("Read unexpected number of bytes. "+found+" vs "+line.length);
int location = 0;
for (int j = 0; j < dataWords.size(); j++) {
DataWord d = dataWords.get(j);
switch( d.data ) {
case FLOAT: F64 = bb.getFloat(location); break;
case DOUBLE: F64 = bb.getDouble(location); break;
case CHAR: I32 = bb.get(location); break;
case UCHAR: I32 = bb.get(location)&0xFF; break;
case SHORT: I32 = bb.getShort(location); break;
case USHORT: I32 = bb.getShort(location)&0xFFFF; break;
case INT: I32 = bb.getInt(location); break;
case UINT: I32 = bb.getInt(location); break; // NOTE: not really uint...
default: throw new RuntimeException("Unsupported");
}
location += d.data.size;
switch( d.var ) {
case X: x = F64; break;
case Y: y = F64; break;
case Z: z = F64; break;
case R: r = I32; break;
case G: g = I32; break;
case B: b = I32; break;
}
}
if( rgb ) {
output.add(x,y,z, r << 16 | g << 8 | b);
} else {
output.add(x,y,z);
}
}
}
private static class DataWord
{
VarType var;
DataType data;
public DataWord(VarType var, DataType data) {
this.var = var;
this.data = data;
}
}
private enum VarType {
X,Y,Z,R,G,B,UNKNOWN
}
private enum DataType {
FLOAT(4),
DOUBLE(8),
CHAR(1),
SHORT(2),
INT(4),
UCHAR(1),
USHORT(2),
UINT(4);
int size;
DataType(int size) {
this.size = size;
}
}
private enum Format {
ASCII,
BINARY_LITTLE,
BINARY_BIG
}
public enum Encoding {
/**
* https://en.wikipedia.org/wiki/PLY_(file_format)
*/
PLY_ASCII,PLY_BINARY
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy